From 818147a6298a1376d8df91b8cc684a1a3fc4ee30 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Thu, 24 Jun 2021 13:54:26 +1000 Subject: [PATCH 1/6] `swap_setup` instead of `spot_price` and `execution_setup` Having `spot_price` and `execution_setup` as separate protocols did not bring any advantages, but was problematic because we had to ensure that `execution_setup` would be triggered after `spot_price`. Because of this dependency it is better to combine the protocols into one. Combining the protocols also allows a refactoring to get rid of the `libp2p-async-await` dependency. Alice always listens for the `swap_setup` protocol. When Bob opens a substream on that protocol the spot price is communicated, and then all execution setup messages (swap-id and signature exchange). --- Cargo.lock | 32 +- swap/Cargo.toml | 1 - swap/src/bin/asb.rs | 4 - swap/src/bin/swap.rs | 36 +- swap/src/bitcoin.rs | 3 +- swap/src/database/bob.rs | 4 +- swap/src/monero.rs | 5 +- swap/src/monero/wallet.rs | 5 - swap/src/network.rs | 2 +- swap/src/network/spot_price.rs | 138 ---- swap/src/network/swap_setup.rs | 111 +++ swap/src/network/swarm.rs | 21 +- swap/src/protocol/alice.rs | 3 +- swap/src/protocol/alice/behaviour.rs | 35 +- swap/src/protocol/alice/event_loop.rs | 82 +- swap/src/protocol/alice/execution_setup.rs | 109 --- swap/src/protocol/alice/spot_price.rs | 825 --------------------- swap/src/protocol/alice/state.rs | 6 +- swap/src/protocol/alice/swap_setup.rs | 513 +++++++++++++ swap/src/protocol/bob.rs | 3 +- swap/src/protocol/bob/behaviour.rs | 26 +- swap/src/protocol/bob/cancel.rs | 2 +- swap/src/protocol/bob/event_loop.rs | 78 +- swap/src/protocol/bob/execution_setup.rs | 95 --- swap/src/protocol/bob/refund.rs | 2 +- swap/src/protocol/bob/spot_price.rs | 86 --- swap/src/protocol/bob/state.rs | 4 +- swap/src/protocol/bob/swap.rs | 59 +- swap/src/protocol/bob/swap_setup.rs | 307 ++++++++ swap/tests/harness/mod.rs | 21 +- 30 files changed, 1059 insertions(+), 1559 deletions(-) delete mode 100644 swap/src/network/spot_price.rs create mode 100644 swap/src/network/swap_setup.rs delete mode 100644 swap/src/protocol/alice/execution_setup.rs delete mode 100644 swap/src/protocol/alice/spot_price.rs create mode 100644 swap/src/protocol/alice/swap_setup.rs delete mode 100644 swap/src/protocol/bob/execution_setup.rs delete mode 100644 swap/src/protocol/bob/spot_price.rs create mode 100644 swap/src/protocol/bob/swap_setup.rs diff --git a/Cargo.lock b/Cargo.lock index ff3dc569..8e586d62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1768,26 +1768,6 @@ version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba4aede83fc3617411dc6993bc8c70919750c1c257c6ca6a502aed6e0e2394ae" -[[package]] -name = "libp2p" -version = "0.37.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08053fbef67cd777049ef7a95ebaca2ece370b4ed7712c3fa404d69a88cb741b" -dependencies = [ - "atomic", - "bytes 1.0.1", - "futures", - "lazy_static", - "libp2p-core", - "libp2p-swarm", - "libp2p-swarm-derive", - "parity-multiaddr", - "parking_lot 0.11.1", - "pin-project 1.0.5", - "smallvec", - "wasm-timer", -] - [[package]] name = "libp2p" version = "0.38.0" @@ -1816,15 +1796,6 @@ dependencies = [ "wasm-timer", ] -[[package]] -name = "libp2p-async-await" -version = "0.1.0" -source = "git+https://github.com/comit-network/rust-libp2p-async-await#50e781b12bbeda7986c0cada090f171f41093144" -dependencies = [ - "libp2p 0.37.1", - "log 0.4.14", -] - [[package]] name = "libp2p-core" version = "0.28.3" @@ -3946,8 +3917,7 @@ dependencies = [ "get-port", "hyper 0.14.9", "itertools 0.10.1", - "libp2p 0.38.0", - "libp2p-async-await", + "libp2p", "miniscript", "monero", "monero-harness", diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 8c6a709f..46deee8a 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -30,7 +30,6 @@ ed25519-dalek = "1" futures = { version = "0.3", default-features = false } itertools = "0.10" libp2p = { version = "0.38", default-features = false, features = [ "tcp-tokio", "yamux", "mplex", "dns-tokio", "noise", "request-response", "websocket", "ping" ] } -libp2p-async-await = { git = "https://github.com/comit-network/rust-libp2p-async-await" } miniscript = { version = "5", features = [ "serde" ] } monero = { version = "0.12", features = [ "serde_support" ] } monero-rpc = { path = "../monero-rpc" } diff --git a/swap/src/bin/asb.rs b/swap/src/bin/asb.rs index ef590dd1..63b4c459 100644 --- a/swap/src/bin/asb.rs +++ b/swap/src/bin/asb.rs @@ -144,13 +144,9 @@ async fn main() -> Result<()> { } }; - let current_balance = monero_wallet.get_balance().await?; - let lock_fee = monero_wallet.static_tx_fee_estimate(); let kraken_rate = KrakenRate::new(config.maker.ask_spread, kraken_price_updates); let mut swarm = swarm::asb( &seed, - current_balance, - lock_fee, config.maker.min_buy_btc, config.maker.max_buy_btc, kraken_rate.clone(), diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index 0cb557b7..71fff4f3 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -85,20 +85,22 @@ async fn main() -> Result<()> { init_monero_wallet(data_dir, monero_daemon_address, env_config).await?; let bitcoin_wallet = Arc::new(bitcoin_wallet); - let mut swarm = swarm::cli(&seed, seller_peer_id, tor_socks5_port).await?; + let mut swarm = swarm::cli( + &seed, + seller_peer_id, + tor_socks5_port, + env_config, + bitcoin_wallet.clone(), + ) + .await?; swarm .behaviour_mut() .add_address(seller_peer_id, seller_addr); tracing::debug!(peer_id = %swarm.local_peer_id(), "Network layer initialized"); - let (event_loop, mut event_loop_handle) = EventLoop::new( - swap_id, - swarm, - seller_peer_id, - bitcoin_wallet.clone(), - env_config, - )?; + let (event_loop, mut event_loop_handle) = + EventLoop::new(swap_id, swarm, seller_peer_id, env_config)?; let event_loop = tokio::spawn(event_loop.run()); let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size()); @@ -185,20 +187,22 @@ async fn main() -> Result<()> { let seller_peer_id = db.get_peer_id(swap_id)?; - let mut swarm = swarm::cli(&seed, seller_peer_id, tor_socks5_port).await?; + let mut swarm = swarm::cli( + &seed, + seller_peer_id, + tor_socks5_port, + env_config, + bitcoin_wallet.clone(), + ) + .await?; let our_peer_id = swarm.local_peer_id(); tracing::debug!(peer_id = %our_peer_id, "Initializing network module"); swarm .behaviour_mut() .add_address(seller_peer_id, seller_addr); - let (event_loop, event_loop_handle) = EventLoop::new( - swap_id, - swarm, - seller_peer_id, - bitcoin_wallet.clone(), - env_config, - )?; + let (event_loop, event_loop_handle) = + EventLoop::new(swap_id, swarm, seller_peer_id, env_config)?; let handle = tokio::spawn(event_loop.run()); let swap = Swap::from_db( diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index fc91996b..c44facdd 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -344,8 +344,7 @@ mod tests { tx_redeem_fee, tx_punish_fee, &mut OsRng, - ) - .unwrap(); + ); let bob_state0 = bob::State0::new( Uuid::new_v4(), diff --git a/swap/src/database/bob.rs b/swap/src/database/bob.rs index bbd5b6a0..b0258b00 100644 --- a/swap/src/database/bob.rs +++ b/swap/src/database/bob.rs @@ -46,7 +46,7 @@ impl From for Bob { fn from(bob_state: BobState) -> Self { match bob_state { BobState::Started { btc_amount } => Bob::Started { btc_amount }, - BobState::ExecutionSetupDone(state2) => Bob::ExecutionSetupDone { state2 }, + BobState::SwapSetupCompleted(state2) => Bob::ExecutionSetupDone { state2 }, BobState::BtcLocked(state3) => Bob::BtcLocked { state3 }, BobState::XmrLockProofReceived { state, @@ -78,7 +78,7 @@ impl From for BobState { fn from(db_state: Bob) -> Self { match db_state { Bob::Started { btc_amount } => BobState::Started { btc_amount }, - Bob::ExecutionSetupDone { state2 } => BobState::ExecutionSetupDone(state2), + Bob::ExecutionSetupDone { state2 } => BobState::SwapSetupCompleted(state2), Bob::BtcLocked { state3 } => BobState::BtcLocked(state3), Bob::XmrLockProofReceived { state, diff --git a/swap/src/monero.rs b/swap/src/monero.rs index c4649410..bcfd3a3d 100644 --- a/swap/src/monero.rs +++ b/swap/src/monero.rs @@ -81,6 +81,9 @@ pub struct PublicViewKey(PublicKey); #[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, PartialOrd)] pub struct Amount(u64); +// Median tx fees on Monero as found here: https://www.monero.how/monero-transaction-fees, XMR 0.000_015 * 2 (to be on the safe side) +pub const MONERO_FEE: Amount = Amount::from_piconero(30000000); + impl Amount { pub const ZERO: Self = Self(0); pub const ONE_XMR: Self = Self(PICONERO_OFFSET); @@ -88,7 +91,7 @@ impl Amount { /// piconeros. /// /// A piconero (a.k.a atomic unit) is equal to 1e-12 XMR. - pub fn from_piconero(amount: u64) -> Self { + pub const fn from_piconero(amount: u64) -> Self { Amount(amount) } diff --git a/swap/src/monero/wallet.rs b/swap/src/monero/wallet.rs index 4732a632..a63ae4ff 100644 --- a/swap/src/monero/wallet.rs +++ b/swap/src/monero/wallet.rs @@ -276,11 +276,6 @@ impl Wallet { pub async fn refresh(&self) -> Result { Ok(self.inner.lock().await.refresh().await?) } - - pub fn static_tx_fee_estimate(&self) -> Amount { - // Median tx fees on Monero as found here: https://www.monero.how/monero-transaction-fees, 0.000_015 * 2 (to be on the safe side) - Amount::from_monero(0.000_03f64).expect("static fee to be convertible without problems") - } } #[derive(Debug)] diff --git a/swap/src/network.rs b/swap/src/network.rs index fb4606fb..1eea35e4 100644 --- a/swap/src/network.rs +++ b/swap/src/network.rs @@ -5,7 +5,7 @@ pub mod encrypted_signature; pub mod json_pull_codec; pub mod quote; pub mod redial; -pub mod spot_price; +pub mod swap_setup; pub mod swarm; pub mod tor_transport; pub mod transfer_proof; diff --git a/swap/src/network/spot_price.rs b/swap/src/network/spot_price.rs deleted file mode 100644 index 8268d146..00000000 --- a/swap/src/network/spot_price.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::monero; -use crate::network::cbor_request_response::CborCodec; -use libp2p::core::ProtocolName; -use libp2p::request_response::{RequestResponse, RequestResponseEvent, RequestResponseMessage}; -use serde::{Deserialize, Serialize}; - -pub const PROTOCOL: &str = "/comit/xmr/btc/spot-price/1.0.0"; -pub type OutEvent = RequestResponseEvent; -pub type Message = RequestResponseMessage; - -pub type Behaviour = RequestResponse>; - -/// The spot price protocol allows parties to **initiate** a trade by requesting -/// a spot price. -/// -/// A spot price is binding for both parties, i.e. after the spot-price protocol -/// completes, both parties are expected to follow up with the `execution-setup` -/// protocol. -/// -/// If a party wishes to only inquire about the current price, they should use -/// the `quote` protocol instead. -#[derive(Debug, Clone, Copy, Default)] -pub struct SpotPriceProtocol; - -impl ProtocolName for SpotPriceProtocol { - fn protocol_name(&self) -> &[u8] { - PROTOCOL.as_bytes() - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Request { - #[serde(with = "::bitcoin::util::amount::serde::as_sat")] - pub btc: bitcoin::Amount, - pub blockchain_network: BlockchainNetwork, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum Response { - Xmr(monero::Amount), - Error(Error), -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum Error { - NoSwapsAccepted, - AmountBelowMinimum { - #[serde(with = "::bitcoin::util::amount::serde::as_sat")] - min: bitcoin::Amount, - #[serde(with = "::bitcoin::util::amount::serde::as_sat")] - buy: bitcoin::Amount, - }, - AmountAboveMaximum { - #[serde(with = "::bitcoin::util::amount::serde::as_sat")] - max: bitcoin::Amount, - #[serde(with = "::bitcoin::util::amount::serde::as_sat")] - buy: bitcoin::Amount, - }, - BalanceTooLow { - #[serde(with = "::bitcoin::util::amount::serde::as_sat")] - buy: bitcoin::Amount, - }, - BlockchainNetworkMismatch { - cli: BlockchainNetwork, - asb: BlockchainNetwork, - }, - /// To be used for errors that cannot be explained on the CLI side (e.g. - /// rate update problems on the seller side) - Other, -} - -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] -pub struct BlockchainNetwork { - #[serde(with = "crate::bitcoin::network")] - pub bitcoin: bitcoin::Network, - #[serde(with = "crate::monero::network")] - pub monero: monero::Network, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::monero; - - #[test] - fn snapshot_test_serialize() { - let amount = monero::Amount::from_piconero(100_000u64); - let xmr = r#"{"Xmr":100000}"#.to_string(); - let serialized = serde_json::to_string(&Response::Xmr(amount)).unwrap(); - assert_eq!(xmr, serialized); - - let error = r#"{"Error":"NoSwapsAccepted"}"#.to_string(); - let serialized = serde_json::to_string(&Response::Error(Error::NoSwapsAccepted)).unwrap(); - assert_eq!(error, serialized); - - let error = r#"{"Error":{"AmountBelowMinimum":{"min":0,"buy":0}}}"#.to_string(); - let serialized = serde_json::to_string(&Response::Error(Error::AmountBelowMinimum { - min: Default::default(), - buy: Default::default(), - })) - .unwrap(); - assert_eq!(error, serialized); - - let error = r#"{"Error":{"AmountAboveMaximum":{"max":0,"buy":0}}}"#.to_string(); - let serialized = serde_json::to_string(&Response::Error(Error::AmountAboveMaximum { - max: Default::default(), - buy: Default::default(), - })) - .unwrap(); - assert_eq!(error, serialized); - - let error = r#"{"Error":{"BalanceTooLow":{"buy":0}}}"#.to_string(); - let serialized = serde_json::to_string(&Response::Error(Error::BalanceTooLow { - buy: Default::default(), - })) - .unwrap(); - assert_eq!(error, serialized); - - let error = r#"{"Error":{"BlockchainNetworkMismatch":{"cli":{"bitcoin":"Mainnet","monero":"Mainnet"},"asb":{"bitcoin":"Testnet","monero":"Stagenet"}}}}"#.to_string(); - let serialized = - serde_json::to_string(&Response::Error(Error::BlockchainNetworkMismatch { - cli: BlockchainNetwork { - bitcoin: bitcoin::Network::Bitcoin, - monero: monero::Network::Mainnet, - }, - asb: BlockchainNetwork { - bitcoin: bitcoin::Network::Testnet, - monero: monero::Network::Stagenet, - }, - })) - .unwrap(); - assert_eq!(error, serialized); - - let error = r#"{"Error":"Other"}"#.to_string(); - let serialized = serde_json::to_string(&Response::Error(Error::Other)).unwrap(); - assert_eq!(error, serialized); - } -} diff --git a/swap/src/network/swap_setup.rs b/swap/src/network/swap_setup.rs new file mode 100644 index 00000000..fb5a5657 --- /dev/null +++ b/swap/src/network/swap_setup.rs @@ -0,0 +1,111 @@ +use crate::monero; +use anyhow::{Context, Result}; +use libp2p::core::upgrade; +use libp2p::swarm::NegotiatedSubstream; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; + +pub const BUF_SIZE: usize = 1024 * 1024; + +pub mod protocol { + use futures::future; + use libp2p::core::upgrade::{from_fn, FromFnUpgrade}; + use libp2p::core::Endpoint; + use libp2p::swarm::NegotiatedSubstream; + use void::Void; + + pub fn new() -> SwapSetup { + from_fn( + b"/comit/xmr/btc/swap_setup/1.0.0", + Box::new(|socket, _| future::ready(Ok(socket))), + ) + } + + pub type SwapSetup = FromFnUpgrade< + &'static [u8], + Box< + dyn Fn( + NegotiatedSubstream, + Endpoint, + ) -> future::Ready> + + Send + + 'static, + >, + >; +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] +pub struct BlockchainNetwork { + #[serde(with = "crate::bitcoin::network")] + pub bitcoin: bitcoin::Network, + #[serde(with = "crate::monero::network")] + pub monero: monero::Network, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SpotPriceRequest { + #[serde(with = "::bitcoin::util::amount::serde::as_sat")] + pub btc: bitcoin::Amount, + pub blockchain_network: BlockchainNetwork, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum SpotPriceResponse { + Xmr(monero::Amount), + Error(SpotPriceError), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum SpotPriceError { + NoSwapsAccepted, + AmountBelowMinimum { + #[serde(with = "::bitcoin::util::amount::serde::as_sat")] + min: bitcoin::Amount, + #[serde(with = "::bitcoin::util::amount::serde::as_sat")] + buy: bitcoin::Amount, + }, + AmountAboveMaximum { + #[serde(with = "::bitcoin::util::amount::serde::as_sat")] + max: bitcoin::Amount, + #[serde(with = "::bitcoin::util::amount::serde::as_sat")] + buy: bitcoin::Amount, + }, + BalanceTooLow { + #[serde(with = "::bitcoin::util::amount::serde::as_sat")] + buy: bitcoin::Amount, + }, + BlockchainNetworkMismatch { + cli: BlockchainNetwork, + asb: BlockchainNetwork, + }, + /// To be used for errors that cannot be explained on the CLI side (e.g. + /// rate update problems on the seller side) + Other, +} + +pub async fn read_cbor_message(substream: &mut NegotiatedSubstream) -> Result +where + T: DeserializeOwned, +{ + let bytes = upgrade::read_one(substream, BUF_SIZE) + .await + .context("Failed to read length-prefixed message from stream")?; + let mut de = serde_cbor::Deserializer::from_slice(&bytes); + let message = + T::deserialize(&mut de).context("Failed to deserialize bytes into message using CBOR")?; + + Ok(message) +} + +pub async fn write_cbor_message(substream: &mut NegotiatedSubstream, message: T) -> Result<()> +where + T: Serialize, +{ + let bytes = + serde_cbor::to_vec(&message).context("Failed to serialize message as bytes using CBOR")?; + upgrade::write_with_len_prefix(substream, &bytes) + .await + .context("Failed to write bytes as length-prefixed message")?; + + Ok(()) +} diff --git a/swap/src/network/swarm.rs b/swap/src/network/swarm.rs index 37e9f219..43d635d7 100644 --- a/swap/src/network/swarm.rs +++ b/swap/src/network/swarm.rs @@ -1,17 +1,16 @@ use crate::protocol::alice::event_loop::LatestRate; use crate::protocol::{alice, bob}; use crate::seed::Seed; -use crate::{asb, cli, env, monero, tor}; +use crate::{asb, bitcoin, cli, env, tor}; use anyhow::Result; use libp2p::swarm::SwarmBuilder; use libp2p::{PeerId, Swarm}; use std::fmt::Debug; +use std::sync::Arc; #[allow(clippy::too_many_arguments)] pub fn asb( seed: &Seed, - balance: monero::Amount, - lock_fee: monero::Amount, min_buy: bitcoin::Amount, max_buy: bitcoin::Amount, latest_rate: LR, @@ -19,17 +18,9 @@ pub fn asb( env_config: env::Config, ) -> Result>> where - LR: LatestRate + Send + 'static + Debug, + LR: LatestRate + Send + 'static + Debug + Clone, { - let behaviour = alice::Behaviour::new( - balance, - lock_fee, - min_buy, - max_buy, - latest_rate, - resume_only, - env_config, - ); + let behaviour = alice::Behaviour::new(min_buy, max_buy, latest_rate, resume_only, env_config); let identity = seed.derive_libp2p_identity(); let transport = asb::transport::new(&identity)?; @@ -48,13 +39,15 @@ pub async fn cli( seed: &Seed, alice: PeerId, tor_socks5_port: u16, + env_config: env::Config, + bitcoin_wallet: Arc, ) -> Result> { let maybe_tor_socks5_port = match tor::Client::new(tor_socks5_port).assert_tor_running().await { Ok(()) => Some(tor_socks5_port), Err(_) => None, }; - let behaviour = bob::Behaviour::new(alice); + let behaviour = bob::Behaviour::new(alice, env_config, bitcoin_wallet); let identity = seed.derive_libp2p_identity(); let transport = cli::transport::new(&identity, maybe_tor_socks5_port)?; diff --git a/swap/src/protocol/alice.rs b/swap/src/protocol/alice.rs index 9d5394ba..fa8a4317 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -19,11 +19,10 @@ pub use self::swap::{run, run_until}; mod behaviour; pub mod event_loop; -mod execution_setup; mod recovery; -mod spot_price; pub mod state; pub mod swap; +pub mod swap_setup; pub struct Swap { pub state: AliceState, diff --git a/swap/src/protocol/alice/behaviour.rs b/swap/src/protocol/alice/behaviour.rs index ce7334b9..97c1b4b6 100644 --- a/swap/src/protocol/alice/behaviour.rs +++ b/swap/src/protocol/alice/behaviour.rs @@ -1,8 +1,9 @@ +use crate::env; use crate::network::quote::BidQuote; use crate::network::{encrypted_signature, quote, transfer_proof}; use crate::protocol::alice::event_loop::LatestRate; -use crate::protocol::alice::{execution_setup, spot_price, State3}; -use crate::{env, monero}; +use crate::protocol::alice::swap_setup::WalletSnapshot; +use crate::protocol::alice::{swap_setup, State3}; use anyhow::{anyhow, Error}; use libp2p::ping::{Ping, PingEvent}; use libp2p::request_response::{RequestId, ResponseChannel}; @@ -11,24 +12,22 @@ use uuid::Uuid; #[derive(Debug)] pub enum OutEvent { - SwapRequestDeclined { - peer: PeerId, - error: spot_price::Error, + SwapSetupInitiated { + send_wallet_snapshot: bmrng::RequestReceiver, + }, + SwapSetupCompleted { + peer_id: PeerId, + swap_id: Uuid, + state3: Box, }, - ExecutionSetupStart { + SwapDeclined { peer: PeerId, - btc: bitcoin::Amount, - xmr: monero::Amount, + error: swap_setup::Error, }, QuoteRequested { channel: ResponseChannel, peer: PeerId, }, - ExecutionSetupDone { - bob_peer_id: PeerId, - swap_id: Uuid, - state3: Box, - }, TransferProofAcknowledged { peer: PeerId, id: RequestId, @@ -72,8 +71,7 @@ where LR: LatestRate + Send + 'static, { pub quote: quote::Behaviour, - pub spot_price: spot_price::Behaviour, - pub execution_setup: execution_setup::Behaviour, + pub swap_setup: swap_setup::Behaviour, pub transfer_proof: transfer_proof::Behaviour, pub encrypted_signature: encrypted_signature::Behaviour, @@ -88,8 +86,6 @@ where LR: LatestRate + Send + 'static, { pub fn new( - balance: monero::Amount, - lock_fee: monero::Amount, min_buy: bitcoin::Amount, max_buy: bitcoin::Amount, latest_rate: LR, @@ -98,16 +94,13 @@ where ) -> Self { Self { quote: quote::alice(), - spot_price: spot_price::Behaviour::new( - balance, - lock_fee, + swap_setup: swap_setup::Behaviour::new( min_buy, max_buy, env_config, latest_rate, resume_only, ), - execution_setup: Default::default(), transfer_proof: transfer_proof::alice(), encrypted_signature: encrypted_signature::alice(), ping: Ping::default(), diff --git a/swap/src/protocol/alice/event_loop.rs b/swap/src/protocol/alice/event_loop.rs index 7f90db25..6456e152 100644 --- a/swap/src/protocol/alice/event_loop.rs +++ b/swap/src/protocol/alice/event_loop.rs @@ -3,7 +3,8 @@ use crate::database::Database; use crate::env::Config; use crate::network::quote::BidQuote; use crate::network::transfer_proof; -use crate::protocol::alice::{AliceState, Behaviour, OutEvent, State0, State3, Swap}; +use crate::protocol::alice::swap_setup::WalletSnapshot; +use crate::protocol::alice::{AliceState, Behaviour, OutEvent, State3, Swap}; use crate::{bitcoin, kraken, monero}; use anyhow::{Context, Result}; use futures::future; @@ -12,7 +13,6 @@ use futures::stream::{FuturesUnordered, StreamExt}; use libp2p::request_response::{RequestId, ResponseChannel}; use libp2p::swarm::SwarmEvent; use libp2p::{PeerId, Swarm}; -use rand::rngs::OsRng; use rust_decimal::Decimal; use std::collections::HashMap; use std::convert::Infallible; @@ -34,7 +34,7 @@ type OutgoingTransferProof = #[allow(missing_debug_implementations)] pub struct EventLoop where - LR: LatestRate + Send + 'static + Debug, + LR: LatestRate + Send + 'static + Debug + Clone, { swarm: libp2p::Swarm>, env_config: Config, @@ -64,7 +64,7 @@ where impl EventLoop where - LR: LatestRate + Send + 'static + Debug, + LR: LatestRate + Send + 'static + Debug + Clone, { #[allow(clippy::too_many_arguments)] pub fn new( @@ -150,77 +150,34 @@ where tokio::select! { swarm_event = self.swarm.next_event() => { match swarm_event { - SwarmEvent::Behaviour(OutEvent::ExecutionSetupStart { peer, btc, xmr }) => { - - let tx_redeem_fee = self.bitcoin_wallet - .estimate_fee(bitcoin::TxRedeem::weight(), btc) - .await; - let tx_punish_fee = self.bitcoin_wallet - .estimate_fee(bitcoin::TxPunish::weight(), btc) - .await; - let redeem_address = self.bitcoin_wallet.new_address().await; - let punish_address = self.bitcoin_wallet.new_address().await; - - let (redeem_address, punish_address) = match ( - redeem_address, - punish_address, - ) { - (Ok(redeem_address), Ok(punish_address)) => { - (redeem_address, punish_address) - } - _ => { - tracing::error!(%peer, "Failed to get new address during execution setup"); - continue; - } - }; + SwarmEvent::Behaviour(OutEvent::SwapSetupInitiated { mut send_wallet_snapshot }) => { - let (tx_redeem_fee, tx_punish_fee) = match ( - tx_redeem_fee, - tx_punish_fee, - ) { - (Ok(tx_redeem_fee), Ok(tx_punish_fee)) => { - (tx_redeem_fee, tx_punish_fee) - } - _ => { - tracing::error!(%peer, "Failed to calculate transaction fees during execution setup"); + let (btc, responder) = match send_wallet_snapshot.recv().await { + Ok((btc, responder)) => (btc, responder), + Err(error) => { + tracing::error!("Swap request will be ignored because of a failure when requesting information for the wallet snapshot: {:#}", error); continue; } }; - let state0 = match State0::new( - btc, - xmr, - self.env_config, - redeem_address, - punish_address, - tx_redeem_fee, - tx_punish_fee, - &mut OsRng - ) { - Ok(state) => state, + let wallet_snapshot = match WalletSnapshot::capture(&self.bitcoin_wallet, &self.monero_wallet, btc).await { + Ok(wallet_snapshot) => wallet_snapshot, Err(error) => { - tracing::warn!(%peer, "Failed to make State0 for execution setup. Error {:#}", error); + tracing::error!("Swap request will be ignored because we were unable to create wallet snapshot for swap: {:#}", error); continue; } }; - self.swarm.behaviour_mut().execution_setup.run(peer, state0); + // Ignore result, we should never hit this because the receiver will alive as long as the connection is. + let _ = responder.respond(wallet_snapshot); } - SwarmEvent::Behaviour(OutEvent::SwapRequestDeclined { peer, error }) => { + SwarmEvent::Behaviour(OutEvent::SwapSetupCompleted{peer_id, swap_id, state3}) => { + let _ = self.handle_execution_setup_done(peer_id, swap_id, *state3).await; + } + SwarmEvent::Behaviour(OutEvent::SwapDeclined { peer, error }) => { tracing::warn!(%peer, "Ignoring spot price request because: {}", error); } SwarmEvent::Behaviour(OutEvent::QuoteRequested { channel, peer }) => { - // TODO: Move the spot-price update into dedicated update stream to decouple it from quote requests - let current_balance = self.monero_wallet.get_balance().await; - match current_balance { - Ok(balance) => { - self.swarm.behaviour_mut().spot_price.update_balance(balance); - } - Err(e) => { - tracing::error!("Failed to fetch Monero balance: {:#}", e); - } - } - let quote = match self.make_quote(self.min_buy, self.max_buy).await { Ok(quote) => quote, Err(error) => { @@ -233,9 +190,6 @@ where tracing::debug!(%peer, "Failed to respond with quote"); } } - SwarmEvent::Behaviour(OutEvent::ExecutionSetupDone{bob_peer_id, swap_id, state3}) => { - let _ = self.handle_execution_setup_done(bob_peer_id, swap_id, *state3).await; - } SwarmEvent::Behaviour(OutEvent::TransferProofAcknowledged { peer, id }) => { tracing::debug!(%peer, "Bob acknowledged transfer proof"); if let Some(responder) = self.inflight_transfer_proofs.remove(&id) { diff --git a/swap/src/protocol/alice/execution_setup.rs b/swap/src/protocol/alice/execution_setup.rs deleted file mode 100644 index 21a371a9..00000000 --- a/swap/src/protocol/alice/execution_setup.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::network::cbor_request_response::BUF_SIZE; -use crate::protocol::alice::{State0, State3}; -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)] -pub enum OutEvent { - Done { - bob_peer_id: PeerId, - swap_id: Uuid, - state3: State3, - }, - Failure { - peer: PeerId, - error: Error, - }, -} - -impl From> for OutEvent { - fn from(event: BehaviourOutEvent<(PeerId, (Uuid, State3)), (), Error>) -> Self { - match event { - BehaviourOutEvent::Inbound(_, Ok((bob_peer_id, (swap_id, state3)))) => OutEvent::Done { - bob_peer_id, - swap_id, - state3, - }, - BehaviourOutEvent::Inbound(peer, Err(e)) => OutEvent::Failure { peer, error: e }, - BehaviourOutEvent::Outbound(..) => unreachable!("Alice only supports inbound"), - } - } -} - -#[derive(libp2p::NetworkBehaviour)] -#[behaviour(out_event = "OutEvent", event_process = false)] -pub struct Behaviour { - inner: libp2p_async_await::Behaviour<(PeerId, (Uuid, State3)), (), anyhow::Error>, -} - -impl Default for Behaviour { - fn default() -> Self { - Self { - inner: libp2p_async_await::Behaviour::new(b"/comit/xmr/btc/execution_setup/1.0.0"), - } - } -} - -impl Behaviour { - pub fn run(&mut self, bob: PeerId, state0: State0) { - 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")?; - let (swap_id, state1) = state0.receive(message0)?; - - substream - .write_message( - &serde_cbor::to_vec(&state1.next_message()) - .context("Failed to serialize message1")?, - ) - .await?; - - let message2 = - serde_cbor::from_slice::(&substream.read_message(BUF_SIZE).await?) - .context("Failed to deserialize message2")?; - let state2 = state1 - .receive(message2) - .context("Failed to receive Message2")?; - - substream - .write_message( - &serde_cbor::to_vec(&state2.next_message()) - .context("Failed to serialize message3")?, - ) - .await?; - - let message4 = - serde_cbor::from_slice::(&substream.read_message(BUF_SIZE).await?) - .context("Failed to deserialize message4")?; - let state3 = state2.receive(message4)?; - - Ok((bob, (swap_id, state3))) - }; - - async move { tokio::time::timeout(Duration::from_secs(60), protocol).await? } - }); - } -} - -impl From for alice::OutEvent { - fn from(event: OutEvent) -> Self { - match event { - OutEvent::Done { - bob_peer_id, - state3, - swap_id, - } => Self::ExecutionSetupDone { - bob_peer_id, - state3: Box::new(state3), - swap_id, - }, - OutEvent::Failure { peer, error } => Self::Failure { peer, error }, - } - } -} diff --git a/swap/src/protocol/alice/spot_price.rs b/swap/src/protocol/alice/spot_price.rs deleted file mode 100644 index 43fcf600..00000000 --- a/swap/src/protocol/alice/spot_price.rs +++ /dev/null @@ -1,825 +0,0 @@ -use crate::network::cbor_request_response::CborCodec; -use crate::network::spot_price; -use crate::network::spot_price::{BlockchainNetwork, SpotPriceProtocol}; -use crate::protocol::alice; -use crate::protocol::alice::event_loop::LatestRate; -use crate::{env, monero}; -use libp2p::request_response::{ - ProtocolSupport, RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, - ResponseChannel, -}; -use libp2p::swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}; -use libp2p::{NetworkBehaviour, PeerId}; -use std::collections::VecDeque; -use std::fmt::Debug; -use std::task::{Context, Poll}; - -#[derive(Debug)] -pub enum OutEvent { - ExecutionSetupParams { - peer: PeerId, - btc: bitcoin::Amount, - xmr: monero::Amount, - }, - Error { - peer: PeerId, - error: Error, - }, -} - -#[derive(NetworkBehaviour)] -#[behaviour(out_event = "OutEvent", poll_method = "poll", event_process = true)] -#[allow(missing_debug_implementations)] -pub struct Behaviour -where - LR: LatestRate + Send + 'static, -{ - behaviour: spot_price::Behaviour, - - #[behaviour(ignore)] - events: VecDeque, - - #[behaviour(ignore)] - balance: monero::Amount, - #[behaviour(ignore)] - lock_fee: monero::Amount, - #[behaviour(ignore)] - min_buy: bitcoin::Amount, - #[behaviour(ignore)] - max_buy: bitcoin::Amount, - #[behaviour(ignore)] - env_config: env::Config, - #[behaviour(ignore)] - latest_rate: LR, - #[behaviour(ignore)] - resume_only: bool, -} - -/// Behaviour that handles spot prices. -/// All the logic how to react to a spot price request is contained here, events -/// reporting the successful handling of a spot price request or a failure are -/// bubbled up to the parent behaviour. -impl Behaviour -where - LR: LatestRate + Send + 'static, -{ - pub fn new( - balance: monero::Amount, - lock_fee: monero::Amount, - min_buy: bitcoin::Amount, - max_buy: bitcoin::Amount, - env_config: env::Config, - latest_rate: LR, - resume_only: bool, - ) -> Self { - Self { - behaviour: spot_price::Behaviour::new( - CborCodec::default(), - vec![(SpotPriceProtocol, ProtocolSupport::Inbound)], - RequestResponseConfig::default(), - ), - events: Default::default(), - balance, - lock_fee, - min_buy, - max_buy, - env_config, - latest_rate, - resume_only, - } - } - - pub fn update_balance(&mut self, balance: monero::Amount) { - self.balance = balance; - } - - fn decline( - &mut self, - peer: PeerId, - channel: ResponseChannel, - error: Error, - ) { - if self - .behaviour - .send_response( - channel, - spot_price::Response::Error(error.to_error_response()), - ) - .is_err() - { - tracing::debug!(%peer, "Unable to send error response for spot price request"); - } - - self.events.push_back(OutEvent::Error { peer, error }); - } - - fn poll( - &mut self, - _cx: &mut Context<'_>, - _params: &mut impl PollParameters, - ) -> Poll> { - if let Some(event) = self.events.pop_front() { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); - } - - // We trust in libp2p to poll us. - Poll::Pending - } -} - -impl NetworkBehaviourEventProcess for Behaviour -where - LR: LatestRate + Send + 'static, -{ - fn inject_event(&mut self, event: spot_price::OutEvent) { - let (peer, message) = match event { - RequestResponseEvent::Message { peer, message } => (peer, message), - RequestResponseEvent::OutboundFailure { peer, error, .. } => { - tracing::error!(%peer, "Failure sending spot price response: {:#}", error); - return; - } - RequestResponseEvent::InboundFailure { peer, error, .. } => { - tracing::warn!(%peer, "Inbound failure when handling spot price request: {:#}", error); - return; - } - RequestResponseEvent::ResponseSent { peer, .. } => { - tracing::debug!(%peer, "Spot price response sent"); - return; - } - }; - - let (request, channel) = match message { - RequestResponseMessage::Request { - request, channel, .. - } => (request, channel), - RequestResponseMessage::Response { .. } => { - tracing::error!("Unexpected message"); - return; - } - }; - - let blockchain_network = BlockchainNetwork { - bitcoin: self.env_config.bitcoin_network, - monero: self.env_config.monero_network, - }; - - if request.blockchain_network != blockchain_network { - self.decline(peer, channel, Error::BlockchainNetworkMismatch { - cli: request.blockchain_network, - asb: blockchain_network, - }); - return; - } - - if self.resume_only { - self.decline(peer, channel, Error::ResumeOnlyMode); - return; - } - - let btc = request.btc; - - if btc < self.min_buy { - self.decline(peer, channel, Error::AmountBelowMinimum { - min: self.min_buy, - buy: btc, - }); - return; - } - - if btc > self.max_buy { - self.decline(peer, channel, Error::AmountAboveMaximum { - max: self.max_buy, - buy: btc, - }); - return; - } - - let rate = match self.latest_rate.latest_rate() { - Ok(rate) => rate, - Err(e) => { - self.decline(peer, channel, Error::LatestRateFetchFailed(Box::new(e))); - return; - } - }; - let xmr = match rate.sell_quote(btc) { - Ok(xmr) => xmr, - Err(e) => { - self.decline(peer, channel, Error::SellQuoteCalculationFailed(e)); - return; - } - }; - - let xmr_balance = self.balance; - let xmr_lock_fees = self.lock_fee; - - if xmr_balance < xmr + xmr_lock_fees { - self.decline(peer, channel, Error::BalanceTooLow { - balance: xmr_balance, - buy: btc, - }); - return; - } - - if self - .behaviour - .send_response(channel, spot_price::Response::Xmr(xmr)) - .is_err() - { - tracing::error!(%peer, "Failed to send spot price response of {} for {}", xmr, btc) - } - - self.events - .push_back(OutEvent::ExecutionSetupParams { peer, btc, xmr }); - } -} - -impl From for alice::OutEvent { - fn from(event: OutEvent) -> Self { - match event { - OutEvent::ExecutionSetupParams { peer, btc, xmr } => { - Self::ExecutionSetupStart { peer, btc, xmr } - } - OutEvent::Error { peer, error } => Self::SwapRequestDeclined { peer, error }, - } - } -} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("ASB is running in resume-only mode")] - ResumeOnlyMode, - #[error("Amount {buy} below minimum {min}")] - AmountBelowMinimum { - min: bitcoin::Amount, - buy: bitcoin::Amount, - }, - #[error("Amount {buy} above maximum {max}")] - AmountAboveMaximum { - max: bitcoin::Amount, - buy: bitcoin::Amount, - }, - #[error("Balance {balance} too low to fulfill swapping {buy}")] - BalanceTooLow { - balance: monero::Amount, - buy: bitcoin::Amount, - }, - #[error("Failed to fetch latest rate")] - LatestRateFetchFailed(#[source] Box), - #[error("Failed to calculate quote: {0}")] - SellQuoteCalculationFailed(#[source] anyhow::Error), - #[error("Blockchain networks did not match, we are on {asb:?}, but request from {cli:?}")] - BlockchainNetworkMismatch { - cli: spot_price::BlockchainNetwork, - asb: spot_price::BlockchainNetwork, - }, -} - -impl Error { - pub fn to_error_response(&self) -> spot_price::Error { - match self { - Error::ResumeOnlyMode => spot_price::Error::NoSwapsAccepted, - Error::AmountBelowMinimum { min, buy } => spot_price::Error::AmountBelowMinimum { - min: *min, - buy: *buy, - }, - Error::AmountAboveMaximum { max, buy } => spot_price::Error::AmountAboveMaximum { - max: *max, - buy: *buy, - }, - Error::BalanceTooLow { buy, .. } => spot_price::Error::BalanceTooLow { buy: *buy }, - Error::BlockchainNetworkMismatch { cli, asb } => { - spot_price::Error::BlockchainNetworkMismatch { - cli: *cli, - asb: *asb, - } - } - Error::LatestRateFetchFailed(_) | Error::SellQuoteCalculationFailed(_) => { - spot_price::Error::Other - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::asb::Rate; - use crate::env::GetConfig; - use crate::monero; - use crate::network::test::{await_events_or_timeout, connect, new_swarm}; - use crate::protocol::{alice, bob}; - use anyhow::anyhow; - use libp2p::Swarm; - use rust_decimal::Decimal; - - impl Default for AliceBehaviourValues { - fn default() -> Self { - Self { - balance: monero::Amount::from_monero(1.0).unwrap(), - lock_fee: monero::Amount::ZERO, - min_buy: bitcoin::Amount::from_btc(0.001).unwrap(), - max_buy: bitcoin::Amount::from_btc(0.01).unwrap(), - rate: TestRate::default(), // 0.01 - resume_only: false, - env_config: env::Testnet::get_config(), - } - } - } - - #[tokio::test] - async fn given_alice_has_sufficient_balance_then_returns_price() { - let mut test = SpotPriceTest::setup(AliceBehaviourValues::default()).await; - - let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); - let expected_xmr = monero::Amount::from_monero(1.0).unwrap(); - - test.construct_and_send_request(btc_to_swap); - test.assert_price((btc_to_swap, expected_xmr), expected_xmr) - .await; - } - - #[tokio::test] - async fn given_alice_has_insufficient_balance_then_returns_error() { - let mut test = SpotPriceTest::setup( - AliceBehaviourValues::default().with_balance(monero::Amount::ZERO), - ) - .await; - - let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); - - test.construct_and_send_request(btc_to_swap); - test.assert_error( - alice::spot_price::Error::BalanceTooLow { - balance: monero::Amount::ZERO, - buy: btc_to_swap, - }, - bob::spot_price::Error::BalanceTooLow { buy: btc_to_swap }, - ) - .await; - } - - #[tokio::test] - async fn given_alice_has_insufficient_balance_after_balance_update_then_returns_error() { - let mut test = SpotPriceTest::setup(AliceBehaviourValues::default()).await; - - let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); - let expected_xmr = monero::Amount::from_monero(1.0).unwrap(); - - test.construct_and_send_request(btc_to_swap); - test.assert_price((btc_to_swap, expected_xmr), expected_xmr) - .await; - - test.alice_swarm - .behaviour_mut() - .update_balance(monero::Amount::ZERO); - - test.construct_and_send_request(btc_to_swap); - test.assert_error( - alice::spot_price::Error::BalanceTooLow { - balance: monero::Amount::ZERO, - buy: btc_to_swap, - }, - bob::spot_price::Error::BalanceTooLow { buy: btc_to_swap }, - ) - .await; - } - - #[tokio::test] - async fn given_alice_has_insufficient_balance_because_of_lock_fee_then_returns_error() { - let balance = monero::Amount::from_monero(1.0).unwrap(); - - let mut test = SpotPriceTest::setup( - AliceBehaviourValues::default() - .with_balance(balance) - .with_lock_fee(monero::Amount::from_piconero(1)), - ) - .await; - - let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); - test.construct_and_send_request(btc_to_swap); - test.assert_error( - alice::spot_price::Error::BalanceTooLow { - balance, - buy: btc_to_swap, - }, - bob::spot_price::Error::BalanceTooLow { buy: btc_to_swap }, - ) - .await; - } - - #[tokio::test] - async fn given_below_min_buy_then_returns_error() { - let min_buy = bitcoin::Amount::from_btc(0.001).unwrap(); - - let mut test = - SpotPriceTest::setup(AliceBehaviourValues::default().with_min_buy(min_buy)).await; - - let btc_to_swap = bitcoin::Amount::from_btc(0.0001).unwrap(); - test.construct_and_send_request(btc_to_swap); - test.assert_error( - alice::spot_price::Error::AmountBelowMinimum { - buy: btc_to_swap, - min: min_buy, - }, - bob::spot_price::Error::AmountBelowMinimum { - buy: btc_to_swap, - min: min_buy, - }, - ) - .await; - } - - #[tokio::test] - async fn given_above_max_buy_then_returns_error() { - let max_buy = bitcoin::Amount::from_btc(0.001).unwrap(); - - let mut test = - SpotPriceTest::setup(AliceBehaviourValues::default().with_max_buy(max_buy)).await; - - let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); - - test.construct_and_send_request(btc_to_swap); - test.assert_error( - alice::spot_price::Error::AmountAboveMaximum { - buy: btc_to_swap, - max: max_buy, - }, - bob::spot_price::Error::AmountAboveMaximum { - buy: btc_to_swap, - max: max_buy, - }, - ) - .await; - } - - #[tokio::test] - async fn given_alice_in_resume_only_mode_then_returns_error() { - let mut test = - SpotPriceTest::setup(AliceBehaviourValues::default().with_resume_only(true)).await; - - let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); - test.construct_and_send_request(btc_to_swap); - test.assert_error( - alice::spot_price::Error::ResumeOnlyMode, - bob::spot_price::Error::NoSwapsAccepted, - ) - .await; - } - - #[tokio::test] - async fn given_rate_fetch_problem_then_returns_error() { - let mut test = - SpotPriceTest::setup(AliceBehaviourValues::default().with_rate(TestRate::error_rate())) - .await; - - let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); - test.construct_and_send_request(btc_to_swap); - test.assert_error( - alice::spot_price::Error::LatestRateFetchFailed(Box::new(TestRateError {})), - bob::spot_price::Error::Other, - ) - .await; - } - - #[tokio::test] - async fn given_rate_calculation_problem_then_returns_error() { - let mut test = SpotPriceTest::setup( - AliceBehaviourValues::default().with_rate(TestRate::from_rate_and_spread(0.0, 0)), - ) - .await; - - let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); - - test.construct_and_send_request(btc_to_swap); - test.assert_error( - alice::spot_price::Error::SellQuoteCalculationFailed(anyhow!( - "Error text irrelevant, won't be checked here" - )), - bob::spot_price::Error::Other, - ) - .await; - } - - #[tokio::test] - async fn given_alice_mainnnet_bob_testnet_then_network_mismatch_error() { - let mut test = SpotPriceTest::setup( - AliceBehaviourValues::default().with_env_config(env::Mainnet::get_config()), - ) - .await; - - let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); - test.construct_and_send_request(btc_to_swap); - test.assert_error( - alice::spot_price::Error::BlockchainNetworkMismatch { - cli: BlockchainNetwork { - bitcoin: bitcoin::Network::Testnet, - monero: monero::Network::Stagenet, - }, - asb: BlockchainNetwork { - bitcoin: bitcoin::Network::Bitcoin, - monero: monero::Network::Mainnet, - }, - }, - bob::spot_price::Error::BlockchainNetworkMismatch { - cli: BlockchainNetwork { - bitcoin: bitcoin::Network::Testnet, - monero: monero::Network::Stagenet, - }, - asb: BlockchainNetwork { - bitcoin: bitcoin::Network::Bitcoin, - monero: monero::Network::Mainnet, - }, - }, - ) - .await; - } - - #[tokio::test] - async fn given_alice_testnet_bob_mainnet_then_network_mismatch_error() { - let mut test = SpotPriceTest::setup(AliceBehaviourValues::default()).await; - - let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); - let request = spot_price::Request { - btc: btc_to_swap, - blockchain_network: BlockchainNetwork { - bitcoin: bitcoin::Network::Bitcoin, - monero: monero::Network::Mainnet, - }, - }; - - test.send_request(request); - test.assert_error( - alice::spot_price::Error::BlockchainNetworkMismatch { - cli: BlockchainNetwork { - bitcoin: bitcoin::Network::Bitcoin, - monero: monero::Network::Mainnet, - }, - asb: BlockchainNetwork { - bitcoin: bitcoin::Network::Testnet, - monero: monero::Network::Stagenet, - }, - }, - bob::spot_price::Error::BlockchainNetworkMismatch { - cli: BlockchainNetwork { - bitcoin: bitcoin::Network::Bitcoin, - monero: monero::Network::Mainnet, - }, - asb: BlockchainNetwork { - bitcoin: bitcoin::Network::Testnet, - monero: monero::Network::Stagenet, - }, - }, - ) - .await; - } - - struct SpotPriceTest { - alice_swarm: Swarm>, - bob_swarm: Swarm, - - alice_peer_id: PeerId, - } - - impl SpotPriceTest { - pub async fn setup(values: AliceBehaviourValues) -> Self { - let (mut alice_swarm, _, alice_peer_id) = new_swarm(|_, _| { - Behaviour::new( - values.balance, - values.lock_fee, - values.min_buy, - values.max_buy, - values.env_config, - values.rate.clone(), - values.resume_only, - ) - }); - let (mut bob_swarm, ..) = new_swarm(|_, _| bob::spot_price::bob()); - - connect(&mut alice_swarm, &mut bob_swarm).await; - - Self { - alice_swarm, - bob_swarm, - alice_peer_id, - } - } - - pub fn construct_and_send_request(&mut self, btc_to_swap: bitcoin::Amount) { - let request = spot_price::Request { - btc: btc_to_swap, - blockchain_network: BlockchainNetwork { - bitcoin: bitcoin::Network::Testnet, - monero: monero::Network::Stagenet, - }, - }; - self.send_request(request); - } - - pub fn send_request(&mut self, spot_price_request: spot_price::Request) { - self.bob_swarm - .behaviour_mut() - .send_request(&self.alice_peer_id, spot_price_request); - } - - async fn assert_price( - &mut self, - alice_assert: (bitcoin::Amount, monero::Amount), - bob_assert: monero::Amount, - ) { - match await_events_or_timeout(self.alice_swarm.next(), self.bob_swarm.next()).await { - ( - alice::spot_price::OutEvent::ExecutionSetupParams { btc, xmr, .. }, - spot_price::OutEvent::Message { message, .. }, - ) => { - assert_eq!(alice_assert, (btc, xmr)); - - let response = match message { - RequestResponseMessage::Response { response, .. } => response, - _ => panic!("Unexpected message {:?} for Bob", message), - }; - - match response { - spot_price::Response::Xmr(xmr) => { - assert_eq!(bob_assert, xmr) - } - _ => panic!("Unexpected response {:?} for Bob", response), - } - } - (alice_event, bob_event) => panic!( - "Received unexpected event, alice emitted {:?} and bob emitted {:?}", - alice_event, bob_event - ), - } - } - - async fn assert_error( - &mut self, - alice_assert: alice::spot_price::Error, - bob_assert: bob::spot_price::Error, - ) { - match await_events_or_timeout(self.alice_swarm.next(), self.bob_swarm.next()).await { - ( - alice::spot_price::OutEvent::Error { error, .. }, - spot_price::OutEvent::Message { message, .. }, - ) => { - // TODO: Somehow make PartialEq work on Alice's spot_price::Error - match (alice_assert, error) { - ( - alice::spot_price::Error::BalanceTooLow { - balance: balance1, - buy: buy1, - }, - alice::spot_price::Error::BalanceTooLow { - balance: balance2, - buy: buy2, - }, - ) => { - assert_eq!(balance1, balance2); - assert_eq!(buy1, buy2); - } - ( - alice::spot_price::Error::BlockchainNetworkMismatch { - cli: cli1, - asb: asb1, - }, - alice::spot_price::Error::BlockchainNetworkMismatch { - cli: cli2, - asb: asb2, - }, - ) => { - assert_eq!(cli1, cli2); - assert_eq!(asb1, asb2); - } - ( - alice::spot_price::Error::AmountBelowMinimum { .. }, - alice::spot_price::Error::AmountBelowMinimum { .. }, - ) - | ( - alice::spot_price::Error::AmountAboveMaximum { .. }, - alice::spot_price::Error::AmountAboveMaximum { .. }, - ) - | ( - alice::spot_price::Error::LatestRateFetchFailed(_), - alice::spot_price::Error::LatestRateFetchFailed(_), - ) - | ( - alice::spot_price::Error::SellQuoteCalculationFailed(_), - alice::spot_price::Error::SellQuoteCalculationFailed(_), - ) - | ( - alice::spot_price::Error::ResumeOnlyMode, - alice::spot_price::Error::ResumeOnlyMode, - ) => {} - (alice_assert, error) => { - panic!("Expected: {:?} Actual: {:?}", alice_assert, error) - } - } - - let response = match message { - RequestResponseMessage::Response { response, .. } => response, - _ => panic!("Unexpected message {:?} for Bob", message), - }; - - match response { - spot_price::Response::Error(error) => { - assert_eq!(bob_assert, error.into()) - } - _ => panic!("Unexpected response {:?} for Bob", response), - } - } - (alice_event, bob_event) => panic!( - "Received unexpected event, alice emitted {:?} and bob emitted {:?}", - alice_event, bob_event - ), - } - } - } - - struct AliceBehaviourValues { - pub balance: monero::Amount, - pub lock_fee: monero::Amount, - pub min_buy: bitcoin::Amount, - pub max_buy: bitcoin::Amount, - pub rate: TestRate, // 0.01 - pub resume_only: bool, - pub env_config: env::Config, - } - - impl AliceBehaviourValues { - pub fn with_balance(mut self, balance: monero::Amount) -> AliceBehaviourValues { - self.balance = balance; - self - } - - pub fn with_lock_fee(mut self, lock_fee: monero::Amount) -> AliceBehaviourValues { - self.lock_fee = lock_fee; - self - } - - pub fn with_min_buy(mut self, min_buy: bitcoin::Amount) -> AliceBehaviourValues { - self.min_buy = min_buy; - self - } - - pub fn with_max_buy(mut self, max_buy: bitcoin::Amount) -> AliceBehaviourValues { - self.max_buy = max_buy; - self - } - - pub fn with_resume_only(mut self, resume_only: bool) -> AliceBehaviourValues { - self.resume_only = resume_only; - self - } - - pub fn with_rate(mut self, rate: TestRate) -> AliceBehaviourValues { - self.rate = rate; - self - } - - pub fn with_env_config(mut self, env_config: env::Config) -> AliceBehaviourValues { - self.env_config = env_config; - self - } - } - - #[derive(Clone, Debug)] - pub enum TestRate { - Rate(Rate), - Err(TestRateError), - } - - impl TestRate { - pub const RATE: f64 = 0.01; - - pub fn from_rate_and_spread(rate: f64, spread: u64) -> Self { - let ask = bitcoin::Amount::from_btc(rate).expect("Static value should never fail"); - let spread = Decimal::from(spread); - Self::Rate(Rate::new(ask, spread)) - } - - pub fn error_rate() -> Self { - Self::Err(TestRateError {}) - } - } - - impl Default for TestRate { - fn default() -> Self { - TestRate::from_rate_and_spread(Self::RATE, 0) - } - } - - #[derive(Debug, Clone, thiserror::Error)] - #[error("Could not fetch rate")] - pub struct TestRateError {} - - impl LatestRate for TestRate { - type Error = TestRateError; - - fn latest_rate(&mut self) -> Result { - match self { - TestRate::Rate(rate) => Ok(*rate), - TestRate::Err(error) => Err(error.clone()), - } - } - } -} diff --git a/swap/src/protocol/alice/state.rs b/swap/src/protocol/alice/state.rs index a2b1a6a1..18c2a18d 100644 --- a/swap/src/protocol/alice/state.rs +++ b/swap/src/protocol/alice/state.rs @@ -136,7 +136,7 @@ impl State0 { tx_redeem_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount, rng: &mut R, - ) -> Result + ) -> Self where R: RngCore + CryptoRng, { @@ -146,7 +146,7 @@ impl State0 { let s_a = monero::Scalar::random(rng); let (dleq_proof_s_a, (S_a_bitcoin, S_a_monero)) = CROSS_CURVE_PROOF_SYSTEM.prove(&s_a, rng); - Ok(Self { + Self { a, s_a, v_a, @@ -163,7 +163,7 @@ impl State0 { punish_timelock: env_config.bitcoin_punish_timelock, tx_redeem_fee, tx_punish_fee, - }) + } } pub fn receive(self, msg: Message0) -> Result<(Uuid, State1)> { diff --git a/swap/src/protocol/alice/swap_setup.rs b/swap/src/protocol/alice/swap_setup.rs new file mode 100644 index 00000000..333a4769 --- /dev/null +++ b/swap/src/protocol/alice/swap_setup.rs @@ -0,0 +1,513 @@ +use crate::network::swap_setup; +use crate::network::swap_setup::{ + protocol, BlockchainNetwork, SpotPriceError, SpotPriceRequest, SpotPriceResponse, +}; +use crate::protocol::alice::event_loop::LatestRate; +use crate::protocol::alice::{State0, State3}; +use crate::protocol::{alice, Message0, Message2, Message4}; +use crate::{bitcoin, env, monero}; +use anyhow::{anyhow, Context, Result}; +use futures::future::{BoxFuture, OptionFuture}; +use futures::FutureExt; +use libp2p::core::connection::ConnectionId; +use libp2p::core::upgrade; +use libp2p::swarm::{ + KeepAlive, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, PollParameters, + ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol, +}; +use libp2p::{Multiaddr, PeerId}; +use std::collections::VecDeque; +use std::fmt::Debug; +use std::task::Poll; +use std::time::{Duration, Instant}; +use uuid::Uuid; +use void::Void; + +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +pub enum OutEvent { + Initiated { + send_wallet_snapshot: bmrng::RequestReceiver, + }, + Completed { + peer_id: PeerId, + swap_id: Uuid, + state3: State3, + }, + Error { + peer_id: PeerId, + error: anyhow::Error, + }, +} + +#[derive(Debug)] +pub struct WalletSnapshot { + balance: monero::Amount, + lock_fee: monero::Amount, + + // TODO: Consider using the same address for punish and redeem (they are mutually exclusive, so + // effectively the address will only be used once) + redeem_address: bitcoin::Address, + punish_address: bitcoin::Address, + + redeem_fee: bitcoin::Amount, + punish_fee: bitcoin::Amount, +} + +impl WalletSnapshot { + pub async fn capture( + bitcoin_wallet: &bitcoin::Wallet, + monero_wallet: &monero::Wallet, + transfer_amount: bitcoin::Amount, + ) -> Result { + let balance = monero_wallet.get_balance().await?; + let redeem_address = bitcoin_wallet.new_address().await?; + let punish_address = bitcoin_wallet.new_address().await?; + let redeem_fee = bitcoin_wallet + .estimate_fee(bitcoin::TxRedeem::weight(), transfer_amount) + .await?; + let punish_fee = bitcoin_wallet + .estimate_fee(bitcoin::TxPunish::weight(), transfer_amount) + .await?; + + Ok(Self { + balance, + lock_fee: monero::MONERO_FEE, + redeem_address, + punish_address, + redeem_fee, + punish_fee, + }) + } +} + +impl From for alice::OutEvent { + fn from(event: OutEvent) -> Self { + match event { + OutEvent::Initiated { + send_wallet_snapshot, + } => alice::OutEvent::SwapSetupInitiated { + send_wallet_snapshot, + }, + OutEvent::Completed { + peer_id: bob_peer_id, + swap_id, + state3, + } => alice::OutEvent::SwapSetupCompleted { + peer_id: bob_peer_id, + swap_id, + state3: Box::new(state3), + }, + OutEvent::Error { peer_id, error } => alice::OutEvent::Failure { + peer: peer_id, + error: anyhow!(error), + }, + } + } +} + +#[allow(missing_debug_implementations)] +pub struct Behaviour { + events: VecDeque, + min_buy: bitcoin::Amount, + max_buy: bitcoin::Amount, + env_config: env::Config, + + latest_rate: LR, + resume_only: bool, +} + +impl Behaviour { + pub fn new( + min_buy: bitcoin::Amount, + max_buy: bitcoin::Amount, + env_config: env::Config, + latest_rate: LR, + resume_only: bool, + ) -> Self { + Self { + events: Default::default(), + min_buy, + max_buy, + env_config, + latest_rate, + resume_only, + } + } +} + +impl NetworkBehaviour for Behaviour +where + LR: LatestRate + Send + 'static + Clone, +{ + type ProtocolsHandler = Handler; + type OutEvent = OutEvent; + + fn new_handler(&mut self) -> Self::ProtocolsHandler { + Handler::new( + self.min_buy, + self.max_buy, + self.env_config, + self.latest_rate.clone(), + self.resume_only, + ) + } + + fn addresses_of_peer(&mut self, _: &PeerId) -> Vec { + Vec::new() + } + + fn inject_connected(&mut self, _: &PeerId) {} + + fn inject_disconnected(&mut self, _: &PeerId) {} + + fn inject_event(&mut self, peer_id: PeerId, _: ConnectionId, event: HandlerOutEvent) { + match event { + HandlerOutEvent::Initiated(send_wallet_snapshot) => { + self.events.push_back(OutEvent::Initiated { + send_wallet_snapshot, + }) + } + HandlerOutEvent::Completed(Ok((swap_id, state3))) => { + self.events.push_back(OutEvent::Completed { + peer_id, + swap_id, + state3, + }) + } + HandlerOutEvent::Completed(Err(error)) => { + self.events.push_back(OutEvent::Error { peer_id, error }) + } + } + } + + fn poll( + &mut self, + _cx: &mut std::task::Context<'_>, + _params: &mut impl PollParameters, + ) -> Poll> { + if let Some(event) = self.events.pop_front() { + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); + } + + Poll::Pending + } +} + +type InboundStream = BoxFuture<'static, Result<(Uuid, alice::State3)>>; + +pub struct Handler { + inbound_stream: OptionFuture, + events: VecDeque, + + min_buy: bitcoin::Amount, + max_buy: bitcoin::Amount, + env_config: env::Config, + + latest_rate: LR, + resume_only: bool, + + timeout: Duration, + keep_alive: KeepAlive, +} + +impl Handler { + fn new( + min_buy: bitcoin::Amount, + max_buy: bitcoin::Amount, + env_config: env::Config, + latest_rate: LR, + resume_only: bool, + ) -> Self { + Self { + inbound_stream: OptionFuture::from(None), + events: Default::default(), + min_buy, + max_buy, + env_config, + latest_rate, + resume_only, + timeout: Duration::from_secs(60), + keep_alive: KeepAlive::Until(Instant::now() + Duration::from_secs(5)), + } + } +} + +#[allow(clippy::large_enum_variant)] +pub enum HandlerOutEvent { + Initiated(bmrng::RequestReceiver), + Completed(Result<(Uuid, alice::State3)>), +} + +impl ProtocolsHandler for Handler +where + LR: LatestRate + Send + 'static, +{ + type InEvent = (); + type OutEvent = HandlerOutEvent; + type Error = Error; + type InboundProtocol = protocol::SwapSetup; + type OutboundProtocol = upgrade::DeniedUpgrade; + type InboundOpenInfo = (); + type OutboundOpenInfo = (); + + fn listen_protocol(&self) -> SubstreamProtocol { + SubstreamProtocol::new(protocol::new(), ()) + } + + fn inject_fully_negotiated_inbound( + &mut self, + mut substream: NegotiatedSubstream, + _: Self::InboundOpenInfo, + ) { + self.keep_alive = KeepAlive::Yes; + + let (sender, receiver) = bmrng::channel_with_timeout::( + 1, + Duration::from_secs(5), + ); + let resume_only = self.resume_only; + let min_buy = self.min_buy; + let max_buy = self.max_buy; + let latest_rate = self.latest_rate.latest_rate(); + let env_config = self.env_config; + + let protocol = tokio::time::timeout(self.timeout, async move { + let request = swap_setup::read_cbor_message::(&mut substream) + .await + .context("Failed to read spot price request")?; + + let wallet_snapshot = sender + .send_receive(request.btc) + .await + .context("Failed to receive wallet snapshot")?; + + // wrap all of these into another future so we can `return` from all the + // different blocks + let validate = async { + if resume_only { + return Err(Error::ResumeOnlyMode); + }; + + let blockchain_network = BlockchainNetwork { + bitcoin: env_config.bitcoin_network, + monero: env_config.monero_network, + }; + + if request.blockchain_network != blockchain_network { + return Err(Error::BlockchainNetworkMismatch { + cli: request.blockchain_network, + asb: blockchain_network, + }); + } + + let btc = request.btc; + + if btc < min_buy { + return Err(Error::AmountBelowMinimum { + min: min_buy, + buy: btc, + }); + } + + if btc > max_buy { + return Err(Error::AmountAboveMaximum { + max: max_buy, + buy: btc, + }); + } + + let rate = latest_rate.map_err(|e| Error::LatestRateFetchFailed(Box::new(e)))?; + let xmr = rate + .sell_quote(btc) + .map_err(Error::SellQuoteCalculationFailed)?; + + if wallet_snapshot.balance < xmr + wallet_snapshot.lock_fee { + return Err(Error::BalanceTooLow { + balance: wallet_snapshot.balance, + buy: btc, + }); + } + + Ok(xmr) + }; + + let result = validate.await; + + swap_setup::write_cbor_message( + &mut substream, + SpotPriceResponse::from_result_ref(&result), + ) + .await + .context("Failed to write spot price response")?; + + let xmr = result?; + + let state0 = State0::new( + request.btc, + xmr, + env_config, + wallet_snapshot.redeem_address, + wallet_snapshot.punish_address, + wallet_snapshot.redeem_fee, + wallet_snapshot.punish_fee, + &mut rand::thread_rng(), + ); + + let message0 = swap_setup::read_cbor_message::(&mut substream) + .await + .context("Failed to read message0")?; + let (swap_id, state1) = state0 + .receive(message0) + .context("Failed to transition state0 -> state1 using message0")?; + + swap_setup::write_cbor_message(&mut substream, state1.next_message()) + .await + .context("Failed to send message1")?; + + let message2 = swap_setup::read_cbor_message::(&mut substream) + .await + .context("Failed to read message2")?; + let state2 = state1 + .receive(message2) + .context("Failed to transition state1 -> state2 using message2")?; + + swap_setup::write_cbor_message(&mut substream, state2.next_message()) + .await + .context("Failed to send message3")?; + + let message4 = swap_setup::read_cbor_message::(&mut substream) + .await + .context("Failed to read message4")?; + let state3 = state2 + .receive(message4) + .context("Failed to transition state2 -> state3 using message4")?; + + Ok((swap_id, state3)) + }); + + let max_seconds = self.timeout.as_secs(); + self.inbound_stream = OptionFuture::from(Some( + async move { + protocol.await.with_context(|| { + format!("Failed to complete execution setup within {}s", max_seconds) + })? + } + .boxed(), + )); + + self.events.push_back(HandlerOutEvent::Initiated(receiver)); + } + + fn inject_fully_negotiated_outbound(&mut self, _: Void, _: Self::OutboundOpenInfo) { + unreachable!("Alice does not support outbound in the handler") + } + + fn inject_event(&mut self, _: Self::InEvent) { + unreachable!("Alice does not receive events from the Behaviour in the handler") + } + + fn inject_dial_upgrade_error( + &mut self, + _: Self::OutboundOpenInfo, + _: ProtocolsHandlerUpgrErr, + ) { + unreachable!("Alice does not dial") + } + + fn connection_keep_alive(&self) -> KeepAlive { + self.keep_alive + } + + #[allow(clippy::type_complexity)] + fn poll( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> Poll< + ProtocolsHandlerEvent< + Self::OutboundProtocol, + Self::OutboundOpenInfo, + Self::OutEvent, + Self::Error, + >, + > { + if let Some(event) = self.events.pop_front() { + return Poll::Ready(ProtocolsHandlerEvent::Custom(event)); + } + + if let Some(result) = futures::ready!(self.inbound_stream.poll_unpin(cx)) { + self.keep_alive = KeepAlive::No; + self.inbound_stream = OptionFuture::from(None); + return Poll::Ready(ProtocolsHandlerEvent::Custom(HandlerOutEvent::Completed( + result, + ))); + } + + Poll::Pending + } +} + +impl SpotPriceResponse { + pub fn from_result_ref(result: &Result) -> Self { + match result { + Ok(amount) => SpotPriceResponse::Xmr(*amount), + Err(error) => SpotPriceResponse::Error(error.to_error_response()), + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("ASB is running in resume-only mode")] + ResumeOnlyMode, + #[error("Amount {buy} below minimum {min}")] + AmountBelowMinimum { + min: bitcoin::Amount, + buy: bitcoin::Amount, + }, + #[error("Amount {buy} above maximum {max}")] + AmountAboveMaximum { + max: bitcoin::Amount, + buy: bitcoin::Amount, + }, + #[error("Balance {balance} too low to fulfill swapping {buy}")] + BalanceTooLow { + balance: monero::Amount, + buy: bitcoin::Amount, + }, + #[error("Failed to fetch latest rate")] + LatestRateFetchFailed(#[source] Box), + #[error("Failed to calculate quote")] + SellQuoteCalculationFailed(#[source] anyhow::Error), + #[error("Blockchain networks did not match, we are on {asb:?}, but request from {cli:?}")] + BlockchainNetworkMismatch { + cli: BlockchainNetwork, + asb: BlockchainNetwork, + }, +} + +impl Error { + pub fn to_error_response(&self) -> SpotPriceError { + match self { + Error::ResumeOnlyMode => SpotPriceError::NoSwapsAccepted, + Error::AmountBelowMinimum { min, buy } => SpotPriceError::AmountBelowMinimum { + min: *min, + buy: *buy, + }, + Error::AmountAboveMaximum { max, buy } => SpotPriceError::AmountAboveMaximum { + max: *max, + buy: *buy, + }, + Error::BalanceTooLow { buy, .. } => SpotPriceError::BalanceTooLow { buy: *buy }, + Error::BlockchainNetworkMismatch { cli, asb } => { + SpotPriceError::BlockchainNetworkMismatch { + cli: *cli, + asb: *asb, + } + } + Error::LatestRateFetchFailed(_) | Error::SellQuoteCalculationFailed(_) => { + SpotPriceError::Other + } + } + } +} diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index ae263f97..4c93b73b 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -14,11 +14,10 @@ pub use self::swap::{run, run_until}; mod behaviour; pub mod cancel; pub mod event_loop; -mod execution_setup; pub mod refund; -pub mod spot_price; pub mod state; pub mod swap; +mod swap_setup; pub struct Swap { pub state: BobState, diff --git a/swap/src/protocol/bob/behaviour.rs b/swap/src/protocol/bob/behaviour.rs index 8f156e1b..951c84f6 100644 --- a/swap/src/protocol/bob/behaviour.rs +++ b/swap/src/protocol/bob/behaviour.rs @@ -1,12 +1,13 @@ use crate::network::quote::BidQuote; -use crate::network::{encrypted_signature, quote, redial, spot_price, transfer_proof}; -use crate::protocol::bob; -use crate::protocol::bob::{execution_setup, State2}; +use crate::network::{encrypted_signature, quote, redial, transfer_proof}; +use crate::protocol::bob::{swap_setup, State2}; +use crate::{bitcoin, env}; use anyhow::{anyhow, Error, Result}; use libp2p::core::Multiaddr; use libp2p::ping::{Ping, PingEvent}; use libp2p::request_response::{RequestId, ResponseChannel}; use libp2p::{NetworkBehaviour, PeerId}; +use std::sync::Arc; use std::time::Duration; #[derive(Debug)] @@ -15,11 +16,7 @@ pub enum OutEvent { id: RequestId, response: BidQuote, }, - SpotPriceReceived { - id: RequestId, - response: spot_price::Response, - }, - ExecutionSetupDone(Box>), + SwapSetupCompleted(Box>), TransferProofReceived { msg: Box, channel: ResponseChannel<()>, @@ -62,8 +59,7 @@ impl OutEvent { #[allow(missing_debug_implementations)] pub struct Behaviour { pub quote: quote::Behaviour, - pub spot_price: spot_price::Behaviour, - pub execution_setup: execution_setup::Behaviour, + pub swap_setup: swap_setup::Behaviour, pub transfer_proof: transfer_proof::Behaviour, pub encrypted_signature: encrypted_signature::Behaviour, pub redial: redial::Behaviour, @@ -75,11 +71,14 @@ pub struct Behaviour { } impl Behaviour { - pub fn new(alice: PeerId) -> Self { + pub fn new( + alice: PeerId, + env_config: env::Config, + bitcoin_wallet: Arc, + ) -> Self { Self { quote: quote::bob(), - spot_price: bob::spot_price::bob(), - execution_setup: Default::default(), + swap_setup: swap_setup::Behaviour::new(env_config, bitcoin_wallet), transfer_proof: transfer_proof::bob(), encrypted_signature: encrypted_signature::bob(), redial: redial::Behaviour::new(alice, Duration::from_secs(2)), @@ -90,7 +89,6 @@ impl Behaviour { /// Add a known address for the given peer pub fn add_address(&mut self, peer_id: PeerId, address: Multiaddr) { self.quote.add_address(&peer_id, address.clone()); - self.spot_price.add_address(&peer_id, address.clone()); self.transfer_proof.add_address(&peer_id, address.clone()); self.encrypted_signature.add_address(&peer_id, address); } diff --git a/swap/src/protocol/bob/cancel.rs b/swap/src/protocol/bob/cancel.rs index 9e667a5f..7723067c 100644 --- a/swap/src/protocol/bob/cancel.rs +++ b/swap/src/protocol/bob/cancel.rs @@ -26,7 +26,7 @@ pub async fn cancel( BobState::EncSigSent(state4) => state4.cancel(), BobState::CancelTimelockExpired(state6) => state6, BobState::Started { .. } - | BobState::ExecutionSetupDone(_) + | BobState::SwapSetupCompleted(_) | BobState::BtcRedeemed(_) | BobState::BtcCancelled(_) | BobState::BtcRefunded(_) diff --git a/swap/src/protocol/bob/event_loop.rs b/swap/src/protocol/bob/event_loop.rs index c8e39eee..742129a0 100644 --- a/swap/src/protocol/bob/event_loop.rs +++ b/swap/src/protocol/bob/event_loop.rs @@ -1,18 +1,16 @@ use crate::bitcoin::EncryptedSignature; +use crate::network::encrypted_signature; use crate::network::quote::BidQuote; -use crate::network::spot_price::{BlockchainNetwork, Response}; -use crate::network::{encrypted_signature, spot_price}; -use crate::protocol::bob; -use crate::protocol::bob::{Behaviour, OutEvent, State0, State2}; -use crate::{bitcoin, env, monero}; -use anyhow::{bail, Context, Result}; +use crate::protocol::bob::swap_setup::NewSwap; +use crate::protocol::bob::{Behaviour, OutEvent, State2}; +use crate::{env, monero}; +use anyhow::{Context, Result}; use futures::future::{BoxFuture, OptionFuture}; use futures::{FutureExt, StreamExt}; use libp2p::request_response::{RequestId, ResponseChannel}; use libp2p::swarm::SwarmEvent; use libp2p::{PeerId, Swarm}; use std::collections::HashMap; -use std::sync::Arc; use std::time::Duration; use uuid::Uuid; @@ -20,22 +18,19 @@ use uuid::Uuid; pub struct EventLoop { swap_id: Uuid, swarm: libp2p::Swarm, - bitcoin_wallet: Arc, alice_peer_id: PeerId, // these streams represents outgoing requests that we have to make quote_requests: bmrng::RequestReceiverStream<(), BidQuote>, - spot_price_requests: bmrng::RequestReceiverStream, encrypted_signatures: bmrng::RequestReceiverStream, - execution_setup_requests: bmrng::RequestReceiverStream>, + swap_setup_requests: bmrng::RequestReceiverStream>, // these represents requests that are currently in-flight. // once we get a response to a matching [`RequestId`], we will use the responder to relay the // response. - inflight_spot_price_requests: HashMap>, inflight_quote_requests: HashMap>, inflight_encrypted_signature_requests: HashMap>, - inflight_execution_setup: Option>>, + inflight_swap_setup: Option>>, /// The sender we will use to relay incoming transfer proofs. transfer_proof: bmrng::RequestSender, @@ -54,37 +49,31 @@ impl EventLoop { swap_id: Uuid, swarm: Swarm, alice_peer_id: PeerId, - bitcoin_wallet: Arc, env_config: env::Config, ) -> Result<(Self, EventLoopHandle)> { let execution_setup = bmrng::channel_with_timeout(1, Duration::from_secs(60)); let transfer_proof = bmrng::channel_with_timeout(1, Duration::from_secs(60)); let encrypted_signature = bmrng::channel_with_timeout(1, Duration::from_secs(60)); - let spot_price = bmrng::channel_with_timeout(1, Duration::from_secs(60)); let quote = bmrng::channel_with_timeout(1, Duration::from_secs(60)); let event_loop = EventLoop { swap_id, swarm, alice_peer_id, - bitcoin_wallet, - execution_setup_requests: execution_setup.1.into(), + swap_setup_requests: execution_setup.1.into(), transfer_proof: transfer_proof.0, encrypted_signatures: encrypted_signature.1.into(), - spot_price_requests: spot_price.1.into(), quote_requests: quote.1.into(), - inflight_spot_price_requests: HashMap::default(), inflight_quote_requests: HashMap::default(), - inflight_execution_setup: None, + inflight_swap_setup: None, inflight_encrypted_signature_requests: HashMap::default(), pending_transfer_proof: OptionFuture::from(None), }; let handle = EventLoopHandle { - execution_setup: execution_setup.0, + swap_setup: execution_setup.0, transfer_proof: transfer_proof.1, encrypted_signature: encrypted_signature.0, - spot_price: spot_price.0, quote: quote.0, env_config, }; @@ -106,18 +95,13 @@ impl EventLoop { tokio::select! { swarm_event = self.swarm.next_event().fuse() => { match swarm_event { - SwarmEvent::Behaviour(OutEvent::SpotPriceReceived { id, response }) => { - if let Some(responder) = self.inflight_spot_price_requests.remove(&id) { - let _ = responder.respond(response); - } - } SwarmEvent::Behaviour(OutEvent::QuoteReceived { id, response }) => { if let Some(responder) = self.inflight_quote_requests.remove(&id) { let _ = responder.respond(response); } } - SwarmEvent::Behaviour(OutEvent::ExecutionSetupDone(response)) => { - if let Some(responder) = self.inflight_execution_setup.take() { + SwarmEvent::Behaviour(OutEvent::SwapSetupCompleted(response)) => { + if let Some(responder) = self.inflight_swap_setup.take() { let _ = responder.respond(*response); } } @@ -197,17 +181,13 @@ impl EventLoop { // Handle to-be-sent requests for all our network protocols. // Use `self.is_connected_to_alice` as a guard to "buffer" requests until we are connected. - Some((request, responder)) = self.spot_price_requests.next().fuse(), if self.is_connected_to_alice() => { - let id = self.swarm.behaviour_mut().spot_price.send_request(&self.alice_peer_id, request); - self.inflight_spot_price_requests.insert(id, responder); - }, Some(((), responder)) = self.quote_requests.next().fuse(), if self.is_connected_to_alice() => { let id = self.swarm.behaviour_mut().quote.send_request(&self.alice_peer_id, ()); self.inflight_quote_requests.insert(id, responder); }, - Some((request, responder)) = self.execution_setup_requests.next().fuse(), if self.is_connected_to_alice() => { - self.swarm.behaviour_mut().execution_setup.run(self.alice_peer_id, request, self.bitcoin_wallet.clone()); - self.inflight_execution_setup = Some(responder); + Some((swap, responder)) = self.swap_setup_requests.next().fuse(), if self.is_connected_to_alice() => { + self.swarm.behaviour_mut().swap_setup.start(self.alice_peer_id, swap).await; + self.inflight_swap_setup = Some(responder); }, Some((tx_redeem_encsig, responder)) = self.encrypted_signatures.next().fuse(), if self.is_connected_to_alice() => { let request = encrypted_signature::Request { @@ -235,17 +215,16 @@ impl EventLoop { #[derive(Debug)] pub struct EventLoopHandle { - execution_setup: bmrng::RequestSender>, + swap_setup: bmrng::RequestSender>, transfer_proof: bmrng::RequestReceiver, encrypted_signature: bmrng::RequestSender, - spot_price: bmrng::RequestSender, quote: bmrng::RequestSender<(), BidQuote>, env_config: env::Config, } impl EventLoopHandle { - pub async fn execution_setup(&mut self, state0: State0) -> Result { - self.execution_setup.send_receive(state0).await? + pub async fn setup_swap(&mut self, swap: NewSwap) -> Result { + self.swap_setup.send_receive(swap).await? } pub async fn recv_transfer_proof(&mut self) -> Result { @@ -261,27 +240,6 @@ impl EventLoopHandle { Ok(transfer_proof) } - pub async fn request_spot_price(&mut self, btc: bitcoin::Amount) -> Result { - let response = self - .spot_price - .send_receive(spot_price::Request { - btc, - blockchain_network: BlockchainNetwork { - bitcoin: self.env_config.bitcoin_network, - monero: self.env_config.monero_network, - }, - }) - .await?; - - match response { - Response::Xmr(xmr) => Ok(xmr), - Response::Error(error) => { - let error: bob::spot_price::Error = error.into(); - bail!(error); - } - } - } - pub async fn request_quote(&mut self) -> Result { Ok(self.quote.send_receive(()).await?) } diff --git a/swap/src/protocol/bob/execution_setup.rs b/swap/src/protocol/bob/execution_setup.rs deleted file mode 100644 index 947af3bd..00000000 --- a/swap/src/protocol/bob/execution_setup.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::network::cbor_request_response::BUF_SIZE; -use crate::protocol::bob::{State0, State2}; -use crate::protocol::{bob, Message1, Message3}; -use anyhow::{Context, Error, Result}; -use libp2p::PeerId; -use libp2p_async_await::BehaviourOutEvent; -use std::sync::Arc; -use std::time::Duration; - -#[derive(Debug)] -pub enum OutEvent { - Done(Result), -} - -impl From> for OutEvent { - fn from(event: BehaviourOutEvent<(), State2, Error>) -> Self { - match event { - BehaviourOutEvent::Outbound(_, Ok(State2)) => OutEvent::Done(Ok(State2)), - BehaviourOutEvent::Outbound(_, Err(e)) => OutEvent::Done(Err(e)), - BehaviourOutEvent::Inbound(..) => unreachable!("Bob only supports outbound"), - } - } -} - -#[derive(libp2p::NetworkBehaviour)] -#[behaviour(out_event = "OutEvent", event_process = false)] -pub struct Behaviour { - inner: libp2p_async_await::Behaviour<(), State2, anyhow::Error>, -} - -impl Default for Behaviour { - fn default() -> Self { - Self { - inner: libp2p_async_await::Behaviour::new(b"/comit/xmr/btc/execution_setup/1.0.0"), - } - } -} - -impl Behaviour { - pub fn run( - &mut self, - alice: PeerId, - state0: State0, - bitcoin_wallet: Arc, - ) { - self.inner.do_protocol_dialer(alice, move |mut substream| { - let protocol = async move { - tracing::debug!("Starting execution setup with {}", alice); - - substream - .write_message( - &serde_cbor::to_vec(&state0.next_message()) - .context("Failed to serialize message0")?, - ) - .await?; - - let message1 = - serde_cbor::from_slice::(&substream.read_message(BUF_SIZE).await?) - .context("Failed to deserialize message1")?; - let state1 = state0.receive(bitcoin_wallet.as_ref(), message1).await?; - - substream - .write_message( - &serde_cbor::to_vec(&state1.next_message()) - .context("Failed to serialize message2")?, - ) - .await?; - - let message3 = - serde_cbor::from_slice::(&substream.read_message(BUF_SIZE).await?) - .context("Failed to deserialize message3")?; - let state2 = state1.receive(message3)?; - - substream - .write_message( - &serde_cbor::to_vec(&state2.next_message()) - .context("Failed to serialize message4")?, - ) - .await?; - - Ok(state2) - }; - - async move { tokio::time::timeout(Duration::from_secs(60), protocol).await? } - }) - } -} - -impl From for bob::OutEvent { - fn from(event: OutEvent) -> Self { - match event { - OutEvent::Done(res) => Self::ExecutionSetupDone(Box::new(res)), - } - } -} diff --git a/swap/src/protocol/bob/refund.rs b/swap/src/protocol/bob/refund.rs index 2fe324ce..a769eda9 100644 --- a/swap/src/protocol/bob/refund.rs +++ b/swap/src/protocol/bob/refund.rs @@ -26,7 +26,7 @@ pub async fn refund( BobState::CancelTimelockExpired(state6) => state6, BobState::BtcCancelled(state6) => state6, BobState::Started { .. } - | BobState::ExecutionSetupDone(_) + | BobState::SwapSetupCompleted(_) | BobState::BtcRedeemed(_) | BobState::BtcRefunded(_) | BobState::XmrRedeemed { .. } diff --git a/swap/src/protocol/bob/spot_price.rs b/swap/src/protocol/bob/spot_price.rs deleted file mode 100644 index 072fdb2d..00000000 --- a/swap/src/protocol/bob/spot_price.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::network::cbor_request_response::CborCodec; -use crate::network::spot_price; -use crate::network::spot_price::SpotPriceProtocol; -use crate::protocol::bob::OutEvent; -use libp2p::request_response::{ProtocolSupport, RequestResponseConfig}; -use libp2p::PeerId; - -const PROTOCOL: &str = spot_price::PROTOCOL; -pub type SpotPriceOutEvent = spot_price::OutEvent; - -/// Constructs a new instance of the `spot-price` behaviour to be used by Bob. -/// -/// Bob only supports outbound connections, i.e. requesting a spot price for a -/// given amount of BTC in XMR. -pub fn bob() -> spot_price::Behaviour { - spot_price::Behaviour::new( - CborCodec::default(), - vec![(SpotPriceProtocol, ProtocolSupport::Outbound)], - RequestResponseConfig::default(), - ) -} - -impl From<(PeerId, spot_price::Message)> for OutEvent { - fn from((peer, message): (PeerId, spot_price::Message)) -> Self { - match message { - spot_price::Message::Request { .. } => Self::unexpected_request(peer), - spot_price::Message::Response { - response, - request_id, - } => Self::SpotPriceReceived { - id: request_id, - response, - }, - } - } -} - -crate::impl_from_rr_event!(SpotPriceOutEvent, OutEvent, PROTOCOL); - -#[derive(Clone, Debug, thiserror::Error, PartialEq)] -pub enum Error { - #[error("Seller currently does not accept incoming swap requests, please try again later")] - NoSwapsAccepted, - #[error("Seller refused to buy {buy} because the minimum configured buy limit is {min}")] - AmountBelowMinimum { - min: bitcoin::Amount, - buy: bitcoin::Amount, - }, - #[error("Seller refused to buy {buy} because the maximum configured buy limit is {max}")] - AmountAboveMaximum { - max: bitcoin::Amount, - buy: bitcoin::Amount, - }, - #[error("Seller's XMR balance is currently too low to fulfill the swap request to buy {buy}, please try again later")] - BalanceTooLow { buy: bitcoin::Amount }, - - #[error("Seller blockchain network {asb:?} setup did not match your blockchain network setup {cli:?}")] - BlockchainNetworkMismatch { - cli: spot_price::BlockchainNetwork, - asb: spot_price::BlockchainNetwork, - }, - - /// To be used for errors that cannot be explained on the CLI side (e.g. - /// rate update problems on the seller side) - #[error("Seller encountered a problem, please try again later.")] - Other, -} - -impl From for Error { - fn from(error: spot_price::Error) -> Self { - match error { - spot_price::Error::NoSwapsAccepted => Error::NoSwapsAccepted, - spot_price::Error::AmountBelowMinimum { min, buy } => { - Error::AmountBelowMinimum { min, buy } - } - spot_price::Error::AmountAboveMaximum { max, buy } => { - Error::AmountAboveMaximum { max, buy } - } - spot_price::Error::BalanceTooLow { buy } => Error::BalanceTooLow { buy }, - spot_price::Error::BlockchainNetworkMismatch { cli, asb } => { - Error::BlockchainNetworkMismatch { cli, asb } - } - spot_price::Error::Other => Error::Other, - } - } -} diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index 94481fe0..e80634b6 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -26,7 +26,7 @@ pub enum BobState { Started { btc_amount: bitcoin::Amount, }, - ExecutionSetupDone(State2), + SwapSetupCompleted(State2), BtcLocked(State3), XmrLockProofReceived { state: State3, @@ -52,7 +52,7 @@ impl fmt::Display for BobState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { BobState::Started { .. } => write!(f, "quote has been requested"), - BobState::ExecutionSetupDone(..) => write!(f, "execution setup done"), + BobState::SwapSetupCompleted(..) => write!(f, "execution setup done"), BobState::BtcLocked(..) => write!(f, "btc is locked"), BobState::XmrLockProofReceived { .. } => { write!(f, "XMR lock transaction transfer proof received") diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 5cdf7165..0d8eefee 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -1,12 +1,11 @@ use crate::bitcoin::{ExpiredTimelocks, TxCancel, TxRefund}; use crate::database::Swap; -use crate::env::Config; use crate::protocol::bob; use crate::protocol::bob::event_loop::EventLoopHandle; use crate::protocol::bob::state::*; +use crate::protocol::bob::swap_setup::NewSwap; use crate::{bitcoin, monero}; use anyhow::{bail, Context, Result}; -use rand::rngs::OsRng; use tokio::select; use uuid::Uuid; @@ -38,7 +37,6 @@ pub async fn run_until( &mut swap.event_loop_handle, swap.bitcoin_wallet.as_ref(), swap.monero_wallet.as_ref(), - &swap.env_config, swap.receive_monero_address, ) .await?; @@ -58,7 +56,6 @@ async fn next_state( event_loop_handle: &mut EventLoopHandle, bitcoin_wallet: &bitcoin::Wallet, monero_wallet: &monero::Wallet, - env_config: &Config, receive_monero_address: monero::Address, ) -> Result { tracing::trace!(%state, "Advancing state"); @@ -73,20 +70,19 @@ async fn next_state( .estimate_fee(TxCancel::weight(), btc_amount) .await?; - let state2 = request_price_and_setup( - swap_id, - btc_amount, - event_loop_handle, - env_config, - bitcoin_refund_address, - tx_refund_fee, - tx_cancel_fee, - ) - .await?; + let state2 = event_loop_handle + .setup_swap(NewSwap { + swap_id, + btc: btc_amount, + tx_refund_fee, + tx_cancel_fee, + bitcoin_refund_address, + }) + .await?; - BobState::ExecutionSetupDone(state2) + BobState::SwapSetupCompleted(state2) } - BobState::ExecutionSetupDone(state2) => { + BobState::SwapSetupCompleted(state2) => { // Alice and Bob have exchanged info let (state3, tx_lock) = state2.lock_btc().await?; let signed_tx = bitcoin_wallet @@ -268,34 +264,3 @@ async fn next_state( BobState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id }, }) } - -pub async fn request_price_and_setup( - swap_id: Uuid, - btc: bitcoin::Amount, - event_loop_handle: &mut EventLoopHandle, - env_config: &Config, - bitcoin_refund_address: bitcoin::Address, - tx_refund_fee: bitcoin::Amount, - tx_cancel_fee: bitcoin::Amount, -) -> Result { - let xmr = event_loop_handle.request_spot_price(btc).await?; - - tracing::info!(%btc, %xmr, "Spot price"); - - let state0 = State0::new( - swap_id, - &mut OsRng, - btc, - xmr, - env_config.bitcoin_cancel_timelock, - env_config.bitcoin_punish_timelock, - bitcoin_refund_address, - env_config.monero_finality_confirmations, - tx_refund_fee, - tx_cancel_fee, - ); - - let state2 = event_loop_handle.execution_setup(state0).await?; - - Ok(state2) -} diff --git a/swap/src/protocol/bob/swap_setup.rs b/swap/src/protocol/bob/swap_setup.rs new file mode 100644 index 00000000..9c8b07f4 --- /dev/null +++ b/swap/src/protocol/bob/swap_setup.rs @@ -0,0 +1,307 @@ +use crate::network::swap_setup::{ + protocol, read_cbor_message, write_cbor_message, BlockchainNetwork, SpotPriceError, + SpotPriceRequest, SpotPriceResponse, +}; +use crate::protocol::bob::State0; +use crate::protocol::{bob, Message1, Message3}; +use crate::{bitcoin, env, monero}; +use anyhow::Result; +use futures::future::{BoxFuture, OptionFuture}; +use futures::FutureExt; +use libp2p::core::connection::ConnectionId; +use libp2p::core::upgrade; +use libp2p::swarm::{ + KeepAlive, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, + PollParameters, ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, + SubstreamProtocol, +}; +use libp2p::{Multiaddr, PeerId}; +use std::collections::VecDeque; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::time::{Duration, Instant}; +use uuid::Uuid; +use void::Void; + +#[allow(missing_debug_implementations)] +pub struct Behaviour { + env_config: env::Config, + bitcoin_wallet: Arc, + new_swaps: VecDeque<(PeerId, NewSwap)>, + completed_swaps: VecDeque<(PeerId, Completed)>, +} + +impl Behaviour { + pub fn new(env_config: env::Config, bitcoin_wallet: Arc) -> Self { + Self { + env_config, + bitcoin_wallet, + new_swaps: VecDeque::default(), + completed_swaps: VecDeque::default(), + } + } + + pub async fn start(&mut self, alice: PeerId, swap: NewSwap) { + self.new_swaps.push_back((alice, swap)) + } +} + +impl From for bob::OutEvent { + fn from(completed: Completed) -> Self { + bob::OutEvent::SwapSetupCompleted(Box::new(completed.0)) + } +} + +impl NetworkBehaviour for Behaviour { + type ProtocolsHandler = Handler; + type OutEvent = Completed; + + fn new_handler(&mut self) -> Self::ProtocolsHandler { + Handler::new(self.env_config, self.bitcoin_wallet.clone()) + } + + fn addresses_of_peer(&mut self, _: &PeerId) -> Vec { + Vec::new() + } + + fn inject_connected(&mut self, _: &PeerId) {} + + fn inject_disconnected(&mut self, _: &PeerId) {} + + fn inject_event(&mut self, peer: PeerId, _: ConnectionId, completed: Completed) { + self.completed_swaps.push_back((peer, completed)); + } + + fn poll( + &mut self, + _cx: &mut Context<'_>, + _params: &mut impl PollParameters, + ) -> Poll> { + if let Some((_, event)) = self.completed_swaps.pop_front() { + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); + } + + if let Some((peer, event)) = self.new_swaps.pop_front() { + return Poll::Ready(NetworkBehaviourAction::NotifyHandler { + peer_id: peer, + handler: NotifyHandler::Any, + event, + }); + } + + Poll::Pending + } +} + +type OutboundStream = BoxFuture<'static, Result>; + +pub struct Handler { + outbound_stream: OptionFuture, + env_config: env::Config, + timeout: Duration, + new_swaps: VecDeque, + bitcoin_wallet: Arc, + keep_alive: KeepAlive, +} + +impl Handler { + fn new(env_config: env::Config, bitcoin_wallet: Arc) -> Self { + Self { + env_config, + outbound_stream: OptionFuture::from(None), + timeout: Duration::from_secs(60), + new_swaps: VecDeque::default(), + bitcoin_wallet, + keep_alive: KeepAlive::Until(Instant::now() + Duration::from_secs(5)), + } + } +} + +#[derive(Debug)] +pub struct NewSwap { + pub swap_id: Uuid, + pub btc: bitcoin::Amount, + pub tx_refund_fee: bitcoin::Amount, + pub tx_cancel_fee: bitcoin::Amount, + pub bitcoin_refund_address: bitcoin::Address, +} + +pub struct Completed(Result); + +impl ProtocolsHandler for Handler { + type InEvent = NewSwap; + type OutEvent = Completed; + type Error = Void; + type InboundProtocol = upgrade::DeniedUpgrade; + type OutboundProtocol = protocol::SwapSetup; + type InboundOpenInfo = (); + type OutboundOpenInfo = NewSwap; + + fn listen_protocol(&self) -> SubstreamProtocol { + SubstreamProtocol::new(upgrade::DeniedUpgrade, ()) + } + + fn inject_fully_negotiated_inbound(&mut self, _: Void, _: Self::InboundOpenInfo) { + unreachable!("Bob does not support inbound substreams") + } + + fn inject_fully_negotiated_outbound( + &mut self, + mut substream: NegotiatedSubstream, + info: Self::OutboundOpenInfo, + ) { + let bitcoin_wallet = self.bitcoin_wallet.clone(); + let env_config = self.env_config; + + let protocol = tokio::time::timeout(self.timeout, async move { + write_cbor_message(&mut substream, SpotPriceRequest { + btc: info.btc, + blockchain_network: BlockchainNetwork { + bitcoin: env_config.bitcoin_network, + monero: env_config.monero_network, + }, + }) + .await?; + + let xmr = Result::from(read_cbor_message::(&mut substream).await?)?; + + let state0 = State0::new( + info.swap_id, + &mut rand::thread_rng(), + info.btc, + xmr, + env_config.bitcoin_cancel_timelock, + env_config.bitcoin_punish_timelock, + info.bitcoin_refund_address, + env_config.monero_finality_confirmations, + info.tx_refund_fee, + info.tx_cancel_fee, + ); + + write_cbor_message(&mut substream, state0.next_message()).await?; + let message1 = read_cbor_message::(&mut substream).await?; + let state1 = state0.receive(bitcoin_wallet.as_ref(), message1).await?; + + write_cbor_message(&mut substream, state1.next_message()).await?; + let message3 = read_cbor_message::(&mut substream).await?; + let state2 = state1.receive(message3)?; + + write_cbor_message(&mut substream, state2.next_message()).await?; + + Ok(state2) + }); + + let max_seconds = self.timeout.as_secs(); + self.outbound_stream = OptionFuture::from(Some( + async move { + protocol.await.map_err(|_| Error::Timeout { + seconds: max_seconds, + })? + } + .boxed(), + )); + } + + fn inject_event(&mut self, new_swap: Self::InEvent) { + self.new_swaps.push_back(new_swap); + } + + fn inject_dial_upgrade_error( + &mut self, + _: Self::OutboundOpenInfo, + _: ProtocolsHandlerUpgrErr, + ) { + } + + fn connection_keep_alive(&self) -> KeepAlive { + self.keep_alive + } + + #[allow(clippy::type_complexity)] + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll< + ProtocolsHandlerEvent< + Self::OutboundProtocol, + Self::OutboundOpenInfo, + Self::OutEvent, + Self::Error, + >, + > { + if let Some(new_swap) = self.new_swaps.pop_front() { + self.keep_alive = KeepAlive::Yes; + return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new(protocol::new(), new_swap), + }); + } + + if let Some(result) = futures::ready!(self.outbound_stream.poll_unpin(cx)) { + self.keep_alive = KeepAlive::No; + self.outbound_stream = OptionFuture::from(None); + return Poll::Ready(ProtocolsHandlerEvent::Custom(Completed(result))); + } + + Poll::Pending + } +} + +impl From for Result { + fn from(response: SpotPriceResponse) -> Self { + match response { + SpotPriceResponse::Xmr(amount) => Ok(amount), + SpotPriceResponse::Error(e) => Err(e.into()), + } + } +} + +#[derive(Clone, Debug, thiserror::Error, PartialEq)] +pub enum Error { + #[error("Seller currently does not accept incoming swap requests, please try again later")] + NoSwapsAccepted, + #[error("Seller refused to buy {buy} because the minimum configured buy limit is {min}")] + AmountBelowMinimum { + min: bitcoin::Amount, + buy: bitcoin::Amount, + }, + #[error("Seller refused to buy {buy} because the maximum configured buy limit is {max}")] + AmountAboveMaximum { + max: bitcoin::Amount, + buy: bitcoin::Amount, + }, + #[error("Seller's XMR balance is currently too low to fulfill the swap request to buy {buy}, please try again later")] + BalanceTooLow { buy: bitcoin::Amount }, + + #[error("Seller blockchain network {asb:?} setup did not match your blockchain network setup {cli:?}")] + BlockchainNetworkMismatch { + cli: BlockchainNetwork, + asb: BlockchainNetwork, + }, + + #[error("Failed to complete swap setup within {seconds}s")] + Timeout { seconds: u64 }, + + /// To be used for errors that cannot be explained on the CLI side (e.g. + /// rate update problems on the seller side) + #[error("Seller encountered a problem, please try again later.")] + Other, +} + +impl From for Error { + fn from(error: SpotPriceError) -> Self { + match error { + SpotPriceError::NoSwapsAccepted => Error::NoSwapsAccepted, + SpotPriceError::AmountBelowMinimum { min, buy } => { + Error::AmountBelowMinimum { min, buy } + } + SpotPriceError::AmountAboveMaximum { max, buy } => { + Error::AmountAboveMaximum { max, buy } + } + SpotPriceError::BalanceTooLow { buy } => Error::BalanceTooLow { buy }, + SpotPriceError::BlockchainNetworkMismatch { cli, asb } => { + Error::BlockchainNetworkMismatch { cli, asb } + } + SpotPriceError::Other => Error::Other, + } + } +} diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index 3d266bd5..052b1d4b 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -224,8 +224,6 @@ async fn start_alice( ) -> (AliceApplicationHandle, Receiver) { let db = Arc::new(Database::open(db_path.as_path()).unwrap()); - let current_balance = monero_wallet.get_balance().await.unwrap(); - let lock_fee = monero_wallet.static_tx_fee_estimate(); let min_buy = bitcoin::Amount::from_sat(u64::MIN); let max_buy = bitcoin::Amount::from_sat(u64::MAX); let latest_rate = FixedRate::default(); @@ -233,8 +231,6 @@ async fn start_alice( let mut swarm = swarm::asb( &seed, - current_balance, - lock_fee, min_buy, max_buy, latest_rate, @@ -449,18 +445,19 @@ impl BobParams { ) -> Result<(bob::EventLoop, bob::EventLoopHandle)> { let tor_socks5_port = get_port() .expect("We don't care about Tor in the tests so we get a free port to disable it."); - let mut swarm = swarm::cli(&self.seed, self.alice_peer_id, tor_socks5_port).await?; + let mut swarm = swarm::cli( + &self.seed, + self.alice_peer_id, + tor_socks5_port, + self.env_config, + self.bitcoin_wallet.clone(), + ) + .await?; swarm .behaviour_mut() .add_address(self.alice_peer_id, self.alice_address.clone()); - bob::EventLoop::new( - swap_id, - swarm, - self.alice_peer_id, - self.bitcoin_wallet.clone(), - self.env_config, - ) + bob::EventLoop::new(swap_id, swarm, self.alice_peer_id, self.env_config) } } From c0070f8fa7c0e94692b07d27f7dc95024a0a13b2 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Fri, 25 Jun 2021 13:40:33 +1000 Subject: [PATCH 2/6] Move files from `protocol` to appropriate module Some network and application specific code does not belong in the protocol module and was moved. Eventloop, recovery and the outside behaviour were moved to the respective application module because they are application specific. The `swap_setup` was moved into the network module because upon change both sides will have to be changed and should thus stay close together. --- swap/src/asb.rs | 11 +++++++++ swap/src/{protocol/alice => asb}/behaviour.rs | 13 ++++++----- .../src/{protocol/alice => asb}/event_loop.rs | 5 ++-- swap/src/{protocol/alice => asb}/recovery.rs | 0 .../alice => asb}/recovery/cancel.rs | 0 .../alice => asb}/recovery/punish.rs | 0 .../alice => asb}/recovery/redeem.rs | 0 .../alice => asb}/recovery/refund.rs | 0 .../alice => asb}/recovery/safely_abort.rs | 0 swap/src/bin/asb.rs | 17 +++++++------- swap/src/bin/swap.rs | 18 +++++++++------ swap/src/cli.rs | 9 ++++++++ swap/src/{protocol/bob => cli}/behaviour.rs | 7 +++--- swap/src/{protocol/bob => cli}/cancel.rs | 0 swap/src/{protocol/bob => cli}/event_loop.rs | 5 ++-- swap/src/{protocol/bob => cli}/refund.rs | 0 swap/src/network.rs | 2 ++ swap/src/network/alice.rs | 1 + swap/src/network/bob.rs | 1 + swap/src/network/encrypted_signature.rs | 10 ++++---- swap/src/network/quote.rs | 11 ++++----- swap/src/network/redial.rs | 6 ++--- swap/src/network/swap_setup.rs | 3 +++ .../swap_setup/alice.rs} | 18 +++++++-------- .../swap_setup/bob.rs} | 14 +++++------ swap/src/network/swarm.rs | 11 ++++----- swap/src/network/transfer_proof.rs | 11 ++++----- swap/src/protocol/alice.rs | 16 ++----------- swap/src/protocol/alice/swap.rs | 2 +- swap/src/protocol/bob.rs | 23 +++++++------------ swap/src/protocol/bob/swap.rs | 4 ++-- ..._refund_using_cancel_and_refund_command.rs | 11 +++++---- ...and_refund_command_timelock_not_expired.rs | 19 +++++++-------- ...fund_command_timelock_not_expired_force.rs | 13 ++++++----- .../alice_manually_punishes_after_bob_dead.rs | 7 +++--- ..._manually_redeems_after_enc_sig_learned.rs | 6 ++--- .../alice_punishes_after_restart_bob_dead.rs | 2 +- ...lice_refunds_after_restart_bob_refunded.rs | 2 +- ...ncurrent_bobs_after_xmr_lock_proof_sent.rs | 2 +- ...current_bobs_before_xmr_lock_proof_sent.rs | 2 +- swap/tests/happy_path.rs | 2 +- ...ppy_path_restart_alice_after_xmr_locked.rs | 2 +- ...happy_path_restart_bob_after_xmr_locked.rs | 2 +- ...appy_path_restart_bob_before_xmr_locked.rs | 2 +- swap/tests/harness/mod.rs | 14 +++++------ swap/tests/punish.rs | 2 +- 46 files changed, 161 insertions(+), 145 deletions(-) rename swap/src/{protocol/alice => asb}/behaviour.rs (90%) rename swap/src/{protocol/alice => asb}/event_loop.rs (99%) rename swap/src/{protocol/alice => asb}/recovery.rs (100%) rename swap/src/{protocol/alice => asb}/recovery/cancel.rs (100%) rename swap/src/{protocol/alice => asb}/recovery/punish.rs (100%) rename swap/src/{protocol/alice => asb}/recovery/redeem.rs (100%) rename swap/src/{protocol/alice => asb}/recovery/refund.rs (100%) rename swap/src/{protocol/alice => asb}/recovery/safely_abort.rs (100%) rename swap/src/{protocol/bob => cli}/behaviour.rs (94%) rename swap/src/{protocol/bob => cli}/cancel.rs (100%) rename swap/src/{protocol/bob => cli}/event_loop.rs (98%) rename swap/src/{protocol/bob => cli}/refund.rs (100%) create mode 100644 swap/src/network/alice.rs create mode 100644 swap/src/network/bob.rs rename swap/src/{protocol/alice/swap_setup.rs => network/swap_setup/alice.rs} (96%) rename swap/src/{protocol/bob/swap_setup.rs => network/swap_setup/bob.rs} (96%) diff --git a/swap/src/asb.rs b/swap/src/asb.rs index 47ceb072..16fc21e6 100644 --- a/swap/src/asb.rs +++ b/swap/src/asb.rs @@ -1,7 +1,18 @@ +mod behaviour; pub mod command; pub mod config; +mod event_loop; mod rate; +mod recovery; pub mod tracing; pub mod transport; +pub use behaviour::{Behaviour, OutEvent}; +pub use event_loop::{EventLoop, EventLoopHandle, FixedRate, KrakenRate, LatestRate}; pub use rate::Rate; +pub use recovery::cancel::cancel; +pub use recovery::punish::punish; +pub use recovery::redeem::{redeem, Finality}; +pub use recovery::refund::refund; +pub use recovery::safely_abort::safely_abort; +pub use recovery::{cancel, refund}; diff --git a/swap/src/protocol/alice/behaviour.rs b/swap/src/asb/behaviour.rs similarity index 90% rename from swap/src/protocol/alice/behaviour.rs rename to swap/src/asb/behaviour.rs index 97c1b4b6..8f9ef6b8 100644 --- a/swap/src/protocol/alice/behaviour.rs +++ b/swap/src/asb/behaviour.rs @@ -1,9 +1,10 @@ +use crate::asb::event_loop::LatestRate; use crate::env; use crate::network::quote::BidQuote; +use crate::network::swap_setup::alice; +use crate::network::swap_setup::alice::WalletSnapshot; use crate::network::{encrypted_signature, quote, transfer_proof}; -use crate::protocol::alice::event_loop::LatestRate; -use crate::protocol::alice::swap_setup::WalletSnapshot; -use crate::protocol::alice::{swap_setup, State3}; +use crate::protocol::alice::State3; use anyhow::{anyhow, Error}; use libp2p::ping::{Ping, PingEvent}; use libp2p::request_response::{RequestId, ResponseChannel}; @@ -22,7 +23,7 @@ pub enum OutEvent { }, SwapDeclined { peer: PeerId, - error: swap_setup::Error, + error: alice::Error, }, QuoteRequested { channel: ResponseChannel, @@ -71,7 +72,7 @@ where LR: LatestRate + Send + 'static, { pub quote: quote::Behaviour, - pub swap_setup: swap_setup::Behaviour, + pub swap_setup: alice::Behaviour, pub transfer_proof: transfer_proof::Behaviour, pub encrypted_signature: encrypted_signature::Behaviour, @@ -94,7 +95,7 @@ where ) -> Self { Self { quote: quote::alice(), - swap_setup: swap_setup::Behaviour::new( + swap_setup: alice::Behaviour::new( min_buy, max_buy, env_config, diff --git a/swap/src/protocol/alice/event_loop.rs b/swap/src/asb/event_loop.rs similarity index 99% rename from swap/src/protocol/alice/event_loop.rs rename to swap/src/asb/event_loop.rs index 6456e152..e18d595e 100644 --- a/swap/src/protocol/alice/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -1,10 +1,11 @@ +use crate::asb::behaviour::{Behaviour, OutEvent}; use crate::asb::Rate; use crate::database::Database; use crate::env::Config; use crate::network::quote::BidQuote; +use crate::network::swap_setup::alice::WalletSnapshot; use crate::network::transfer_proof; -use crate::protocol::alice::swap_setup::WalletSnapshot; -use crate::protocol::alice::{AliceState, Behaviour, OutEvent, State3, Swap}; +use crate::protocol::alice::{AliceState, State3, Swap}; use crate::{bitcoin, kraken, monero}; use anyhow::{Context, Result}; use futures::future; diff --git a/swap/src/protocol/alice/recovery.rs b/swap/src/asb/recovery.rs similarity index 100% rename from swap/src/protocol/alice/recovery.rs rename to swap/src/asb/recovery.rs diff --git a/swap/src/protocol/alice/recovery/cancel.rs b/swap/src/asb/recovery/cancel.rs similarity index 100% rename from swap/src/protocol/alice/recovery/cancel.rs rename to swap/src/asb/recovery/cancel.rs diff --git a/swap/src/protocol/alice/recovery/punish.rs b/swap/src/asb/recovery/punish.rs similarity index 100% rename from swap/src/protocol/alice/recovery/punish.rs rename to swap/src/asb/recovery/punish.rs diff --git a/swap/src/protocol/alice/recovery/redeem.rs b/swap/src/asb/recovery/redeem.rs similarity index 100% rename from swap/src/protocol/alice/recovery/redeem.rs rename to swap/src/asb/recovery/redeem.rs diff --git a/swap/src/protocol/alice/recovery/refund.rs b/swap/src/asb/recovery/refund.rs similarity index 100% rename from swap/src/protocol/alice/recovery/refund.rs rename to swap/src/asb/recovery/refund.rs diff --git a/swap/src/protocol/alice/recovery/safely_abort.rs b/swap/src/asb/recovery/safely_abort.rs similarity index 100% rename from swap/src/protocol/alice/recovery/safely_abort.rs rename to swap/src/asb/recovery/safely_abort.rs diff --git a/swap/src/bin/asb.rs b/swap/src/bin/asb.rs index 63b4c459..02237284 100644 --- a/swap/src/bin/asb.rs +++ b/swap/src/bin/asb.rs @@ -26,12 +26,11 @@ use swap::asb::command::{parse_args, Arguments, Command}; use swap::asb::config::{ initial_setup, query_user_for_initial_config, read_config, Config, ConfigNotInitialized, }; +use swap::asb::{cancel, punish, redeem, refund, safely_abort, EventLoop, Finality, KrakenRate}; use swap::database::Database; use swap::monero::Amount; use swap::network::swarm; -use swap::protocol::alice; -use swap::protocol::alice::event_loop::KrakenRate; -use swap::protocol::alice::{redeem, run, EventLoop}; +use swap::protocol::alice::run; use swap::seed::Seed; use swap::tor::AuthenticatedClient; use swap::{asb, bitcoin, kraken, monero, tor}; @@ -237,7 +236,7 @@ async fn main() -> Result<()> { let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; let (txid, _) = - alice::cancel(swap_id, Arc::new(bitcoin_wallet), Arc::new(db), force).await??; + cancel(swap_id, Arc::new(bitcoin_wallet), Arc::new(db), force).await??; tracing::info!("Cancel transaction successfully published with id {}", txid); } @@ -245,7 +244,7 @@ async fn main() -> Result<()> { let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; let monero_wallet = init_monero_wallet(&config, env_config).await?; - alice::refund( + refund( swap_id, Arc::new(bitcoin_wallet), Arc::new(monero_wallet), @@ -260,12 +259,12 @@ async fn main() -> Result<()> { let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; let (txid, _) = - alice::punish(swap_id, Arc::new(bitcoin_wallet), Arc::new(db), force).await??; + punish(swap_id, Arc::new(bitcoin_wallet), Arc::new(db), force).await??; tracing::info!("Punish transaction successfully published with id {}", txid); } Command::SafelyAbort { swap_id } => { - alice::safely_abort(swap_id, Arc::new(db)).await?; + safely_abort(swap_id, Arc::new(db)).await?; tracing::info!("Swap safely aborted"); } @@ -276,12 +275,12 @@ async fn main() -> Result<()> { } => { let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; - let (txid, _) = alice::redeem( + let (txid, _) = redeem( swap_id, Arc::new(bitcoin_wallet), Arc::new(db), force, - redeem::Finality::from_bool(do_not_await_finality), + Finality::from_bool(do_not_await_finality), ) .await?; diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index 71fff4f3..1d1327ae 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -24,12 +24,13 @@ use std::sync::Arc; use std::time::Duration; use swap::bitcoin::TxLock; use swap::cli::command::{parse_args_and_apply_defaults, Arguments, Command, ParseResult}; +use swap::cli::EventLoop; use swap::database::Database; use swap::env::Config; use swap::network::quote::BidQuote; use swap::network::swarm; use swap::protocol::bob; -use swap::protocol::bob::{EventLoop, Swap}; +use swap::protocol::bob::Swap; use swap::seed::Seed; use swap::{bitcoin, cli, monero}; use tracing::{debug, error, info, warn}; @@ -245,13 +246,13 @@ async fn main() -> Result<()> { ) .await?; - let cancel = bob::cancel(swap_id, Arc::new(bitcoin_wallet), db, force).await?; + let cancel = cli::cancel(swap_id, Arc::new(bitcoin_wallet), db, force).await?; match cancel { Ok((txid, _)) => { debug!("Cancel transaction successfully published with id {}", txid) } - Err(bob::cancel::Error::CancelTimelockNotExpiredYet) => error!( + Err(cli::cancel::Error::CancelTimelockNotExpiredYet) => error!( "The Cancel Transaction cannot be published yet, because the timelock has not expired. Please try again later" ), } @@ -277,7 +278,7 @@ async fn main() -> Result<()> { ) .await?; - bob::refund(swap_id, Arc::new(bitcoin_wallet), db, force).await??; + cli::refund(swap_id, Arc::new(bitcoin_wallet), db, force).await??; } }; Ok(()) @@ -428,12 +429,15 @@ where #[cfg(test)] mod tests { - use super::*; - use crate::determine_btc_to_swap; - use ::bitcoin::Amount; use std::sync::Mutex; + + use ::bitcoin::Amount; use tracing::subscriber; + use crate::determine_btc_to_swap; + + use super::*; + struct MaxGiveable { amounts: Vec, call_counter: usize, diff --git a/swap/src/cli.rs b/swap/src/cli.rs index 7962efd8..30a7f7d6 100644 --- a/swap/src/cli.rs +++ b/swap/src/cli.rs @@ -1,3 +1,12 @@ +mod behaviour; +pub mod cancel; pub mod command; +mod event_loop; +pub mod refund; pub mod tracing; pub mod transport; + +pub use behaviour::{Behaviour, OutEvent}; +pub use cancel::cancel; +pub use event_loop::{EventLoop, EventLoopHandle}; +pub use refund::refund; diff --git a/swap/src/protocol/bob/behaviour.rs b/swap/src/cli/behaviour.rs similarity index 94% rename from swap/src/protocol/bob/behaviour.rs rename to swap/src/cli/behaviour.rs index 951c84f6..be6596e3 100644 --- a/swap/src/protocol/bob/behaviour.rs +++ b/swap/src/cli/behaviour.rs @@ -1,6 +1,7 @@ use crate::network::quote::BidQuote; +use crate::network::swap_setup::bob; use crate::network::{encrypted_signature, quote, redial, transfer_proof}; -use crate::protocol::bob::{swap_setup, State2}; +use crate::protocol::bob::State2; use crate::{bitcoin, env}; use anyhow::{anyhow, Error, Result}; use libp2p::core::Multiaddr; @@ -59,7 +60,7 @@ impl OutEvent { #[allow(missing_debug_implementations)] pub struct Behaviour { pub quote: quote::Behaviour, - pub swap_setup: swap_setup::Behaviour, + pub swap_setup: bob::Behaviour, pub transfer_proof: transfer_proof::Behaviour, pub encrypted_signature: encrypted_signature::Behaviour, pub redial: redial::Behaviour, @@ -78,7 +79,7 @@ impl Behaviour { ) -> Self { Self { quote: quote::bob(), - swap_setup: swap_setup::Behaviour::new(env_config, bitcoin_wallet), + swap_setup: bob::Behaviour::new(env_config, bitcoin_wallet), transfer_proof: transfer_proof::bob(), encrypted_signature: encrypted_signature::bob(), redial: redial::Behaviour::new(alice, Duration::from_secs(2)), diff --git a/swap/src/protocol/bob/cancel.rs b/swap/src/cli/cancel.rs similarity index 100% rename from swap/src/protocol/bob/cancel.rs rename to swap/src/cli/cancel.rs diff --git a/swap/src/protocol/bob/event_loop.rs b/swap/src/cli/event_loop.rs similarity index 98% rename from swap/src/protocol/bob/event_loop.rs rename to swap/src/cli/event_loop.rs index 742129a0..9d55a81a 100644 --- a/swap/src/protocol/bob/event_loop.rs +++ b/swap/src/cli/event_loop.rs @@ -1,8 +1,9 @@ use crate::bitcoin::EncryptedSignature; +use crate::cli::behaviour::{Behaviour, OutEvent}; use crate::network::encrypted_signature; use crate::network::quote::BidQuote; -use crate::protocol::bob::swap_setup::NewSwap; -use crate::protocol::bob::{Behaviour, OutEvent, State2}; +use crate::network::swap_setup::bob::NewSwap; +use crate::protocol::bob::State2; use crate::{env, monero}; use anyhow::{Context, Result}; use futures::future::{BoxFuture, OptionFuture}; diff --git a/swap/src/protocol/bob/refund.rs b/swap/src/cli/refund.rs similarity index 100% rename from swap/src/protocol/bob/refund.rs rename to swap/src/cli/refund.rs diff --git a/swap/src/network.rs b/swap/src/network.rs index 1eea35e4..d02cddbd 100644 --- a/swap/src/network.rs +++ b/swap/src/network.rs @@ -1,5 +1,7 @@ mod impl_from_rr_event; +pub mod alice; +pub mod bob; pub mod cbor_request_response; pub mod encrypted_signature; pub mod json_pull_codec; diff --git a/swap/src/network/alice.rs b/swap/src/network/alice.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/swap/src/network/alice.rs @@ -0,0 +1 @@ + diff --git a/swap/src/network/bob.rs b/swap/src/network/bob.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/swap/src/network/bob.rs @@ -0,0 +1 @@ + diff --git a/swap/src/network/encrypted_signature.rs b/swap/src/network/encrypted_signature.rs index 041220e7..15323794 100644 --- a/swap/src/network/encrypted_signature.rs +++ b/swap/src/network/encrypted_signature.rs @@ -1,5 +1,5 @@ use crate::network::cbor_request_response::CborCodec; -use crate::protocol::{alice, bob}; +use crate::{asb, cli}; use libp2p::core::ProtocolName; use libp2p::request_response::{ ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent, @@ -46,7 +46,7 @@ pub fn bob() -> Behaviour { ) } -impl From<(PeerId, Message)> for alice::OutEvent { +impl From<(PeerId, Message)> for asb::OutEvent { fn from((peer, message): (PeerId, Message)) -> Self { match message { Message::Request { @@ -60,9 +60,9 @@ impl From<(PeerId, Message)> for alice::OutEvent { } } } -crate::impl_from_rr_event!(OutEvent, alice::OutEvent, PROTOCOL); +crate::impl_from_rr_event!(OutEvent, asb::OutEvent, PROTOCOL); -impl From<(PeerId, Message)> for bob::OutEvent { +impl From<(PeerId, Message)> for cli::OutEvent { fn from((peer, message): (PeerId, Message)) -> Self { match message { Message::Request { .. } => Self::unexpected_request(peer), @@ -72,4 +72,4 @@ impl From<(PeerId, Message)> for bob::OutEvent { } } } -crate::impl_from_rr_event!(OutEvent, bob::OutEvent, PROTOCOL); +crate::impl_from_rr_event!(OutEvent, cli::OutEvent, PROTOCOL); diff --git a/swap/src/network/quote.rs b/swap/src/network/quote.rs index 7fb999ff..1c78adbe 100644 --- a/swap/src/network/quote.rs +++ b/swap/src/network/quote.rs @@ -1,6 +1,5 @@ -use crate::bitcoin; use crate::network::json_pull_codec::JsonPullCodec; -use crate::protocol::{alice, bob}; +use crate::{asb, bitcoin, cli}; use libp2p::core::ProtocolName; use libp2p::request_response::{ ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent, @@ -60,7 +59,7 @@ pub fn bob() -> Behaviour { ) } -impl From<(PeerId, Message)> for alice::OutEvent { +impl From<(PeerId, Message)> for asb::OutEvent { fn from((peer, message): (PeerId, Message)) -> Self { match message { Message::Request { channel, .. } => Self::QuoteRequested { channel, peer }, @@ -68,9 +67,9 @@ impl From<(PeerId, Message)> for alice::OutEvent { } } } -crate::impl_from_rr_event!(OutEvent, alice::OutEvent, PROTOCOL); +crate::impl_from_rr_event!(OutEvent, asb::OutEvent, PROTOCOL); -impl From<(PeerId, Message)> for bob::OutEvent { +impl From<(PeerId, Message)> for cli::OutEvent { fn from((peer, message): (PeerId, Message)) -> Self { match message { Message::Request { .. } => Self::unexpected_request(peer), @@ -84,4 +83,4 @@ impl From<(PeerId, Message)> for bob::OutEvent { } } } -crate::impl_from_rr_event!(OutEvent, bob::OutEvent, PROTOCOL); +crate::impl_from_rr_event!(OutEvent, cli::OutEvent, PROTOCOL); diff --git a/swap/src/network/redial.rs b/swap/src/network/redial.rs index a6c00706..23670344 100644 --- a/swap/src/network/redial.rs +++ b/swap/src/network/redial.rs @@ -1,4 +1,4 @@ -use crate::protocol::bob; +use crate::cli; use backoff::backoff::Backoff; use backoff::ExponentialBackoff; use futures::future::FutureExt; @@ -119,11 +119,11 @@ impl NetworkBehaviour for Behaviour { } } -impl From for bob::OutEvent { +impl From for cli::OutEvent { fn from(event: OutEvent) -> Self { match event { OutEvent::AllAttemptsExhausted { peer } => { - bob::OutEvent::AllRedialAttemptsExhausted { peer } + cli::OutEvent::AllRedialAttemptsExhausted { peer } } } } diff --git a/swap/src/network/swap_setup.rs b/swap/src/network/swap_setup.rs index fb5a5657..621f9df3 100644 --- a/swap/src/network/swap_setup.rs +++ b/swap/src/network/swap_setup.rs @@ -5,6 +5,9 @@ use libp2p::swarm::NegotiatedSubstream; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; +pub mod alice; +pub mod bob; + pub const BUF_SIZE: usize = 1024 * 1024; pub mod protocol { diff --git a/swap/src/protocol/alice/swap_setup.rs b/swap/src/network/swap_setup/alice.rs similarity index 96% rename from swap/src/protocol/alice/swap_setup.rs rename to swap/src/network/swap_setup/alice.rs index 333a4769..ab4a1a17 100644 --- a/swap/src/protocol/alice/swap_setup.rs +++ b/swap/src/network/swap_setup/alice.rs @@ -1,11 +1,11 @@ +use crate::asb::LatestRate; use crate::network::swap_setup; use crate::network::swap_setup::{ protocol, BlockchainNetwork, SpotPriceError, SpotPriceRequest, SpotPriceResponse, }; -use crate::protocol::alice::event_loop::LatestRate; use crate::protocol::alice::{State0, State3}; -use crate::protocol::{alice, Message0, Message2, Message4}; -use crate::{bitcoin, env, monero}; +use crate::protocol::{Message0, Message2, Message4}; +use crate::{asb, bitcoin, env, monero}; use anyhow::{anyhow, Context, Result}; use futures::future::{BoxFuture, OptionFuture}; use futures::FutureExt; @@ -81,24 +81,24 @@ impl WalletSnapshot { } } -impl From for alice::OutEvent { +impl From for asb::OutEvent { fn from(event: OutEvent) -> Self { match event { OutEvent::Initiated { send_wallet_snapshot, - } => alice::OutEvent::SwapSetupInitiated { + } => asb::OutEvent::SwapSetupInitiated { send_wallet_snapshot, }, OutEvent::Completed { peer_id: bob_peer_id, swap_id, state3, - } => alice::OutEvent::SwapSetupCompleted { + } => asb::OutEvent::SwapSetupCompleted { peer_id: bob_peer_id, swap_id, state3: Box::new(state3), }, - OutEvent::Error { peer_id, error } => alice::OutEvent::Failure { + OutEvent::Error { peer_id, error } => asb::OutEvent::Failure { peer: peer_id, error: anyhow!(error), }, @@ -194,7 +194,7 @@ where } } -type InboundStream = BoxFuture<'static, Result<(Uuid, alice::State3)>>; +type InboundStream = BoxFuture<'static, Result<(Uuid, State3)>>; pub struct Handler { inbound_stream: OptionFuture, @@ -236,7 +236,7 @@ impl Handler { #[allow(clippy::large_enum_variant)] pub enum HandlerOutEvent { Initiated(bmrng::RequestReceiver), - Completed(Result<(Uuid, alice::State3)>), + Completed(Result<(Uuid, State3)>), } impl ProtocolsHandler for Handler diff --git a/swap/src/protocol/bob/swap_setup.rs b/swap/src/network/swap_setup/bob.rs similarity index 96% rename from swap/src/protocol/bob/swap_setup.rs rename to swap/src/network/swap_setup/bob.rs index 9c8b07f4..7f8110a1 100644 --- a/swap/src/protocol/bob/swap_setup.rs +++ b/swap/src/network/swap_setup/bob.rs @@ -2,9 +2,9 @@ use crate::network::swap_setup::{ protocol, read_cbor_message, write_cbor_message, BlockchainNetwork, SpotPriceError, SpotPriceRequest, SpotPriceResponse, }; -use crate::protocol::bob::State0; -use crate::protocol::{bob, Message1, Message3}; -use crate::{bitcoin, env, monero}; +use crate::protocol::bob::{State0, State2}; +use crate::protocol::{Message1, Message3}; +use crate::{bitcoin, cli, env, monero}; use anyhow::Result; use futures::future::{BoxFuture, OptionFuture}; use futures::FutureExt; @@ -46,9 +46,9 @@ impl Behaviour { } } -impl From for bob::OutEvent { +impl From for cli::OutEvent { fn from(completed: Completed) -> Self { - bob::OutEvent::SwapSetupCompleted(Box::new(completed.0)) + cli::OutEvent::SwapSetupCompleted(Box::new(completed.0)) } } @@ -93,7 +93,7 @@ impl NetworkBehaviour for Behaviour { } } -type OutboundStream = BoxFuture<'static, Result>; +type OutboundStream = BoxFuture<'static, Result>; pub struct Handler { outbound_stream: OptionFuture, @@ -126,7 +126,7 @@ pub struct NewSwap { pub bitcoin_refund_address: bitcoin::Address, } -pub struct Completed(Result); +pub struct Completed(Result); impl ProtocolsHandler for Handler { type InEvent = NewSwap; diff --git a/swap/src/network/swarm.rs b/swap/src/network/swarm.rs index 43d635d7..bbcc2353 100644 --- a/swap/src/network/swarm.rs +++ b/swap/src/network/swarm.rs @@ -1,5 +1,4 @@ -use crate::protocol::alice::event_loop::LatestRate; -use crate::protocol::{alice, bob}; +use crate::asb::LatestRate; use crate::seed::Seed; use crate::{asb, bitcoin, cli, env, tor}; use anyhow::Result; @@ -16,11 +15,11 @@ pub fn asb( latest_rate: LR, resume_only: bool, env_config: env::Config, -) -> Result>> +) -> Result>> where LR: LatestRate + Send + 'static + Debug + Clone, { - let behaviour = alice::Behaviour::new(min_buy, max_buy, latest_rate, resume_only, env_config); + let behaviour = asb::Behaviour::new(min_buy, max_buy, latest_rate, resume_only, env_config); let identity = seed.derive_libp2p_identity(); let transport = asb::transport::new(&identity)?; @@ -41,13 +40,13 @@ pub async fn cli( tor_socks5_port: u16, env_config: env::Config, bitcoin_wallet: Arc, -) -> Result> { +) -> Result> { let maybe_tor_socks5_port = match tor::Client::new(tor_socks5_port).assert_tor_running().await { Ok(()) => Some(tor_socks5_port), Err(_) => None, }; - let behaviour = bob::Behaviour::new(alice, env_config, bitcoin_wallet); + let behaviour = cli::Behaviour::new(alice, env_config, bitcoin_wallet); let identity = seed.derive_libp2p_identity(); let transport = cli::transport::new(&identity, maybe_tor_socks5_port)?; diff --git a/swap/src/network/transfer_proof.rs b/swap/src/network/transfer_proof.rs index def98bb9..98ab88ac 100644 --- a/swap/src/network/transfer_proof.rs +++ b/swap/src/network/transfer_proof.rs @@ -1,6 +1,5 @@ -use crate::monero; use crate::network::cbor_request_response::CborCodec; -use crate::protocol::{alice, bob}; +use crate::{asb, cli, monero}; use libp2p::core::ProtocolName; use libp2p::request_response::{ ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent, @@ -47,7 +46,7 @@ pub fn bob() -> Behaviour { ) } -impl From<(PeerId, Message)> for alice::OutEvent { +impl From<(PeerId, Message)> for asb::OutEvent { fn from((peer, message): (PeerId, Message)) -> Self { match message { Message::Request { .. } => Self::unexpected_request(peer), @@ -58,9 +57,9 @@ impl From<(PeerId, Message)> for alice::OutEvent { } } } -crate::impl_from_rr_event!(OutEvent, alice::OutEvent, PROTOCOL); +crate::impl_from_rr_event!(OutEvent, asb::OutEvent, PROTOCOL); -impl From<(PeerId, Message)> for bob::OutEvent { +impl From<(PeerId, Message)> for cli::OutEvent { fn from((peer, message): (PeerId, Message)) -> Self { match message { Message::Request { @@ -74,4 +73,4 @@ impl From<(PeerId, Message)> for bob::OutEvent { } } } -crate::impl_from_rr_event!(OutEvent, bob::OutEvent, PROTOCOL); +crate::impl_from_rr_event!(OutEvent, cli::OutEvent, PROTOCOL); diff --git a/swap/src/protocol/alice.rs b/swap/src/protocol/alice.rs index fa8a4317..f8e80ca7 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -2,31 +2,19 @@ //! Alice holds XMR and wishes receive BTC. use crate::database::Database; use crate::env::Config; -use crate::{bitcoin, monero}; +use crate::{asb, bitcoin, monero}; use std::sync::Arc; use uuid::Uuid; -pub use self::behaviour::{Behaviour, OutEvent}; -pub use self::event_loop::{EventLoop, EventLoopHandle}; -pub use self::recovery::cancel::cancel; -pub use self::recovery::punish::punish; -pub use self::recovery::redeem::redeem; -pub use self::recovery::refund::refund; -pub use self::recovery::safely_abort::safely_abort; -pub use self::recovery::{cancel, punish, redeem, refund, safely_abort}; pub use self::state::*; pub use self::swap::{run, run_until}; -mod behaviour; -pub mod event_loop; -mod recovery; pub mod state; pub mod swap; -pub mod swap_setup; pub struct Swap { pub state: AliceState, - pub event_loop_handle: EventLoopHandle, + pub event_loop_handle: asb::EventLoopHandle, pub bitcoin_wallet: Arc, pub monero_wallet: Arc, pub env_config: Config, diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 4070a04b..c15892a1 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -1,8 +1,8 @@ //! Run an XMR/BTC swap in the role of Alice. //! Alice holds XMR and wishes receive BTC. +use crate::asb::{EventLoopHandle, LatestRate}; use crate::bitcoin::ExpiredTimelocks; use crate::env::Config; -use crate::protocol::alice::event_loop::{EventLoopHandle, LatestRate}; use crate::protocol::alice::{AliceState, Swap}; use crate::{bitcoin, database, monero}; use anyhow::{bail, Context, Result}; diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index 4c93b73b..347f86d8 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -1,27 +1,20 @@ -use crate::database::Database; -use crate::{bitcoin, env, monero}; -use anyhow::Result; use std::sync::Arc; + +use anyhow::Result; use uuid::Uuid; -pub use self::behaviour::{Behaviour, OutEvent}; -pub use self::cancel::cancel; -pub use self::event_loop::{EventLoop, EventLoopHandle}; -pub use self::refund::refund; +use crate::database::Database; +use crate::{bitcoin, cli, env, monero}; + pub use self::state::*; pub use self::swap::{run, run_until}; -mod behaviour; -pub mod cancel; -pub mod event_loop; -pub mod refund; pub mod state; pub mod swap; -mod swap_setup; pub struct Swap { pub state: BobState, - pub event_loop_handle: EventLoopHandle, + pub event_loop_handle: cli::EventLoopHandle, pub db: Database, pub bitcoin_wallet: Arc, pub monero_wallet: Arc, @@ -38,7 +31,7 @@ impl Swap { bitcoin_wallet: Arc, monero_wallet: Arc, env_config: env::Config, - event_loop_handle: EventLoopHandle, + event_loop_handle: cli::EventLoopHandle, receive_monero_address: monero::Address, btc_amount: bitcoin::Amount, ) -> Self { @@ -60,7 +53,7 @@ impl Swap { bitcoin_wallet: Arc, monero_wallet: Arc, env_config: env::Config, - event_loop_handle: EventLoopHandle, + event_loop_handle: cli::EventLoopHandle, receive_monero_address: monero::Address, ) -> Result { let state = db.get_state(id)?.try_into_bob()?.into(); diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 0d8eefee..23ab6388 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -1,9 +1,9 @@ use crate::bitcoin::{ExpiredTimelocks, TxCancel, TxRefund}; +use crate::cli::EventLoopHandle; use crate::database::Swap; +use crate::network::swap_setup::bob::NewSwap; use crate::protocol::bob; -use crate::protocol::bob::event_loop::EventLoopHandle; use crate::protocol::bob::state::*; -use crate::protocol::bob::swap_setup::NewSwap; use crate::{bitcoin, monero}; use anyhow::{bail, Context, Result}; use tokio::select; diff --git a/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command.rs b/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command.rs index 557768a3..94e12506 100644 --- a/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command.rs +++ b/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command.rs @@ -3,10 +3,11 @@ pub mod harness; use harness::alice_run_until::is_xmr_lock_transaction_sent; use harness::bob_run_until::is_btc_locked; use harness::FastCancelConfig; -use swap::protocol::alice::event_loop::FixedRate; +use swap::asb::FixedRate; use swap::protocol::alice::AliceState; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; +use swap::{asb, cli}; #[tokio::test] async fn given_alice_and_bob_manually_refund_after_funds_locked_both_refund() { @@ -50,7 +51,7 @@ async fn given_alice_and_bob_manually_refund_after_funds_locked_both_refund() { // Bob manually cancels bob_join_handle.abort(); let (_, state) = - bob::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false).await??; + cli::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false).await??; assert!(matches!(state, BobState::BtcCancelled { .. })); let (bob_swap, bob_join_handle) = ctx @@ -61,7 +62,7 @@ async fn given_alice_and_bob_manually_refund_after_funds_locked_both_refund() { // Bob manually refunds bob_join_handle.abort(); let bob_state = - bob::refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false).await??; + cli::refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false).await??; ctx.assert_bob_refunded(bob_state).await; @@ -74,7 +75,7 @@ async fn given_alice_and_bob_manually_refund_after_funds_locked_both_refund() { AliceState::XmrLockTransactionSent { .. } )); - alice::cancel( + asb::cancel( alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.db, @@ -86,7 +87,7 @@ async fn given_alice_and_bob_manually_refund_after_funds_locked_both_refund() { ctx.restart_alice().await; let alice_swap = ctx.alice_next_swap().await; assert!(matches!(alice_swap.state, AliceState::BtcCancelled { .. })); - let alice_state = alice::refund( + let alice_state = asb::refund( alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.monero_wallet, diff --git a/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired.rs b/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired.rs index 9a20127e..13dd1160 100644 --- a/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired.rs +++ b/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired.rs @@ -3,10 +3,11 @@ pub mod harness; use harness::alice_run_until::is_xmr_lock_transaction_sent; use harness::bob_run_until::is_btc_locked; use harness::SlowCancelConfig; -use swap::protocol::alice::event_loop::FixedRate; +use swap::asb::FixedRate; use swap::protocol::alice::AliceState; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; +use swap::{asb, cli}; #[tokio::test] async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors() { @@ -37,12 +38,12 @@ async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors() )); // Bob tries but fails to manually cancel - let result = bob::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false) + let result = cli::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false) .await? .unwrap_err(); assert!(matches!( result, - bob::cancel::Error::CancelTimelockNotExpiredYet + cli::cancel::Error::CancelTimelockNotExpiredYet )); ctx.restart_alice().await; @@ -53,7 +54,7 @@ async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors() )); // Alice tries but fails manual cancel - let result = alice::cancel( + let result = asb::cancel( alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.db, @@ -63,7 +64,7 @@ async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors() .unwrap_err(); assert!(matches!( result, - alice::cancel::Error::CancelTimelockNotExpiredYet + asb::cancel::Error::CancelTimelockNotExpiredYet )); let (bob_swap, bob_join_handle) = ctx @@ -72,10 +73,10 @@ async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors() assert!(matches!(bob_swap.state, BobState::BtcLocked { .. })); // Bob tries but fails to manually refund - let result = bob::refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false) + let result = cli::refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false) .await? .unwrap_err(); - assert!(matches!(result, bob::refund::SwapNotCancelledYet(_))); + assert!(matches!(result, cli::refund::SwapNotCancelledYet(_))); let (bob_swap, _) = ctx .stop_and_resume_bob_from_db(bob_join_handle, swap_id) @@ -90,7 +91,7 @@ async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors() )); // Alice tries but fails manual cancel - let result = alice::refund( + let result = asb::refund( alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.monero_wallet, @@ -99,7 +100,7 @@ async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors() ) .await? .unwrap_err(); - assert!(matches!(result, alice::refund::Error::SwapNotCancelled)); + assert!(matches!(result, asb::refund::Error::SwapNotCancelled)); ctx.restart_alice().await; let alice_swap = ctx.alice_next_swap().await; diff --git a/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired_force.rs b/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired_force.rs index 843d6a79..de16ec96 100644 --- a/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired_force.rs +++ b/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired_force.rs @@ -3,10 +3,11 @@ pub mod harness; use harness::alice_run_until::is_xmr_lock_transaction_sent; use harness::bob_run_until::is_btc_locked; use harness::SlowCancelConfig; -use swap::protocol::alice::event_loop::FixedRate; +use swap::asb::FixedRate; use swap::protocol::alice::AliceState; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; +use swap::{asb, cli}; #[tokio::test] async fn given_alice_and_bob_manually_force_cancel_when_timelock_not_expired_errors() { @@ -37,7 +38,7 @@ async fn given_alice_and_bob_manually_force_cancel_when_timelock_not_expired_err )); // Bob tries but fails to manually cancel - let result = bob::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, true).await; + let result = cli::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, true).await; assert!(matches!(result, Err(_))); ctx.restart_alice().await; @@ -48,7 +49,7 @@ async fn given_alice_and_bob_manually_force_cancel_when_timelock_not_expired_err )); // Alice tries but fails manual cancel - let is_outer_err = alice::cancel( + let is_outer_err = asb::cancel( alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.db, @@ -64,7 +65,7 @@ async fn given_alice_and_bob_manually_force_cancel_when_timelock_not_expired_err assert!(matches!(bob_swap.state, BobState::BtcLocked { .. })); // Bob tries but fails to manually refund - let is_outer_err = bob::refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, true) + let is_outer_err = cli::refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, true) .await .is_err(); assert!(is_outer_err); @@ -82,7 +83,7 @@ async fn given_alice_and_bob_manually_force_cancel_when_timelock_not_expired_err )); // Alice tries but fails manual cancel - let refund_tx_not_published_yet = alice::refund( + let refund_tx_not_published_yet = asb::refund( alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.monero_wallet, @@ -93,7 +94,7 @@ async fn given_alice_and_bob_manually_force_cancel_when_timelock_not_expired_err .unwrap_err(); assert!(matches!( refund_tx_not_published_yet, - alice::refund::Error::RefundTransactionNotPublishedYet(..) + asb::refund::Error::RefundTransactionNotPublishedYet(..) )); ctx.restart_alice().await; diff --git a/swap/tests/alice_manually_punishes_after_bob_dead.rs b/swap/tests/alice_manually_punishes_after_bob_dead.rs index 2da13969..2cfa11ae 100644 --- a/swap/tests/alice_manually_punishes_after_bob_dead.rs +++ b/swap/tests/alice_manually_punishes_after_bob_dead.rs @@ -3,7 +3,8 @@ pub mod harness; use harness::alice_run_until::is_xmr_lock_transaction_sent; use harness::bob_run_until::is_btc_locked; use harness::FastPunishConfig; -use swap::protocol::alice::event_loop::FixedRate; +use swap::asb; +use swap::asb::FixedRate; use swap::protocol::alice::AliceState; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; @@ -47,7 +48,7 @@ async fn alice_manually_punishes_after_bob_dead() { ctx.restart_alice().await; let alice_swap = ctx.alice_next_swap().await; - let (_, alice_state) = alice::cancel( + let (_, alice_state) = asb::cancel( alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.db, @@ -70,7 +71,7 @@ async fn alice_manually_punishes_after_bob_dead() { ctx.restart_alice().await; let alice_swap = ctx.alice_next_swap().await; - let (_, alice_state) = alice::punish( + let (_, alice_state) = asb::punish( alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.db, diff --git a/swap/tests/alice_manually_redeems_after_enc_sig_learned.rs b/swap/tests/alice_manually_redeems_after_enc_sig_learned.rs index 720dad19..0ece1b85 100644 --- a/swap/tests/alice_manually_redeems_after_enc_sig_learned.rs +++ b/swap/tests/alice_manually_redeems_after_enc_sig_learned.rs @@ -2,8 +2,8 @@ pub mod harness; use harness::alice_run_until::is_encsig_learned; use harness::SlowCancelConfig; -use swap::protocol::alice::event_loop::FixedRate; -use swap::protocol::alice::redeem::Finality; +use swap::asb; +use swap::asb::{Finality, FixedRate}; use swap::protocol::alice::AliceState; use swap::protocol::{alice, bob}; @@ -28,7 +28,7 @@ async fn alice_manually_redeems_after_enc_sig_learned() { // manual redeem ctx.restart_alice().await; let alice_swap = ctx.alice_next_swap().await; - let (_, alice_state) = alice::redeem( + let (_, alice_state) = asb::redeem( alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.db, diff --git a/swap/tests/alice_punishes_after_restart_bob_dead.rs b/swap/tests/alice_punishes_after_restart_bob_dead.rs index a272dbf1..b049d681 100644 --- a/swap/tests/alice_punishes_after_restart_bob_dead.rs +++ b/swap/tests/alice_punishes_after_restart_bob_dead.rs @@ -3,7 +3,7 @@ pub mod harness; use harness::alice_run_until::is_xmr_lock_transaction_sent; use harness::bob_run_until::is_btc_locked; use harness::FastPunishConfig; -use swap::protocol::alice::event_loop::FixedRate; +use swap::asb::FixedRate; use swap::protocol::alice::AliceState; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; diff --git a/swap/tests/alice_refunds_after_restart_bob_refunded.rs b/swap/tests/alice_refunds_after_restart_bob_refunded.rs index 2cf12605..1ec35e34 100644 --- a/swap/tests/alice_refunds_after_restart_bob_refunded.rs +++ b/swap/tests/alice_refunds_after_restart_bob_refunded.rs @@ -2,7 +2,7 @@ pub mod harness; use harness::alice_run_until::is_xmr_lock_transaction_sent; use harness::FastCancelConfig; -use swap::protocol::alice::event_loop::FixedRate; +use swap::asb::FixedRate; use swap::protocol::alice::AliceState; use swap::protocol::{alice, bob}; diff --git a/swap/tests/concurrent_bobs_after_xmr_lock_proof_sent.rs b/swap/tests/concurrent_bobs_after_xmr_lock_proof_sent.rs index e756fd9e..45e27819 100644 --- a/swap/tests/concurrent_bobs_after_xmr_lock_proof_sent.rs +++ b/swap/tests/concurrent_bobs_after_xmr_lock_proof_sent.rs @@ -2,7 +2,7 @@ pub mod harness; use harness::bob_run_until::is_xmr_locked; use harness::SlowCancelConfig; -use swap::protocol::alice::event_loop::FixedRate; +use swap::asb::FixedRate; use swap::protocol::alice::AliceState; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; diff --git a/swap/tests/concurrent_bobs_before_xmr_lock_proof_sent.rs b/swap/tests/concurrent_bobs_before_xmr_lock_proof_sent.rs index 420d3cbe..8c6bc034 100644 --- a/swap/tests/concurrent_bobs_before_xmr_lock_proof_sent.rs +++ b/swap/tests/concurrent_bobs_before_xmr_lock_proof_sent.rs @@ -2,7 +2,7 @@ pub mod harness; use harness::bob_run_until::is_btc_locked; use harness::SlowCancelConfig; -use swap::protocol::alice::event_loop::FixedRate; +use swap::asb::FixedRate; use swap::protocol::alice::AliceState; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; diff --git a/swap/tests/happy_path.rs b/swap/tests/happy_path.rs index 51e132fa..3814eb0e 100644 --- a/swap/tests/happy_path.rs +++ b/swap/tests/happy_path.rs @@ -1,7 +1,7 @@ pub mod harness; use harness::SlowCancelConfig; -use swap::protocol::alice::event_loop::FixedRate; +use swap::asb::FixedRate; use swap::protocol::{alice, bob}; use tokio::join; diff --git a/swap/tests/happy_path_restart_alice_after_xmr_locked.rs b/swap/tests/happy_path_restart_alice_after_xmr_locked.rs index 95a68861..22a50757 100644 --- a/swap/tests/happy_path_restart_alice_after_xmr_locked.rs +++ b/swap/tests/happy_path_restart_alice_after_xmr_locked.rs @@ -2,7 +2,7 @@ pub mod harness; use harness::alice_run_until::is_xmr_lock_transaction_sent; use harness::SlowCancelConfig; -use swap::protocol::alice::event_loop::FixedRate; +use swap::asb::FixedRate; use swap::protocol::alice::AliceState; use swap::protocol::{alice, bob}; diff --git a/swap/tests/happy_path_restart_bob_after_xmr_locked.rs b/swap/tests/happy_path_restart_bob_after_xmr_locked.rs index b3a3b1ed..f38c8953 100644 --- a/swap/tests/happy_path_restart_bob_after_xmr_locked.rs +++ b/swap/tests/happy_path_restart_bob_after_xmr_locked.rs @@ -2,7 +2,7 @@ pub mod harness; use harness::bob_run_until::is_xmr_locked; use harness::SlowCancelConfig; -use swap::protocol::alice::event_loop::FixedRate; +use swap::asb::FixedRate; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; diff --git a/swap/tests/happy_path_restart_bob_before_xmr_locked.rs b/swap/tests/happy_path_restart_bob_before_xmr_locked.rs index b3a3b1ed..f38c8953 100644 --- a/swap/tests/happy_path_restart_bob_before_xmr_locked.rs +++ b/swap/tests/happy_path_restart_bob_before_xmr_locked.rs @@ -2,7 +2,7 @@ pub mod harness; use harness::bob_run_until::is_xmr_locked; use harness::SlowCancelConfig; -use swap::protocol::alice::event_loop::FixedRate; +use swap::asb::FixedRate; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index 052b1d4b..4f692648 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -14,16 +14,16 @@ use std::fmt; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; +use swap::asb::FixedRate; use swap::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxPunish, TxRedeem, TxRefund}; use swap::database::Database; use swap::env::{Config, GetConfig}; use swap::network::swarm; -use swap::protocol::alice::event_loop::FixedRate; use swap::protocol::alice::{AliceState, Swap}; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; use swap::seed::Seed; -use swap::{bitcoin, env, monero}; +use swap::{asb, bitcoin, cli, env, monero}; use tempfile::tempdir; use testcontainers::clients::Cli; use testcontainers::{Container, Docker, RunArgs}; @@ -240,7 +240,7 @@ async fn start_alice( .unwrap(); swarm.listen_on(listen_address).unwrap(); - let (event_loop, swap_handle) = alice::EventLoop::new( + let (event_loop, swap_handle) = asb::EventLoop::new( swarm, env_config, bitcoin_wallet, @@ -399,7 +399,7 @@ struct BobParams { } impl BobParams { - pub async fn new_swap_from_db(&self, swap_id: Uuid) -> Result<(bob::Swap, bob::EventLoop)> { + pub async fn new_swap_from_db(&self, swap_id: Uuid) -> Result<(bob::Swap, cli::EventLoop)> { let (event_loop, handle) = self.new_eventloop(swap_id).await?; let db = Database::open(&self.db_path)?; @@ -419,7 +419,7 @@ impl BobParams { pub async fn new_swap( &self, btc_amount: bitcoin::Amount, - ) -> Result<(bob::Swap, bob::EventLoop)> { + ) -> Result<(bob::Swap, cli::EventLoop)> { let swap_id = Uuid::new_v4(); let (event_loop, handle) = self.new_eventloop(swap_id).await?; @@ -442,7 +442,7 @@ impl BobParams { pub async fn new_eventloop( &self, swap_id: Uuid, - ) -> Result<(bob::EventLoop, bob::EventLoopHandle)> { + ) -> Result<(cli::EventLoop, cli::EventLoopHandle)> { let tor_socks5_port = get_port() .expect("We don't care about Tor in the tests so we get a free port to disable it."); let mut swarm = swarm::cli( @@ -457,7 +457,7 @@ impl BobParams { .behaviour_mut() .add_address(self.alice_peer_id, self.alice_address.clone()); - bob::EventLoop::new(swap_id, swarm, self.alice_peer_id, self.env_config) + cli::EventLoop::new(swap_id, swarm, self.alice_peer_id, self.env_config) } } diff --git a/swap/tests/punish.rs b/swap/tests/punish.rs index 61af1f61..60eadfe3 100644 --- a/swap/tests/punish.rs +++ b/swap/tests/punish.rs @@ -2,7 +2,7 @@ pub mod harness; use harness::bob_run_until::is_btc_locked; use harness::FastPunishConfig; -use swap::protocol::alice::event_loop::FixedRate; +use swap::asb::FixedRate; use swap::protocol::bob::BobState; use swap::protocol::{alice, bob}; From c2c9e975efaca7591e2aeeb292ef45bd6c37c524 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Fri, 25 Jun 2021 16:10:25 +1000 Subject: [PATCH 3/6] Quote protocol asb/cli instead of alice/bob The quote protocol has no relation to the protocol roles but to the application hence the rename. --- swap/src/asb/behaviour.rs | 2 +- swap/src/cli/behaviour.rs | 2 +- swap/src/network/quote.rs | 14 ++++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/swap/src/asb/behaviour.rs b/swap/src/asb/behaviour.rs index 8f9ef6b8..fda8b496 100644 --- a/swap/src/asb/behaviour.rs +++ b/swap/src/asb/behaviour.rs @@ -94,7 +94,7 @@ where env_config: env::Config, ) -> Self { Self { - quote: quote::alice(), + quote: quote::asb(), swap_setup: alice::Behaviour::new( min_buy, max_buy, diff --git a/swap/src/cli/behaviour.rs b/swap/src/cli/behaviour.rs index be6596e3..cbd39b27 100644 --- a/swap/src/cli/behaviour.rs +++ b/swap/src/cli/behaviour.rs @@ -78,7 +78,7 @@ impl Behaviour { bitcoin_wallet: Arc, ) -> Self { Self { - quote: quote::bob(), + quote: quote::cli(), swap_setup: bob::Behaviour::new(env_config, bitcoin_wallet), transfer_proof: transfer_proof::bob(), encrypted_signature: encrypted_signature::bob(), diff --git a/swap/src/network/quote.rs b/swap/src/network/quote.rs index 1c78adbe..76c1ffc5 100644 --- a/swap/src/network/quote.rs +++ b/swap/src/network/quote.rs @@ -37,10 +37,11 @@ pub struct BidQuote { pub max_quantity: bitcoin::Amount, } -/// Constructs a new instance of the `quote` behaviour to be used by Alice. +/// Constructs a new instance of the `quote` behaviour to be used by the ASB. /// -/// Alice only supports inbound connections, i.e. handing out quotes. -pub fn alice() -> Behaviour { +/// The ASB is always listening and only supports inbound connections, i.e. +/// handing out quotes. +pub fn asb() -> Behaviour { Behaviour::new( JsonPullCodec::default(), vec![(BidQuoteProtocol, ProtocolSupport::Inbound)], @@ -48,10 +49,11 @@ pub fn alice() -> Behaviour { ) } -/// Constructs a new instance of the `quote` behaviour to be used by Bob. +/// Constructs a new instance of the `quote` behaviour to be used by the CLI. /// -/// Bob only supports outbound connections, i.e. requesting quotes. -pub fn bob() -> Behaviour { +/// The CLI is always dialing and only supports outbound connections, i.e. +/// requesting quotes. +pub fn cli() -> Behaviour { Behaviour::new( JsonPullCodec::default(), vec![(BidQuoteProtocol, ProtocolSupport::Outbound)], From fd18a074262f311274a5be5d1e3716650a572f91 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Mon, 28 Jun 2021 10:59:11 +1000 Subject: [PATCH 4/6] Flush and close `swap_setup` substreams When swapping on testnet we ran into a problem where the CLI started the swap after sending all messages successfully, but the ASB ran into a `connection closed` error at the end of the `swap_setup` and the swap state machine was never actually triggered. Flushing and closing the stream on both sides should ensure that we don't run into this problem and both parties gracefully exit the protocol. --- swap/src/network/swap_setup/alice.rs | 11 ++++++++++- swap/src/network/swap_setup/bob.rs | 5 ++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/swap/src/network/swap_setup/alice.rs b/swap/src/network/swap_setup/alice.rs index ab4a1a17..58696aef 100644 --- a/swap/src/network/swap_setup/alice.rs +++ b/swap/src/network/swap_setup/alice.rs @@ -8,7 +8,7 @@ use crate::protocol::{Message0, Message2, Message4}; use crate::{asb, bitcoin, env, monero}; use anyhow::{anyhow, Context, Result}; use futures::future::{BoxFuture, OptionFuture}; -use futures::FutureExt; +use futures::{AsyncWriteExt, FutureExt}; use libp2p::core::connection::ConnectionId; use libp2p::core::upgrade; use libp2p::swarm::{ @@ -383,6 +383,15 @@ where .receive(message4) .context("Failed to transition state2 -> state3 using message4")?; + substream + .flush() + .await + .context("Failed to flush substream after all messages were sent")?; + substream + .close() + .await + .context("Failed to close substream after all messages were sent")?; + Ok((swap_id, state3)) }); diff --git a/swap/src/network/swap_setup/bob.rs b/swap/src/network/swap_setup/bob.rs index 7f8110a1..2054974b 100644 --- a/swap/src/network/swap_setup/bob.rs +++ b/swap/src/network/swap_setup/bob.rs @@ -7,7 +7,7 @@ use crate::protocol::{Message1, Message3}; use crate::{bitcoin, cli, env, monero}; use anyhow::Result; use futures::future::{BoxFuture, OptionFuture}; -use futures::FutureExt; +use futures::{AsyncWriteExt, FutureExt}; use libp2p::core::connection::ConnectionId; use libp2p::core::upgrade; use libp2p::swarm::{ @@ -188,6 +188,9 @@ impl ProtocolsHandler for Handler { write_cbor_message(&mut substream, state2.next_message()).await?; + substream.flush().await?; + substream.close().await?; + Ok(state2) }); From cd14e22cdf879c7aaf287fcb25c5d7a31b947af0 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Mon, 28 Jun 2021 11:30:24 +1000 Subject: [PATCH 5/6] Longer timeout (120 secs) for `swap_setup` protocol Given that we combined the `spot_price` and the `execution_setup` messaging into one protocol we should allow the protocol to take longer than 60 seconds to complete. This is especially important for connections over Tor, where messaging can take significantly longer than over clearnet. I ran some tests with Tor and did not run into issues with the 60 seconds, but we get very close to the timeout, so we better make it more resilient by adding more time. --- swap/src/network/swap_setup/alice.rs | 2 +- swap/src/network/swap_setup/bob.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/swap/src/network/swap_setup/alice.rs b/swap/src/network/swap_setup/alice.rs index 58696aef..a2c92170 100644 --- a/swap/src/network/swap_setup/alice.rs +++ b/swap/src/network/swap_setup/alice.rs @@ -227,7 +227,7 @@ impl Handler { env_config, latest_rate, resume_only, - timeout: Duration::from_secs(60), + timeout: Duration::from_secs(120), keep_alive: KeepAlive::Until(Instant::now() + Duration::from_secs(5)), } } diff --git a/swap/src/network/swap_setup/bob.rs b/swap/src/network/swap_setup/bob.rs index 2054974b..225fd7ea 100644 --- a/swap/src/network/swap_setup/bob.rs +++ b/swap/src/network/swap_setup/bob.rs @@ -109,7 +109,7 @@ impl Handler { Self { env_config, outbound_stream: OptionFuture::from(None), - timeout: Duration::from_secs(60), + timeout: Duration::from_secs(120), new_swaps: VecDeque::default(), bitcoin_wallet, keep_alive: KeepAlive::Until(Instant::now() + Duration::from_secs(5)), From dc89a837e70681839b3dcf0e4f912d22fc759ea8 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Mon, 28 Jun 2021 11:53:46 +1000 Subject: [PATCH 6/6] Do not actively set `KeepAlice::No` for `swap_setup` Closing the connection upon completing the `swap_setup` protocol caused problems on the ASB side, because the CLI would close the connection before the last message was properly processed. This would result in swaps going into execution on the CLI side, but not on the ASB side. The CLI ensures an open connection to the ASB over the complete course of a swap. So it does not make much sense to allow a protocol to close the connection (the CLI would immediately redial). For the Alice we set the initial `KeepAlive` to `10` seconds because Bob is expected to request a spot price in reasonable time after opening a connection on the protocol. Since Tor connections can take some time we set 10 seconds fow now for resilience. --- swap/src/network/swap_setup/alice.rs | 3 +-- swap/src/network/swap_setup/bob.rs | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/swap/src/network/swap_setup/alice.rs b/swap/src/network/swap_setup/alice.rs index a2c92170..6930a6f3 100644 --- a/swap/src/network/swap_setup/alice.rs +++ b/swap/src/network/swap_setup/alice.rs @@ -228,7 +228,7 @@ impl Handler { latest_rate, resume_only, timeout: Duration::from_secs(120), - keep_alive: KeepAlive::Until(Instant::now() + Duration::from_secs(5)), + keep_alive: KeepAlive::Until(Instant::now() + Duration::from_secs(10)), } } } @@ -445,7 +445,6 @@ where } if let Some(result) = futures::ready!(self.inbound_stream.poll_unpin(cx)) { - self.keep_alive = KeepAlive::No; self.inbound_stream = OptionFuture::from(None); return Poll::Ready(ProtocolsHandlerEvent::Custom(HandlerOutEvent::Completed( result, diff --git a/swap/src/network/swap_setup/bob.rs b/swap/src/network/swap_setup/bob.rs index 225fd7ea..1a42c0ff 100644 --- a/swap/src/network/swap_setup/bob.rs +++ b/swap/src/network/swap_setup/bob.rs @@ -19,7 +19,7 @@ use libp2p::{Multiaddr, PeerId}; use std::collections::VecDeque; use std::sync::Arc; use std::task::{Context, Poll}; -use std::time::{Duration, Instant}; +use std::time::Duration; use uuid::Uuid; use void::Void; @@ -112,7 +112,7 @@ impl Handler { timeout: Duration::from_secs(120), new_swaps: VecDeque::default(), bitcoin_wallet, - keep_alive: KeepAlive::Until(Instant::now() + Duration::from_secs(5)), + keep_alive: KeepAlive::Yes, } } } @@ -240,7 +240,6 @@ impl ProtocolsHandler for Handler { } if let Some(result) = futures::ready!(self.outbound_stream.poll_unpin(cx)) { - self.keep_alive = KeepAlive::No; self.outbound_stream = OptionFuture::from(None); return Poll::Ready(ProtocolsHandlerEvent::Custom(Completed(result))); }