diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a46ef1f9..7a2f10f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -127,16 +127,10 @@ jobs: matrix: test_name: [ happy_path, - happy_path_restart_alice, - happy_path_restart_bob_after_comm, - happy_path_restart_bob_after_lock_proof_received, happy_path_restart_bob_before_comm, - punish, - refund_restart_alice_cancelled, - refund_restart_alice, bob_refunds_using_cancel_and_refund_command, - bob_refunds_using_cancel_and_refund_command_timelock_not_exired, - bob_refunds_using_cancel_and_refund_command_timelock_not_exired_force, + bob_refunds_using_cancel_and_refund_command_timelock_not_expired, + bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force, ] runs-on: ubuntu-latest steps: diff --git a/Cargo.lock b/Cargo.lock index 8a3e61d6..8e218318 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2885,10 +2885,11 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.9.0" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5c739ba050709eae138f053356d27ff818d71fe54ce5a8d9f4c7a660bfb6684" +checksum = "e3e5a94e2006dd60c603d8481c65b665b4b6694f78d23e15869ad10eb883e36e" dependencies = [ + "arrayvec", "num-traits", "serde", ] diff --git a/bors.toml b/bors.toml index 8b82cb8c..fcb4f9eb 100644 --- a/bors.toml +++ b/bors.toml @@ -3,11 +3,8 @@ status = [ "build_test (x86_64-unknown-linux-gnu)", "build_test (x86_64-apple-darwin)", "docker_tests (happy_path)", - "docker_tests (happy_path_restart_alice)", - "docker_tests (happy_path_restart_bob_after_comm)", - "docker_tests (happy_path_restart_bob_after_lock_proof_received)", "docker_tests (happy_path_restart_bob_before_comm)", - "docker_tests (punish)", - "docker_tests (refund_restart_alice_cancelled)", - "docker_tests (refund_restart_alice)", + "docker_tests (bob_refunds_using_cancel_and_refund_command)", + "docker_tests (bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force)", + "docker_tests (bob_refunds_using_cancel_and_refund_command_timelock_not_expired)" ] diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 85609dcc..34bf5ecb 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -5,6 +5,12 @@ authors = ["CoBloX developers "] edition = "2018" description = "XMR/BTC trustless atomic swaps." +[[bin]] +name = "cli" + +[lib] +name = "swap" + [dependencies] anyhow = "1" async-recursion = "0.3.1" @@ -34,7 +40,7 @@ pem = "0.8" prettytable-rs = "0.8" rand = "0.7" reqwest = { version = "0.11", default-features = false } -rust_decimal = "1.8" +rust_decimal = "1.10" serde = { version = "1", features = ["derive"] } serde_cbor = "0.11" serde_derive = "1.0" diff --git a/swap/src/main.rs b/swap/src/bin/cli.rs similarity index 79% rename from swap/src/main.rs rename to swap/src/bin/cli.rs index bd7ec818..03176ae3 100644 --- a/swap/src/main.rs +++ b/swap/src/bin/cli.rs @@ -12,41 +12,34 @@ #![forbid(unsafe_code)] #![allow(non_snake_case)] -use crate::{ +use anyhow::{Context, Result}; +use log::LevelFilter; +use prettytable::{row, Table}; +use std::{path::PathBuf, sync::Arc}; +use structopt::StructOpt; +use swap::{ + bitcoin, cli::{Cancel, Command, Options, Refund, Resume}, + config, config::{ initial_setup, query_user_for_initial_testnet_config, read_config, ConfigNotInitialized, }, + database::Database, + execution_params, execution_params::GetExecutionParams, + fs::{default_config_path, default_data_dir}, + monero, monero::{CreateWallet, OpenWallet}, - protocol::bob::cancel::CancelError, + protocol::{ + bob, + bob::{cancel::CancelError, Builder}, + SwapAmounts, + }, + trace::init_tracing, }; -use anyhow::{Context, Result}; -use database::Database; -use fs::{default_config_path, default_data_dir}; -use log::LevelFilter; -use prettytable::{row, Table}; -use protocol::{alice, bob, bob::Builder, SwapAmounts}; -use std::{path::PathBuf, sync::Arc}; -use structopt::StructOpt; -use trace::init_tracing; use tracing::{error, info, warn}; use uuid::Uuid; -pub mod bitcoin; -pub mod config; -pub mod database; -pub mod execution_params; -pub mod monero; -pub mod network; -pub mod protocol; -pub mod seed; -pub mod trace; - -mod cli; -mod fs; -mod serde_peer_id; - #[macro_use] extern crate prettytable; @@ -70,7 +63,7 @@ async fn main() -> Result<()> { ); let db_path = data_dir.join("database"); - let seed = config::seed::Seed::from_file_or_generate(&data_dir) + let seed = config::Seed::from_file_or_generate(&data_dir) .expect("Could not retrieve/initialize seed") .into(); @@ -80,42 +73,6 @@ async fn main() -> Result<()> { let execution_params = execution_params::Testnet::get_execution_params(); match opt.cmd { - Command::SellXmr { - listen_addr, - send_monero, - receive_bitcoin, - config, - } => { - let swap_amounts = SwapAmounts { - xmr: send_monero, - btc: receive_bitcoin, - }; - - let (bitcoin_wallet, monero_wallet) = - init_wallets(config.path, bitcoin_network, monero_network).await?; - - let swap_id = Uuid::new_v4(); - - info!( - "Swap sending {} and receiving {} started with ID {}", - send_monero, receive_bitcoin, swap_id - ); - - let alice_factory = alice::Builder::new( - seed, - execution_params, - swap_id, - Arc::new(bitcoin_wallet), - Arc::new(monero_wallet), - db_path, - listen_addr, - ); - let (swap, mut event_loop) = - alice_factory.with_init_params(swap_amounts).build().await?; - - tokio::spawn(async move { event_loop.run().await }); - alice::run(swap).await?; - } Command::BuyXmr { alice_peer_id, alice_addr, @@ -167,28 +124,6 @@ async fn main() -> Result<()> { // Print the table to stdout table.printstd(); } - Command::Resume(Resume::SellXmr { - swap_id, - listen_addr, - config, - }) => { - let (bitcoin_wallet, monero_wallet) = - init_wallets(config.path, bitcoin_network, monero_network).await?; - - let alice_factory = alice::Builder::new( - seed, - execution_params, - swap_id, - Arc::new(bitcoin_wallet), - Arc::new(monero_wallet), - db_path, - listen_addr, - ); - let (swap, mut event_loop) = alice_factory.build().await?; - - tokio::spawn(async move { event_loop.run().await }); - alice::run(swap).await?; - } Command::Resume(Resume::BuyXmr { swap_id, alice_peer_id, diff --git a/swap/src/cli.rs b/swap/src/cli.rs index 16f80f2b..9a1d91a5 100644 --- a/swap/src/cli.rs +++ b/swap/src/cli.rs @@ -19,19 +19,6 @@ pub struct Options { #[derive(structopt::StructOpt, Debug)] #[structopt(name = "xmr_btc-swap", about = "XMR BTC atomic swap")] pub enum Command { - SellXmr { - #[structopt(long = "p2p-address", default_value = "/ip4/0.0.0.0/tcp/9876")] - listen_addr: Multiaddr, - - #[structopt(long = "send-xmr", help = "Monero amount as floating point nr without denomination (e.g. 125.1)", parse(try_from_str = parse_xmr))] - send_monero: monero::Amount, - - #[structopt(long = "receive-btc", help = "Bitcoin amount as floating point nr without denomination (e.g. 1.25)", parse(try_from_str = parse_btc))] - receive_bitcoin: bitcoin::Amount, - - #[structopt(flatten)] - config: Config, - }, BuyXmr { #[structopt(long = "connect-peer-id")] alice_peer_id: PeerId, @@ -56,16 +43,6 @@ pub enum Command { #[derive(structopt::StructOpt, Debug)] pub enum Resume { - SellXmr { - #[structopt(long = "swap-id")] - swap_id: Uuid, - - #[structopt(long = "listen-address", default_value = "/ip4/127.0.0.1/tcp/9876")] - listen_addr: Multiaddr, - - #[structopt(flatten)] - config: Config, - }, BuyXmr { #[structopt(long = "swap-id")] swap_id: Uuid, diff --git a/swap/src/config.rs b/swap/src/config.rs index 11049581..f9f56431 100644 --- a/swap/src/config.rs +++ b/swap/src/config.rs @@ -13,6 +13,8 @@ use url::Url; pub mod seed; +pub use seed::Seed; + const DEFAULT_BITCOIND_TESTNET_URL: &str = "http://127.0.0.1:18332"; const DEFAULT_MONERO_WALLET_RPC_TESTNET_URL: &str = "http://127.0.0.1:38083/json_rpc"; diff --git a/swap/src/database/alice.rs b/swap/src/database/alice.rs index 07e76501..3eadc70d 100644 --- a/swap/src/database/alice.rs +++ b/swap/src/database/alice.rs @@ -14,10 +14,6 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub enum Alice { Started { - amounts: SwapAmounts, - state0: alice::State0, - }, - Negotiated { state3: alice::State3, #[serde(with = "crate::serde_peer_id")] bob_peer_id: PeerId, @@ -54,11 +50,11 @@ pub enum AliceEndState { impl From<&AliceState> for Alice { fn from(alice_state: &AliceState) -> Self { match alice_state { - AliceState::Negotiated { + AliceState::Started { state3, bob_peer_id, .. - } => Alice::Negotiated { + } => Alice::Started { state3: state3.as_ref().clone(), bob_peer_id: *bob_peer_id, }, @@ -76,7 +72,7 @@ impl From<&AliceState> for Alice { encrypted_signature, } => Alice::EncSigLearned { state3: state3.as_ref().clone(), - encrypted_signature: encrypted_signature.clone(), + encrypted_signature: *encrypted_signature.clone(), }, AliceState::BtcRedeemed => Alice::Done(AliceEndState::BtcRedeemed), AliceState::BtcCancelled { state3, .. } => Alice::BtcCancelled(state3.as_ref().clone()), @@ -93,10 +89,6 @@ impl From<&AliceState> for Alice { } AliceState::BtcPunished => Alice::Done(AliceEndState::BtcPunished), AliceState::SafelyAborted => Alice::Done(AliceEndState::SafelyAborted), - AliceState::Started { amounts, state0 } => Alice::Started { - amounts: *amounts, - state0: state0.clone(), - }, } } } @@ -104,11 +96,10 @@ impl From<&AliceState> for Alice { impl From for AliceState { fn from(db_state: Alice) -> Self { match db_state { - Alice::Started { amounts, state0 } => AliceState::Started { amounts, state0 }, - Alice::Negotiated { + Alice::Started { state3, bob_peer_id, - } => AliceState::Negotiated { + } => AliceState::Started { bob_peer_id, amounts: SwapAmounts { btc: state3.btc, @@ -135,7 +126,7 @@ impl From for AliceState { encrypted_signature, } => AliceState::EncSigLearned { state3: Box::new(state), - encrypted_signature, + encrypted_signature: Box::new(encrypted_signature), }, Alice::CancelTimelockExpired(state3) => AliceState::CancelTimelockExpired { state3: Box::new(state3), @@ -150,7 +141,7 @@ impl From for AliceState { AliceState::BtcCancelled { state3: Box::new(state), - tx_cancel, + tx_cancel: Box::new(tx_cancel), } } Alice::BtcPunishable(state3) => { @@ -186,7 +177,6 @@ impl Display for Alice { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Alice::Started { .. } => write!(f, "Started"), - Alice::Negotiated { .. } => f.write_str("Negotiated"), Alice::BtcLocked { .. } => f.write_str("Bitcoin locked"), Alice::XmrLocked(_) => f.write_str("Monero locked"), Alice::CancelTimelockExpired(_) => f.write_str("Cancel timelock is expired"), diff --git a/swap/src/lib.rs b/swap/src/lib.rs index 72c81012..f042cddc 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -17,8 +17,11 @@ )] pub mod bitcoin; +pub mod cli; +pub mod config; pub mod database; pub mod execution_params; +pub mod fs; pub mod monero; pub mod network; pub mod protocol; diff --git a/swap/src/monero.rs b/swap/src/monero.rs index 9d7bdec9..bc40b4a0 100644 --- a/swap/src/monero.rs +++ b/swap/src/monero.rs @@ -15,6 +15,7 @@ use rust_decimal::{ }; use serde::{Deserialize, Serialize}; use std::{ + convert::TryFrom, fmt::Display, ops::{Add, Mul, Sub}, str::FromStr, @@ -88,13 +89,22 @@ impl Amount { self.0 } + pub fn from_monero(amount: f64) -> Result { + let decimal = Decimal::try_from(amount)?; + Self::from_decimal(decimal) + } + pub fn parse_monero(amount: &str) -> Result { let decimal = Decimal::from_str(amount)?; + Self::from_decimal(decimal) + } + + fn from_decimal(amount: Decimal) -> Result { let piconeros_dec = - decimal.mul(Decimal::from_u64(PICONERO_OFFSET).expect("constant to fit into u64")); + amount.mul(Decimal::from_u64(PICONERO_OFFSET).expect("constant to fit into u64")); let piconeros = piconeros_dec .to_u64() - .ok_or_else(|| OverflowError(amount.to_owned()))?; + .ok_or_else(|| OverflowError(amount.to_string()))?; Ok(Amount(piconeros)) } } diff --git a/swap/src/network/transport.rs b/swap/src/network/transport.rs index a8ba1be3..09bd8e9b 100644 --- a/swap/src/network/transport.rs +++ b/swap/src/network/transport.rs @@ -18,10 +18,10 @@ use libp2p::{ /// - DNS name resolution /// - authentication via noise /// - multiplexing via yamux or mplex -pub fn build(id_keys: identity::Keypair) -> Result { +pub fn build(id_keys: &identity::Keypair) -> Result { use libp2p::tcp::TokioTcpConfig; - let dh_keys = noise::Keypair::::new().into_authentic(&id_keys)?; + let dh_keys = noise::Keypair::::new().into_authentic(id_keys)?; let noise = NoiseConfig::xx(dh_keys).into_authenticated(); let tcp = TokioTcpConfig::new().nodelay(true); diff --git a/swap/src/protocol/alice.rs b/swap/src/protocol/alice.rs index 9f741412..2c9d86f3 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -1,28 +1,16 @@ //! Run an XMR/BTC swap in the role of Alice. //! Alice holds XMR and wishes receive BTC. use crate::{ - bitcoin, database, - database::Database, - execution_params::ExecutionParams, - monero, - network::{ - peer_tracker::{self, PeerTracker}, - transport::build, - Seed as NetworkSeed, - }, - protocol::{bob::EncryptedSignature, SwapAmounts}, - seed::Seed, + bitcoin, database, database::Database, execution_params::ExecutionParams, monero, + protocol::SwapAmounts, }; -use anyhow::{bail, Error, Result}; -use libp2p::{ - core::Multiaddr, identity::Keypair, request_response::ResponseChannel, NetworkBehaviour, PeerId, -}; -use rand::rngs::OsRng; -use std::{path::PathBuf, sync::Arc}; -use tracing::{debug, info}; +use anyhow::{bail, Result}; +use libp2p::{core::Multiaddr, PeerId}; +use std::sync::Arc; use uuid::Uuid; pub use self::{ + behaviour::{Behaviour, OutEvent}, event_loop::{EventLoop, EventLoopHandle}, execution_setup::Message1, state::*, @@ -30,9 +18,9 @@ pub use self::{ swap_response::*, transfer_proof::TransferProof, }; -use crate::protocol::bob::SwapRequest; pub use execution_setup::Message3; +mod behaviour; mod encrypted_signature; pub mod event_loop; mod execution_setup; @@ -49,16 +37,15 @@ pub struct Swap { pub monero_wallet: Arc, pub execution_params: ExecutionParams, pub swap_id: Uuid, - pub db: Database, + pub db: Arc, } pub struct Builder { swap_id: Uuid, - identity: Keypair, peer_id: PeerId, - db_path: PathBuf, + db: Arc, execution_params: ExecutionParams, - + event_loop_handle: EventLoopHandle, listen_address: Multiaddr, bitcoin_wallet: Arc, @@ -69,29 +56,31 @@ pub struct Builder { enum InitParams { None, - New { swap_amounts: SwapAmounts }, + New { + swap_amounts: SwapAmounts, + bob_peer_id: PeerId, + state3: Box, + }, } impl Builder { + #[allow(clippy::too_many_arguments)] pub fn new( - seed: Seed, + self_peer_id: PeerId, execution_params: ExecutionParams, swap_id: Uuid, bitcoin_wallet: Arc, monero_wallet: Arc, - db_path: PathBuf, + db: Arc, listen_address: Multiaddr, + event_loop_handle: EventLoopHandle, ) -> Self { - let network_seed = NetworkSeed::new(seed); - let identity = network_seed.derive_libp2p_identity(); - let peer_id = PeerId::from(identity.public()); - Self { swap_id, - identity, - peer_id, - db_path, + peer_id: self_peer_id, + db, execution_params, + event_loop_handle, listen_address, bitcoin_wallet, monero_wallet, @@ -99,43 +88,48 @@ impl Builder { } } - pub fn with_init_params(self, swap_amounts: SwapAmounts) -> Self { + pub fn with_init_params( + self, + swap_amounts: SwapAmounts, + bob_peer_id: PeerId, + state3: State3, + ) -> Self { Self { - init_params: InitParams::New { swap_amounts }, + init_params: InitParams::New { + swap_amounts, + bob_peer_id, + state3: Box::new(state3), + }, ..self } } - pub async fn build(self) -> Result<(Swap, EventLoop)> { + pub async fn build(self) -> Result { match self.init_params { - InitParams::New { swap_amounts } => { - let initial_state = self - .make_initial_state(swap_amounts.btc, swap_amounts.xmr) - .await?; + InitParams::New { + swap_amounts, + bob_peer_id, + ref state3, + } => { + let initial_state = AliceState::Started { + amounts: swap_amounts, + state3: state3.clone(), + bob_peer_id, + }; - let (event_loop, event_loop_handle) = self.init_event_loop()?; - - let db = Database::open(self.db_path.as_path())?; - - Ok(( - Swap { - event_loop_handle, - bitcoin_wallet: self.bitcoin_wallet, - monero_wallet: self.monero_wallet, - execution_params: self.execution_params, - db, - state: initial_state, - swap_id: self.swap_id, - }, - event_loop, - )) + Ok(Swap { + event_loop_handle: self.event_loop_handle, + bitcoin_wallet: self.bitcoin_wallet, + monero_wallet: self.monero_wallet, + execution_params: self.execution_params, + db: self.db, + state: initial_state, + swap_id: self.swap_id, + }) } InitParams::None => { - // reopen the existing database - let db = Database::open(self.db_path.as_path())?; - let resume_state = - if let database::Swap::Alice(state) = db.get_state(self.swap_id)? { + if let database::Swap::Alice(state) = self.db.get_state(self.swap_id)? { state.into() } else { bail!( @@ -144,20 +138,15 @@ impl Builder { ) }; - let (event_loop, event_loop_handle) = self.init_event_loop()?; - - Ok(( - Swap { - state: resume_state, - event_loop_handle, - bitcoin_wallet: self.bitcoin_wallet, - monero_wallet: self.monero_wallet, - execution_params: self.execution_params, - swap_id: self.swap_id, - db, - }, - event_loop, - )) + Ok(Swap { + state: resume_state, + event_loop_handle: self.event_loop_handle, + bitcoin_wallet: self.bitcoin_wallet, + monero_wallet: self.monero_wallet, + execution_params: self.execution_params, + swap_id: self.swap_id, + db: self.db, + }) } } } @@ -169,154 +158,4 @@ impl Builder { pub fn listen_address(&self) -> Multiaddr { self.listen_address.clone() } - - async fn make_initial_state( - &self, - btc_to_swap: bitcoin::Amount, - xmr_to_swap: monero::Amount, - ) -> Result { - let rng = &mut OsRng; - - let amounts = SwapAmounts { - btc: btc_to_swap, - xmr: xmr_to_swap, - }; - - let a = bitcoin::SecretKey::new_random(rng); - let s_a = cross_curve_dleq::Scalar::random(rng); - let v_a = monero::PrivateViewKey::new_random(rng); - let redeem_address = self.bitcoin_wallet.new_address().await?; - let punish_address = redeem_address.clone(); - let state0 = State0::new( - a, - s_a, - v_a, - amounts.btc, - amounts.xmr, - self.execution_params.bitcoin_cancel_timelock, - self.execution_params.bitcoin_punish_timelock, - redeem_address, - punish_address, - rng, - ); - - Ok(AliceState::Started { amounts, state0 }) - } - - fn init_event_loop(&self) -> Result<(EventLoop, EventLoopHandle)> { - let alice_behaviour = Behaviour::default(); - let alice_transport = build(self.identity.clone())?; - EventLoop::new( - alice_transport, - alice_behaviour, - self.listen_address(), - self.peer_id, - ) - } -} - -#[derive(Debug)] -pub enum OutEvent { - ConnectionEstablished(PeerId), - SwapRequest { - msg: SwapRequest, - channel: ResponseChannel, - }, - ExecutionSetupDone(Result>), - TransferProofAcknowledged, - EncryptedSignature { - msg: Box, - channel: ResponseChannel<()>, - }, - ResponseSent, // Same variant is used for all messages as no processing is done - Failure(Error), -} - -impl From for OutEvent { - fn from(event: peer_tracker::OutEvent) -> Self { - match event { - peer_tracker::OutEvent::ConnectionEstablished(id) => { - OutEvent::ConnectionEstablished(id) - } - } - } -} - -impl From for OutEvent { - fn from(event: swap_response::OutEvent) -> Self { - use swap_response::OutEvent::*; - match event { - MsgReceived { msg, channel } => OutEvent::SwapRequest { msg, channel }, - ResponseSent => OutEvent::ResponseSent, - Failure(err) => OutEvent::Failure(err.context("Swap Request/Response failure")), - } - } -} - -impl From for OutEvent { - fn from(event: execution_setup::OutEvent) -> Self { - match event { - execution_setup::OutEvent::Done(res) => OutEvent::ExecutionSetupDone(res.map(Box::new)), - } - } -} - -impl From for OutEvent { - fn from(event: transfer_proof::OutEvent) -> Self { - use transfer_proof::OutEvent::*; - match event { - Acknowledged => OutEvent::TransferProofAcknowledged, - Failure(err) => OutEvent::Failure(err.context("Failure with Transfer Proof")), - } - } -} - -impl From for OutEvent { - fn from(event: encrypted_signature::OutEvent) -> Self { - use encrypted_signature::OutEvent::*; - match event { - MsgReceived { msg, channel } => OutEvent::EncryptedSignature { - msg: Box::new(msg), - channel, - }, - AckSent => OutEvent::ResponseSent, - Failure(err) => OutEvent::Failure(err.context("Failure with Encrypted Signature")), - } - } -} - -/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Alice. -#[derive(NetworkBehaviour, Default)] -#[behaviour(out_event = "OutEvent", event_process = false)] -#[allow(missing_debug_implementations)] -pub struct Behaviour { - pt: PeerTracker, - amounts: swap_response::Behaviour, - execution_setup: execution_setup::Behaviour, - transfer_proof: transfer_proof::Behaviour, - encrypted_signature: encrypted_signature::Behaviour, -} - -impl Behaviour { - /// Alice always sends her messages as a response to a request from Bob. - pub fn send_swap_response( - &mut self, - channel: ResponseChannel, - swap_response: SwapResponse, - ) -> Result<()> { - self.amounts.send(channel, swap_response)?; - info!("Sent swap response"); - Ok(()) - } - - pub fn start_execution_setup(&mut self, bob_peer_id: PeerId, state0: State0) { - self.execution_setup.run(bob_peer_id, state0); - info!("Start execution setup with {}", bob_peer_id); - } - - /// Send Transfer Proof to Bob. - pub fn send_transfer_proof(&mut self, bob: PeerId, msg: TransferProof) { - self.transfer_proof.send(bob, msg); - debug!("Sent Transfer Proof"); - } } diff --git a/swap/src/protocol/alice/behaviour.rs b/swap/src/protocol/alice/behaviour.rs new file mode 100644 index 00000000..4e978f35 --- /dev/null +++ b/swap/src/protocol/alice/behaviour.rs @@ -0,0 +1,143 @@ +use crate::{ + network::{peer_tracker, peer_tracker::PeerTracker}, + protocol::{ + alice::{ + encrypted_signature, execution_setup, swap_response, transfer_proof, State0, State3, + SwapResponse, TransferProof, + }, + bob::{EncryptedSignature, SwapRequest}, + }, +}; +use anyhow::{Error, Result}; +use libp2p::{request_response::ResponseChannel, NetworkBehaviour, PeerId}; +use tracing::{debug, info}; + +#[derive(Debug)] +pub enum OutEvent { + ConnectionEstablished(PeerId), + SwapRequest { + msg: SwapRequest, + channel: ResponseChannel, + bob_peer_id: PeerId, + }, + ExecutionSetupDone { + bob_peer_id: PeerId, + state3: Box, + }, + TransferProofAcknowledged, + EncryptedSignature { + msg: Box, + channel: ResponseChannel<()>, + }, + ResponseSent, // Same variant is used for all messages as no processing is done + Failure(Error), +} + +impl From for OutEvent { + fn from(event: peer_tracker::OutEvent) -> Self { + match event { + peer_tracker::OutEvent::ConnectionEstablished(id) => { + OutEvent::ConnectionEstablished(id) + } + } + } +} + +impl From for OutEvent { + fn from(event: swap_response::OutEvent) -> Self { + use crate::protocol::alice::swap_response::OutEvent::*; + match event { + MsgReceived { + msg, + channel, + bob_peer_id, + } => OutEvent::SwapRequest { + msg, + channel, + bob_peer_id, + }, + ResponseSent => OutEvent::ResponseSent, + Failure(err) => OutEvent::Failure(err.context("Swap Request/Response failure")), + } + } +} + +impl From for OutEvent { + fn from(event: execution_setup::OutEvent) -> Self { + use crate::protocol::alice::execution_setup::OutEvent::*; + match event { + Done { + bob_peer_id, + state3, + } => OutEvent::ExecutionSetupDone { + bob_peer_id, + state3: Box::new(state3), + }, + Failure(err) => OutEvent::Failure(err), + } + } +} + +impl From for OutEvent { + fn from(event: transfer_proof::OutEvent) -> Self { + use crate::protocol::alice::transfer_proof::OutEvent::*; + match event { + Acknowledged => OutEvent::TransferProofAcknowledged, + Failure(err) => OutEvent::Failure(err.context("Failure with Transfer Proof")), + } + } +} + +impl From for OutEvent { + fn from(event: encrypted_signature::OutEvent) -> Self { + use crate::protocol::alice::encrypted_signature::OutEvent::*; + match event { + MsgReceived { msg, channel } => OutEvent::EncryptedSignature { + msg: Box::new(msg), + channel, + }, + AckSent => OutEvent::ResponseSent, + Failure(err) => OutEvent::Failure(err.context("Failure with Encrypted Signature")), + } + } +} + +/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Alice. +#[derive(NetworkBehaviour, Default)] +#[behaviour(out_event = "OutEvent", event_process = false)] +#[allow(missing_debug_implementations)] +pub struct Behaviour { + pt: PeerTracker, + swap_response: swap_response::Behaviour, + execution_setup: execution_setup::Behaviour, + transfer_proof: transfer_proof::Behaviour, + encrypted_signature: encrypted_signature::Behaviour, +} + +impl Behaviour { + /// Alice always sends her messages as a response to a request from Bob. + pub fn send_swap_response( + &mut self, + channel: ResponseChannel, + swap_response: SwapResponse, + ) -> anyhow::Result<()> { + self.swap_response.send(channel, swap_response)?; + info!("Sent swap response"); + Ok(()) + } + + pub fn start_execution_setup(&mut self, bob_peer_id: PeerId, state0: State0) { + self.execution_setup.run(bob_peer_id, state0); + info!("Start execution setup with {}", bob_peer_id); + } + + /// Send Transfer Proof to Bob. + pub fn send_transfer_proof(&mut self, bob: PeerId, msg: TransferProof) { + self.transfer_proof.send(bob, msg); + debug!("Sent Transfer Proof"); + } + + pub fn send_encrypted_signature_ack(&mut self, channel: ResponseChannel<()>) -> Result<()> { + self.encrypted_signature.send_ack(channel) + } +} diff --git a/swap/src/protocol/alice/event_loop.rs b/swap/src/protocol/alice/event_loop.rs index b2b3873c..02bd17b3 100644 --- a/swap/src/protocol/alice/event_loop.rs +++ b/swap/src/protocol/alice/event_loop.rs @@ -1,121 +1,77 @@ use crate::{ + bitcoin, + database::Database, execution_params::ExecutionParams, - network::{transport::SwapTransport, TokioExecutor}, + monero, network, + network::{transport, TokioExecutor}, protocol::{ - alice::{Behaviour, OutEvent, State0, State3, SwapResponse, TransferProof}, + alice, + alice::{Behaviour, Builder, OutEvent, State0, State3, SwapResponse, TransferProof}, bob::{EncryptedSignature, SwapRequest}, + SwapAmounts, }, + seed::Seed, }; use anyhow::{anyhow, Context, Result}; use libp2p::{ core::Multiaddr, futures::FutureExt, request_response::ResponseChannel, PeerId, Swarm, }; -use tokio::{ - sync::mpsc::{Receiver, Sender}, - time::timeout, -}; -use tracing::{error, trace}; +use rand::rngs::OsRng; +use std::{collections::HashMap, sync::Arc}; +use tokio::sync::{broadcast, mpsc}; +use tracing::{debug, error, trace}; +use uuid::Uuid; + +// TODO: Use dynamic +const RATE: u32 = 100; #[allow(missing_debug_implementations)] -pub struct Channels { - sender: Sender, - receiver: Receiver, +pub struct MpscChannels { + sender: mpsc::Sender, + receiver: mpsc::Receiver, } -impl Channels { - pub fn new() -> Channels { - let (sender, receiver) = tokio::sync::mpsc::channel(100); - Channels { sender, receiver } +impl Default for MpscChannels { + fn default() -> Self { + let (sender, receiver) = mpsc::channel(100); + MpscChannels { sender, receiver } } } -impl Default for Channels { +#[allow(missing_debug_implementations)] +pub struct BroadcastChannels +where + T: Clone, +{ + sender: broadcast::Sender, +} + +impl Default for BroadcastChannels +where + T: Clone, +{ fn default() -> Self { - Self::new() + let (sender, _receiver) = broadcast::channel(100); + BroadcastChannels { sender } } } #[derive(Debug)] pub struct EventLoopHandle { - done_execution_setup: Receiver>, - recv_encrypted_signature: Receiver, - recv_swap_request: Receiver<(SwapRequest, ResponseChannel)>, - conn_established: Receiver, - send_swap_response: Sender<(ResponseChannel, SwapResponse)>, - start_execution_setup: Sender<(PeerId, State0)>, - send_transfer_proof: Sender<(PeerId, TransferProof)>, - recv_transfer_proof_ack: Receiver<()>, + recv_encrypted_signature: broadcast::Receiver, + send_transfer_proof: mpsc::Sender<(PeerId, TransferProof)>, } impl EventLoopHandle { - pub async fn recv_conn_established(&mut self) -> Result { - self.conn_established - .recv() - .await - .ok_or_else(|| anyhow!("Failed to receive connection established from Bob")) - } - - pub async fn execution_setup(&mut self, bob_peer_id: PeerId, state0: State0) -> Result { - let _ = self - .start_execution_setup - .send((bob_peer_id, state0)) - .await?; - - self.done_execution_setup - .recv() - .await - .ok_or_else(|| anyhow!("Failed to setup execution with Bob"))? - } - pub async fn recv_encrypted_signature(&mut self) -> Result { self.recv_encrypted_signature .recv() .await - .ok_or_else(|| anyhow!("Failed to receive Bitcoin encrypted signature from Bob")) + .context("Failed to receive Bitcoin encrypted signature from Bob") } - - pub async fn recv_swap_request( - &mut self, - ) -> Result<(SwapRequest, ResponseChannel)> { - self.recv_swap_request - .recv() - .await - .ok_or_else(|| anyhow!("Failed to receive amounts request from Bob")) - } - - pub async fn send_swap_response( - &mut self, - channel: ResponseChannel, - swap_response: SwapResponse, - ) -> Result<()> { - let _ = self - .send_swap_response - .send((channel, swap_response)) - .await?; - Ok(()) - } - - pub async fn send_transfer_proof( - &mut self, - bob: PeerId, - msg: TransferProof, - execution_params: ExecutionParams, - ) -> Result<()> { + pub async fn send_transfer_proof(&mut self, bob: PeerId, msg: TransferProof) -> Result<()> { let _ = self.send_transfer_proof.send((bob, msg)).await?; - // TODO: Re-evaluate if these acknowledges are necessary at all. - // If we don't use a timeout here and Alice fails to dial Bob she will wait - // indefinitely for this acknowledge. - if timeout( - execution_params.bob_time_to_act, - self.recv_transfer_proof_ack.recv(), - ) - .await - .is_err() - { - error!("Failed to receive transfer proof ack from Bob") - } - Ok(()) } } @@ -123,65 +79,74 @@ impl EventLoopHandle { #[allow(missing_debug_implementations)] pub struct EventLoop { swarm: libp2p::Swarm, - start_execution_setup: Receiver<(PeerId, State0)>, - done_execution_setup: Sender>, - recv_encrypted_signature: Sender, - recv_swap_request: Sender<(SwapRequest, ResponseChannel)>, - conn_established: Sender, - send_swap_response: Receiver<(ResponseChannel, SwapResponse)>, - send_transfer_proof: Receiver<(PeerId, TransferProof)>, - recv_transfer_proof_ack: Sender<()>, + peer_id: PeerId, + execution_params: ExecutionParams, + bitcoin_wallet: Arc, + monero_wallet: Arc, + db: Arc, + listen_address: Multiaddr, + + // Amounts agreed upon for swaps currently in the execution setup phase + // Note: We can do one execution setup per peer at a given time. + swap_amounts: HashMap, + + recv_encrypted_signature: broadcast::Sender, + send_transfer_proof: mpsc::Receiver<(PeerId, TransferProof)>, + + // Only used to produce new handles + send_transfer_proof_sender: mpsc::Sender<(PeerId, TransferProof)>, } impl EventLoop { pub fn new( - transport: SwapTransport, - behaviour: Behaviour, - listen: Multiaddr, - peer_id: PeerId, - ) -> Result<(Self, EventLoopHandle)> { + listen_address: Multiaddr, + seed: Seed, + execution_params: ExecutionParams, + bitcoin_wallet: Arc, + monero_wallet: Arc, + db: Arc, + ) -> Result { + let identity = network::Seed::new(seed).derive_libp2p_identity(); + let behaviour = Behaviour::default(); + let transport = transport::build(&identity)?; + let peer_id = PeerId::from(identity.public()); + let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, peer_id) .executor(Box::new(TokioExecutor { handle: tokio::runtime::Handle::current(), })) .build(); - Swarm::listen_on(&mut swarm, listen.clone()) - .with_context(|| format!("Address is not supported: {:#}", listen))?; + Swarm::listen_on(&mut swarm, listen_address.clone()) + .with_context(|| format!("Address is not supported: {:#}", listen_address))?; - let start_execution_setup = Channels::new(); - let done_execution_setup = Channels::new(); - let recv_encrypted_signature = Channels::new(); - let request = Channels::new(); - let conn_established = Channels::new(); - let send_swap_response = Channels::new(); - let send_transfer_proof = Channels::new(); - let recv_transfer_proof_ack = Channels::new(); + let recv_encrypted_signature = BroadcastChannels::default(); + let send_transfer_proof = MpscChannels::default(); - let driver = EventLoop { + Ok(EventLoop { swarm, - start_execution_setup: start_execution_setup.receiver, - done_execution_setup: done_execution_setup.sender, + peer_id, + execution_params, + bitcoin_wallet, + monero_wallet, + db, + listen_address, + swap_amounts: Default::default(), recv_encrypted_signature: recv_encrypted_signature.sender, - recv_swap_request: request.sender, - conn_established: conn_established.sender, - send_swap_response: send_swap_response.receiver, send_transfer_proof: send_transfer_proof.receiver, - recv_transfer_proof_ack: recv_transfer_proof_ack.sender, - }; + send_transfer_proof_sender: send_transfer_proof.sender, + }) + } - let handle = EventLoopHandle { - start_execution_setup: start_execution_setup.sender, - done_execution_setup: done_execution_setup.receiver, - recv_encrypted_signature: recv_encrypted_signature.receiver, - recv_swap_request: request.receiver, - conn_established: conn_established.receiver, - send_swap_response: send_swap_response.sender, - send_transfer_proof: send_transfer_proof.sender, - recv_transfer_proof_ack: recv_transfer_proof_ack.receiver, - }; + pub fn new_handle(&self) -> EventLoopHandle { + EventLoopHandle { + recv_encrypted_signature: self.recv_encrypted_signature.subscribe(), + send_transfer_proof: self.send_transfer_proof_sender.clone(), + } + } - Ok((driver, handle)) + pub fn peer_id(&self) -> PeerId { + self.peer_id } pub async fn run(&mut self) { @@ -190,22 +155,21 @@ impl EventLoop { swarm_event = self.swarm.next().fuse() => { match swarm_event { OutEvent::ConnectionEstablished(alice) => { - let _ = self.conn_established.send(alice).await; + debug!("Connection Established with {}", alice); } - OutEvent::SwapRequest { msg, channel } => { - let _ = self.recv_swap_request.send((msg, channel)).await; + OutEvent::SwapRequest { msg, channel, bob_peer_id } => { + let _ = self.handle_swap_request(msg, channel, bob_peer_id).await; } - OutEvent::ExecutionSetupDone(res) => { - let _ = self.done_execution_setup.send(res.map(|state|*state)).await; + OutEvent::ExecutionSetupDone{bob_peer_id, state3} => { + let _ = self.handle_execution_setup_done(bob_peer_id, *state3).await; } OutEvent::TransferProofAcknowledged => { trace!("Bob acknowledged transfer proof"); - let _ = self.recv_transfer_proof_ack.send(()).await; } OutEvent::EncryptedSignature{ msg, channel } => { - let _ = self.recv_encrypted_signature.send(*msg).await; + let _ = self.recv_encrypted_signature.send(*msg); // Send back empty response so that the request/response protocol completes. - if let Err(error) = self.swarm.encrypted_signature.send_ack(channel) { + if let Err(error) = self.swarm.send_encrypted_signature_ack(channel) { error!("Failed to send Encrypted Signature ack: {:?}", error); } } @@ -215,21 +179,6 @@ impl EventLoop { } } }, - swap_response = self.send_swap_response.recv().fuse() => { - if let Some((channel, swap_response)) = swap_response { - let _ = self - .swarm - .send_swap_response(channel, swap_response) - .map_err(|err|error!("Failed to send swap response: {:#}", err)); - } - }, - option = self.start_execution_setup.recv().fuse() => { - if let Some((bob_peer_id, state0)) = option { - let _ = self - .swarm - .start_execution_setup(bob_peer_id, state0); - } - }, transfer_proof = self.send_transfer_proof.recv().fuse() => { if let Some((bob_peer_id, msg)) = transfer_proof { self.swarm.send_transfer_proof(bob_peer_id, msg); @@ -238,4 +187,78 @@ impl EventLoop { } } } + + async fn handle_swap_request( + &mut self, + swap_request: SwapRequest, + channel: ResponseChannel, + bob_peer_id: PeerId, + ) -> Result<()> { + // 1. Check if acceptable request + // 2. Send response + + let btc_amount = swap_request.btc_amount; + let xmr_amount = btc_amount.as_btc() * RATE as f64; + let xmr_amount = monero::Amount::from_monero(xmr_amount)?; + let swap_response = SwapResponse { xmr_amount }; + + self.swarm + .send_swap_response(channel, swap_response) + .context("Failed to send swap response")?; + + // 3. Start setup execution + + let state0 = State0::new( + btc_amount, + xmr_amount, + self.execution_params, + self.bitcoin_wallet.as_ref(), + &mut OsRng, + ) + .await?; + + // if a node restart during execution setup, the swap is aborted (safely). + self.swap_amounts.insert(bob_peer_id, SwapAmounts { + btc: btc_amount, + xmr: xmr_amount, + }); + + self.swarm.start_execution_setup(bob_peer_id, state0); + // Continues once the execution setup protocol is done + Ok(()) + } + + async fn handle_execution_setup_done( + &mut self, + bob_peer_id: PeerId, + state3: State3, + ) -> Result<()> { + let swap_id = Uuid::new_v4(); + let handle = self.new_handle(); + + let swap_amounts = self.swap_amounts.remove(&bob_peer_id).ok_or_else(|| { + anyhow!( + "execution setup done for an unknown peer id: {}, node restarted in between?", + bob_peer_id + ) + })?; + + let swap = Builder::new( + self.peer_id, + self.execution_params, + swap_id, + self.bitcoin_wallet.clone(), + self.monero_wallet.clone(), + self.db.clone(), + self.listen_address.clone(), + handle, + ) + .with_init_params(swap_amounts, bob_peer_id, state3) + .build() + .await?; + + tokio::spawn(async move { alice::run(swap).await }); + + Ok(()) + } } diff --git a/swap/src/protocol/alice/execution_setup.rs b/swap/src/protocol/alice/execution_setup.rs index 967ba26f..af1e2350 100644 --- a/swap/src/protocol/alice/execution_setup.rs +++ b/swap/src/protocol/alice/execution_setup.rs @@ -8,7 +8,7 @@ use crate::{ bob::{Message0, Message2, Message4}, }, }; -use anyhow::{Context, Error, Result}; +use anyhow::{Context, Error}; use libp2p::PeerId; use libp2p_async_await::BehaviourOutEvent; use serde::{Deserialize, Serialize}; @@ -32,14 +32,18 @@ pub struct Message3 { #[derive(Debug)] pub enum OutEvent { - Done(Result), + Done { bob_peer_id: PeerId, state3: State3 }, + Failure(Error), } -impl From> for OutEvent { - fn from(event: BehaviourOutEvent) -> Self { +impl From> for OutEvent { + fn from(event: BehaviourOutEvent<(PeerId, State3), (), Error>) -> Self { match event { - BehaviourOutEvent::Inbound(_, Ok(State3)) => OutEvent::Done(Ok(State3)), - BehaviourOutEvent::Inbound(_, Err(e)) => OutEvent::Done(Err(e)), + BehaviourOutEvent::Inbound(_, Ok((bob_peer_id, state3))) => OutEvent::Done { + bob_peer_id, + state3, + }, + BehaviourOutEvent::Inbound(_, Err(e)) => OutEvent::Failure(e), BehaviourOutEvent::Outbound(..) => unreachable!("Alice only supports inbound"), } } @@ -48,7 +52,7 @@ impl From> for OutEvent { #[derive(libp2p::NetworkBehaviour)] #[behaviour(out_event = "OutEvent", event_process = false)] pub struct Behaviour { - inner: libp2p_async_await::Behaviour, + inner: libp2p_async_await::Behaviour<(PeerId, State3), (), anyhow::Error>, } impl Default for Behaviour { @@ -92,7 +96,7 @@ impl Behaviour { .context("failed to deserialize message4")?; let state3 = state2.receive(message4)?; - Ok(state3) + Ok((bob, state3)) }) } } diff --git a/swap/src/protocol/alice/state.rs b/swap/src/protocol/alice/state.rs index a6bbf21f..9905a069 100644 --- a/swap/src/protocol/alice/state.rs +++ b/swap/src/protocol/alice/state.rs @@ -6,6 +6,7 @@ use crate::{ wait_for_cancel_timelock_to_expire, GetBlockHeight, TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction, }, + execution_params::ExecutionParams, monero, protocol::{ alice::{Message1, Message3, TransferProof}, @@ -24,10 +25,6 @@ use std::fmt; #[derive(Debug)] pub enum AliceState { Started { - amounts: SwapAmounts, - state0: State0, - }, - Negotiated { bob_peer_id: PeerId, amounts: SwapAmounts, state3: Box, @@ -41,12 +38,12 @@ pub enum AliceState { state3: Box, }, EncSigLearned { - encrypted_signature: bitcoin::EncryptedSignature, + encrypted_signature: Box, state3: Box, }, BtcRedeemed, BtcCancelled { - tx_cancel: TxCancel, + tx_cancel: Box, state3: Box, }, BtcRefunded { @@ -69,7 +66,6 @@ impl fmt::Display for AliceState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { AliceState::Started { .. } => write!(f, "started"), - AliceState::Negotiated { .. } => write!(f, "negotiated"), AliceState::BtcLocked { .. } => write!(f, "btc is locked"), AliceState::XmrLocked { .. } => write!(f, "xmr is locked"), AliceState::EncSigLearned { .. } => write!(f, "encrypted signature is learned"), @@ -101,25 +97,24 @@ pub struct State0 { } impl State0 { - #[allow(clippy::too_many_arguments)] - pub fn new( - a: bitcoin::SecretKey, - s_a: cross_curve_dleq::Scalar, - v_a: monero::PrivateViewKey, + pub async fn new( btc: bitcoin::Amount, xmr: monero::Amount, - cancel_timelock: Timelock, - punish_timelock: Timelock, - redeem_address: bitcoin::Address, - punish_address: bitcoin::Address, + execution_params: ExecutionParams, + bitcoin_wallet: &bitcoin::Wallet, rng: &mut R, - ) -> Self + ) -> Result where R: RngCore + CryptoRng, { + let a = bitcoin::SecretKey::new_random(rng); + let s_a = cross_curve_dleq::Scalar::random(rng); + let v_a = monero::PrivateViewKey::new_random(rng); + let redeem_address = bitcoin_wallet.new_address().await?; + let punish_address = redeem_address.clone(); let dleq_proof_s_a = cross_curve_dleq::Proof::new(rng, &s_a); - Self { + Ok(Self { a, s_a, v_a, @@ -128,9 +123,9 @@ impl State0 { punish_address, btc, xmr, - cancel_timelock, - punish_timelock, - } + cancel_timelock: execution_params.bitcoin_cancel_timelock, + punish_timelock: execution_params.bitcoin_punish_timelock, + }) } pub fn receive(self, msg: Message0) -> Result { diff --git a/swap/src/protocol/alice/steps.rs b/swap/src/protocol/alice/steps.rs index 8cff4b5e..2e437339 100644 --- a/swap/src/protocol/alice/steps.rs +++ b/swap/src/protocol/alice/steps.rs @@ -12,7 +12,7 @@ use crate::{ monero::Transfer, protocol::{ alice, - alice::{event_loop::EventLoopHandle, SwapResponse, TransferProof}, + alice::{event_loop::EventLoopHandle, TransferProof}, SwapAmounts, }, }; @@ -26,43 +26,7 @@ use libp2p::PeerId; use sha2::Sha256; use std::sync::Arc; use tokio::time::timeout; -use tracing::{info, trace}; - -pub async fn negotiate( - state0: alice::State0, - xmr_amount: monero::Amount, - event_loop_handle: &mut EventLoopHandle, - execution_params: ExecutionParams, -) -> Result<(PeerId, alice::State3)> { - trace!("Starting negotiate"); - - // todo: we can move this out, we dont need to timeout here - let bob_peer_id = timeout( - execution_params.bob_time_to_act, - event_loop_handle.recv_conn_established(), - ) - .await - .context("Failed to receive dial connection from Bob")??; - - let event = timeout( - execution_params.bob_time_to_act, - event_loop_handle.recv_swap_request(), - ) - .await - .context("Failed to receive swap request from Bob")??; - - event_loop_handle - .send_swap_response(event.1, SwapResponse { xmr_amount }) - .await?; - - let state3 = timeout( - execution_params.bob_time_to_act, - event_loop_handle.execution_setup(bob_peer_id, state0), - ) - .await??; - - Ok((bob_peer_id, state3)) -} +use tracing::info; // TODO(Franck): Use helper functions from xmr-btc instead of re-writing them // here @@ -96,7 +60,6 @@ pub async fn lock_xmr( state3: alice::State3, event_loop_handle: &mut EventLoopHandle, monero_wallet: Arc, - execution_params: ExecutionParams, ) -> Result<()> where W: Transfer, @@ -118,13 +81,9 @@ where // Otherwise Alice might publish the lock tx twice! event_loop_handle - .send_transfer_proof( - bob_peer_id, - TransferProof { - tx_lock_proof: transfer_proof, - }, - execution_params, - ) + .send_transfer_proof(bob_peer_id, TransferProof { + tx_lock_proof: transfer_proof, + }) .await?; Ok(()) diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 97a542de..4bd6ef6a 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -17,10 +17,10 @@ use crate::{ event_loop::EventLoopHandle, steps::{ build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction, - extract_monero_private_key, lock_xmr, negotiate, - publish_bitcoin_punish_transaction, publish_bitcoin_redeem_transaction, - publish_cancel_transaction, wait_for_bitcoin_encrypted_signature, - wait_for_bitcoin_refund, wait_for_locked_bitcoin, + extract_monero_private_key, lock_xmr, publish_bitcoin_punish_transaction, + publish_bitcoin_redeem_transaction, publish_cancel_transaction, + wait_for_bitcoin_encrypted_signature, wait_for_bitcoin_refund, + wait_for_locked_bitcoin, }, AliceState, }, @@ -84,44 +84,14 @@ async fn run_until_internal( monero_wallet: Arc, execution_params: ExecutionParams, swap_id: Uuid, - db: Database, + db: Arc, ) -> Result { info!("Current state:{}", state); if is_target_state(&state) { Ok(state) } else { match state { - AliceState::Started { amounts, state0 } => { - let (bob_peer_id, state3) = negotiate( - state0, - amounts.xmr, - &mut event_loop_handle, - execution_params, - ) - .await?; - - let state = AliceState::Negotiated { - bob_peer_id, - amounts, - state3: Box::new(state3), - }; - - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet, - monero_wallet, - execution_params, - swap_id, - db, - ) - .await - } - AliceState::Negotiated { + AliceState::Started { state3, bob_peer_id, amounts, @@ -165,7 +135,6 @@ async fn run_until_internal( *state3.clone(), &mut event_loop_handle, monero_wallet.clone(), - execution_params, ) .await?; @@ -202,7 +171,7 @@ async fn run_until_internal( Either::Left(_) => AliceState::CancelTimelockExpired { state3 }, Either::Right((enc_sig, _)) => AliceState::EncSigLearned { state3, - encrypted_signature: enc_sig?, + encrypted_signature: Box::new(enc_sig?), }, } } @@ -231,7 +200,7 @@ async fn run_until_internal( let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { ExpiredTimelocks::None => { match build_bitcoin_redeem_transaction( - encrypted_signature, + *encrypted_signature, &state3.tx_lock, state3.a.clone(), state3.s_a, @@ -305,7 +274,10 @@ async fn run_until_internal( ) .await?; - let state = AliceState::BtcCancelled { state3, tx_cancel }; + let state = AliceState::BtcCancelled { + state3, + tx_cancel: Box::new(tx_cancel), + }; let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; diff --git a/swap/src/protocol/alice/swap_response.rs b/swap/src/protocol/alice/swap_response.rs index 69138642..551d3b11 100644 --- a/swap/src/protocol/alice/swap_response.rs +++ b/swap/src/protocol/alice/swap_response.rs @@ -9,7 +9,7 @@ use libp2p::{ ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, ResponseChannel, }, - NetworkBehaviour, + NetworkBehaviour, PeerId, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -20,6 +20,7 @@ pub enum OutEvent { MsgReceived { msg: SwapRequest, channel: ResponseChannel, + bob_peer_id: PeerId, }, ResponseSent, Failure(Error), @@ -45,6 +46,7 @@ impl From> for OutEvent { OutEvent::MsgReceived { msg: request, channel, + bob_peer_id: peer, } } RequestResponseEvent::Message { diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index 4807d16e..d3010801 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -168,7 +168,7 @@ impl Builder { &self, ) -> Result<(bob::event_loop::EventLoop, bob::event_loop::EventLoopHandle)> { let bob_behaviour = bob::Behaviour::default(); - let bob_transport = build(self.identity.clone())?; + let bob_transport = build(&self.identity)?; bob::event_loop::EventLoop::new( bob_transport, diff --git a/swap/src/protocol/bob/event_loop.rs b/swap/src/protocol/bob/event_loop.rs index 4d8229d6..821d1f67 100644 --- a/swap/src/protocol/bob/event_loop.rs +++ b/swap/src/protocol/bob/event_loop.rs @@ -43,7 +43,6 @@ pub struct EventLoopHandle { dial_alice: Sender<()>, send_swap_request: Sender, send_encrypted_signature: Sender, - recv_encrypted_signature_ack: Receiver<()>, } impl EventLoopHandle { @@ -95,10 +94,6 @@ impl EventLoopHandle { ) -> Result<()> { self.send_encrypted_signature.send(tx_redeem_encsig).await?; - self.recv_encrypted_signature_ack - .recv() - .await - .ok_or_else(|| anyhow!("Failed to receive encrypted signature ack from Alice"))?; Ok(()) } } @@ -116,7 +111,6 @@ pub struct EventLoop { conn_established: Sender, send_swap_request: Receiver, send_encrypted_signature: Receiver, - recv_encrypted_signature_ack: Sender<()>, } impl EventLoop { @@ -144,7 +138,6 @@ impl EventLoop { let conn_established = Channels::new(); let send_swap_request = Channels::new(); let send_encrypted_signature = Channels::new(); - let recv_encrypted_signature_ack = Channels::new(); let event_loop = EventLoop { swarm, @@ -158,7 +151,6 @@ impl EventLoop { dial_alice: dial_alice.receiver, send_swap_request: send_swap_request.receiver, send_encrypted_signature: send_encrypted_signature.receiver, - recv_encrypted_signature_ack: recv_encrypted_signature_ack.sender, }; let handle = EventLoopHandle { @@ -170,7 +162,6 @@ impl EventLoop { dial_alice: dial_alice.sender, send_swap_request: send_swap_request.sender, send_encrypted_signature: send_encrypted_signature.sender, - recv_encrypted_signature_ack: recv_encrypted_signature_ack.receiver, }; Ok((event_loop, handle)) @@ -199,7 +190,6 @@ impl EventLoop { } OutEvent::EncryptedSignatureAcknowledged => { debug!("Alice acknowledged encrypted signature"); - let _ = self.recv_encrypted_signature_ack.send(()).await; } OutEvent::ResponseSent => {} OutEvent::Failure(err) => { diff --git a/swap/tests/bob_refunds_using_cancel_and_refund_command.rs b/swap/tests/bob_refunds_using_cancel_and_refund_command.rs index 9b64d826..43007177 100644 --- a/swap/tests/bob_refunds_using_cancel_and_refund_command.rs +++ b/swap/tests/bob_refunds_using_cancel_and_refund_command.rs @@ -1,17 +1,13 @@ pub mod testutils; -use swap::protocol::{alice, bob, bob::BobState}; +use swap::protocol::{bob, bob::BobState}; use testutils::{bob_run_until::is_btc_locked, FastCancelConfig}; #[tokio::test] async fn given_bob_manually_refunds_after_btc_locked_bob_refunds() { testutils::setup_test(FastCancelConfig, |mut ctx| async move { - let (alice_swap, _) = ctx.new_swap_as_alice().await; let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await; - let alice_handle = alice::run(alice_swap); - let alice_swap_handle = tokio::spawn(alice_handle); - let bob_state = bob::run_until(bob_swap, is_btc_locked).await.unwrap(); assert!(matches!(bob_state, BobState::BtcLocked { .. })); @@ -29,6 +25,7 @@ async fn given_bob_manually_refunds_after_btc_locked_bob_refunds() { } // Bob manually cancels + bob_join_handle.abort(); let (_, state) = bob::cancel( bob_swap.swap_id, bob_swap.state, @@ -41,10 +38,11 @@ async fn given_bob_manually_refunds_after_btc_locked_bob_refunds() { .unwrap(); assert!(matches!(state, BobState::BtcCancelled { .. })); - let (bob_swap, _) = ctx.stop_and_resume_bob_from_db(bob_join_handle).await; + let (bob_swap, bob_join_handle) = ctx.stop_and_resume_bob_from_db(bob_join_handle).await; assert!(matches!(bob_swap.state, BobState::BtcCancelled { .. })); // Bob manually refunds + bob_join_handle.abort(); let bob_state = bob::refund( bob_swap.swap_id, bob_swap.state, @@ -58,9 +56,6 @@ async fn given_bob_manually_refunds_after_btc_locked_bob_refunds() { .unwrap(); ctx.assert_bob_refunded(bob_state).await; - - let alice_state = alice_swap_handle.await.unwrap().unwrap(); - ctx.assert_alice_refunded(alice_state).await; }) .await; } diff --git a/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_exired.rs b/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired.rs similarity index 89% rename from swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_exired.rs rename to swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired.rs index 07b69590..9e8a79aa 100644 --- a/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_exired.rs +++ b/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired.rs @@ -1,18 +1,14 @@ pub mod testutils; use bob::cancel::CancelError; -use swap::protocol::{alice, bob, bob::BobState}; +use swap::protocol::{bob, bob::BobState}; use testutils::{bob_run_until::is_btc_locked, SlowCancelConfig}; #[tokio::test] async fn given_bob_manually_cancels_when_timelock_not_expired_errors() { testutils::setup_test(SlowCancelConfig, |mut ctx| async move { - let (alice_swap, _) = ctx.new_swap_as_alice().await; let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await; - let alice_handle = alice::run(alice_swap); - tokio::spawn(alice_handle); - let bob_state = bob::run_until(bob_swap, is_btc_locked).await.unwrap(); assert!(matches!(bob_state, BobState::BtcLocked { .. })); diff --git a/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_exired_force.rs b/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force.rs similarity index 89% rename from swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_exired_force.rs rename to swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force.rs index 098f8752..fd3465d7 100644 --- a/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_exired_force.rs +++ b/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force.rs @@ -1,17 +1,13 @@ pub mod testutils; -use swap::protocol::{alice, bob, bob::BobState}; +use swap::protocol::{bob, bob::BobState}; use testutils::{bob_run_until::is_btc_locked, SlowCancelConfig}; #[tokio::test] async fn given_bob_manually_forces_cancel_when_timelock_not_expired_errors() { testutils::setup_test(SlowCancelConfig, |mut ctx| async move { - let (alice_swap, _) = ctx.new_swap_as_alice().await; let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await; - let alice_handle = alice::run(alice_swap); - tokio::spawn(alice_handle); - let bob_state = bob::run_until(bob_swap, is_btc_locked).await.unwrap(); assert!(matches!(bob_state, BobState::BtcLocked { .. })); diff --git a/swap/tests/happy_path.rs b/swap/tests/happy_path.rs index 548aee86..8ba5e903 100644 --- a/swap/tests/happy_path.rs +++ b/swap/tests/happy_path.rs @@ -1,23 +1,18 @@ pub mod testutils; -use swap::protocol::{alice, bob}; +use swap::protocol::bob; use testutils::SlowCancelConfig; -use tokio::join; /// Run the following tests with RUST_MIN_STACK=10000000 #[tokio::test] async fn happy_path() { testutils::setup_test(SlowCancelConfig, |mut ctx| async move { - let (alice_swap, _) = ctx.new_swap_as_alice().await; let (bob_swap, _) = ctx.new_swap_as_bob().await; - let alice = alice::run(alice_swap); - let bob = bob::run(bob_swap); + let bob_state = bob::run(bob_swap).await; - let (alice_state, bob_state) = join!(alice, bob); - - ctx.assert_alice_redeemed(alice_state.unwrap()).await; + ctx.assert_alice_redeemed().await; ctx.assert_bob_redeemed(bob_state.unwrap()).await; }) .await; diff --git a/swap/tests/happy_path_restart_alice.rs b/swap/tests/happy_path_restart_alice.rs deleted file mode 100644 index fbf2515c..00000000 --- a/swap/tests/happy_path_restart_alice.rs +++ /dev/null @@ -1,31 +0,0 @@ -pub mod testutils; - -use swap::protocol::{alice, alice::AliceState, bob}; -use testutils::{alice_run_until::is_encsig_learned, SlowCancelConfig}; - -#[tokio::test] -async fn given_alice_restarts_after_encsig_is_learned_resume_swap() { - testutils::setup_test(SlowCancelConfig, |mut ctx| async move { - let (alice_swap, alice_join_handle) = ctx.new_swap_as_alice().await; - let (bob_swap, _) = ctx.new_swap_as_bob().await; - - let bob = bob::run(bob_swap); - let bob_handle = tokio::spawn(bob); - - let alice_state = alice::run_until(alice_swap, is_encsig_learned) - .await - .unwrap(); - assert!(matches!(alice_state, AliceState::EncSigLearned { .. })); - - let alice_swap = ctx.stop_and_resume_alice_from_db(alice_join_handle).await; - assert!(matches!(alice_swap.state, AliceState::EncSigLearned { .. })); - - let alice_state = alice::run(alice_swap).await.unwrap(); - - ctx.assert_alice_redeemed(alice_state).await; - - let bob_state = bob_handle.await.unwrap(); - ctx.assert_bob_redeemed(bob_state.unwrap()).await - }) - .await; -} diff --git a/swap/tests/happy_path_restart_bob_after_comm.rs b/swap/tests/happy_path_restart_bob_after_comm.rs deleted file mode 100644 index 6cd08c64..00000000 --- a/swap/tests/happy_path_restart_bob_after_comm.rs +++ /dev/null @@ -1,30 +0,0 @@ -pub mod testutils; - -use swap::protocol::{alice, bob, bob::BobState}; -use testutils::{bob_run_until::is_encsig_sent, SlowCancelConfig}; - -#[tokio::test] -async fn given_bob_restarts_after_encsig_is_sent_resume_swap() { - testutils::setup_test(SlowCancelConfig, |mut ctx| async move { - let (alice_swap, _) = ctx.new_swap_as_alice().await; - let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await; - - let alice = alice::run(alice_swap); - let alice_handle = tokio::spawn(alice); - - let bob_state = bob::run_until(bob_swap, is_encsig_sent).await.unwrap(); - - assert!(matches!(bob_state, BobState::EncSigSent { .. })); - - let (bob_swap, _) = ctx.stop_and_resume_bob_from_db(bob_join_handle).await; - assert!(matches!(bob_swap.state, BobState::EncSigSent { .. })); - - let bob_state = bob::run(bob_swap).await.unwrap(); - - ctx.assert_bob_redeemed(bob_state).await; - - let alice_state = alice_handle.await.unwrap(); - ctx.assert_alice_redeemed(alice_state.unwrap()).await; - }) - .await; -} diff --git a/swap/tests/happy_path_restart_bob_after_lock_proof_received.rs b/swap/tests/happy_path_restart_bob_after_lock_proof_received.rs deleted file mode 100644 index d335acc9..00000000 --- a/swap/tests/happy_path_restart_bob_after_lock_proof_received.rs +++ /dev/null @@ -1,35 +0,0 @@ -pub mod testutils; - -use swap::protocol::{alice, bob, bob::BobState}; -use testutils::{bob_run_until::is_lock_proof_received, SlowCancelConfig}; - -#[tokio::test] -async fn given_bob_restarts_after_lock_proof_received_resume_swap() { - testutils::setup_test(SlowCancelConfig, |mut ctx| async move { - let (alice_swap, _) = ctx.new_swap_as_alice().await; - let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await; - - let alice_handle = alice::run(alice_swap); - let alice_swap_handle = tokio::spawn(alice_handle); - - let bob_state = bob::run_until(bob_swap, is_lock_proof_received) - .await - .unwrap(); - - assert!(matches!(bob_state, BobState::XmrLockProofReceived { .. })); - - let (bob_swap, _) = ctx.stop_and_resume_bob_from_db(bob_join_handle).await; - assert!(matches!( - bob_swap.state, - BobState::XmrLockProofReceived { .. } - )); - - let bob_state = bob::run(bob_swap).await.unwrap(); - - ctx.assert_bob_redeemed(bob_state).await; - - let alice_state = alice_swap_handle.await.unwrap().unwrap(); - ctx.assert_alice_redeemed(alice_state).await; - }) - .await; -} diff --git a/swap/tests/happy_path_restart_bob_before_comm.rs b/swap/tests/happy_path_restart_bob_before_comm.rs index 7c72adf9..6235b0ec 100644 --- a/swap/tests/happy_path_restart_bob_before_comm.rs +++ b/swap/tests/happy_path_restart_bob_before_comm.rs @@ -1,17 +1,13 @@ pub mod testutils; -use swap::protocol::{alice, bob, bob::BobState}; +use swap::protocol::{bob, bob::BobState}; use testutils::{bob_run_until::is_xmr_locked, SlowCancelConfig}; #[tokio::test] async fn given_bob_restarts_after_xmr_is_locked_resume_swap() { testutils::setup_test(SlowCancelConfig, |mut ctx| async move { - let (alice_swap, _) = ctx.new_swap_as_alice().await; let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await; - let alice_handle = alice::run(alice_swap); - let alice_swap_handle = tokio::spawn(alice_handle); - let bob_state = bob::run_until(bob_swap, is_xmr_locked).await.unwrap(); assert!(matches!(bob_state, BobState::XmrLocked { .. })); @@ -23,8 +19,7 @@ async fn given_bob_restarts_after_xmr_is_locked_resume_swap() { ctx.assert_bob_redeemed(bob_state).await; - let alice_state = alice_swap_handle.await.unwrap(); - ctx.assert_alice_redeemed(alice_state.unwrap()).await; + ctx.assert_alice_redeemed().await; }) .await; } diff --git a/swap/tests/punish.rs b/swap/tests/punish.rs deleted file mode 100644 index ea29a95a..00000000 --- a/swap/tests/punish.rs +++ /dev/null @@ -1,34 +0,0 @@ -pub mod testutils; - -use swap::protocol::{alice, bob, bob::BobState}; -use testutils::{bob_run_until::is_btc_locked, FastPunishConfig}; - -/// Bob locks Btc and Alice locks Xmr. Bob does not act; he fails to send Alice -/// the encsig and fail to refund or redeem. Alice punishes. -#[tokio::test] -async fn alice_punishes_if_bob_never_acts_after_fund() { - testutils::setup_test(FastPunishConfig, |mut ctx| async move { - let (alice_swap, _) = ctx.new_swap_as_alice().await; - let (bob_swap, bob_join_handle) = ctx.new_swap_as_bob().await; - - let alice = alice::run(alice_swap); - let alice_handle = tokio::spawn(alice); - - let bob_state = bob::run_until(bob_swap, is_btc_locked).await.unwrap(); - - assert!(matches!(bob_state, BobState::BtcLocked { .. })); - - let alice_state = alice_handle.await.unwrap(); - ctx.assert_alice_punished(alice_state.unwrap()).await; - - // Restart Bob after Alice punished to ensure Bob transitions to - // punished and does not run indefinitely - let (bob_swap, _) = ctx.stop_and_resume_bob_from_db(bob_join_handle).await; - assert!(matches!(bob_swap.state, BobState::BtcLocked { .. })); - - let bob_state = bob::run(bob_swap).await.unwrap(); - - ctx.assert_bob_punished(bob_state).await; - }) - .await; -} diff --git a/swap/tests/refund_restart_alice.rs b/swap/tests/refund_restart_alice.rs deleted file mode 100644 index 5af2c7a0..00000000 --- a/swap/tests/refund_restart_alice.rs +++ /dev/null @@ -1,33 +0,0 @@ -pub mod testutils; - -use swap::protocol::{alice, alice::AliceState, bob}; -use testutils::{alice_run_until::is_xmr_locked, FastCancelConfig}; - -/// Bob locks btc and Alice locks xmr. Alice fails to act so Bob refunds. Alice -/// then also refunds. -#[tokio::test] -async fn given_alice_restarts_after_xmr_is_locked_refund_swap() { - testutils::setup_test(FastCancelConfig, |mut ctx| async move { - let (alice_swap, alice_join_handle) = ctx.new_swap_as_alice().await; - let (bob_swap, _) = ctx.new_swap_as_bob().await; - - let bob = bob::run(bob_swap); - let bob_handle = tokio::spawn(bob); - - let alice_state = alice::run_until(alice_swap, is_xmr_locked).await.unwrap(); - assert!(matches!(alice_state, AliceState::XmrLocked { .. })); - - // Alice does not act, Bob refunds - let bob_state = bob_handle.await.unwrap(); - ctx.assert_bob_refunded(bob_state.unwrap()).await; - - // Once bob has finished Alice is restarted and refunds as well - let alice_swap = ctx.stop_and_resume_alice_from_db(alice_join_handle).await; - assert!(matches!(alice_swap.state, AliceState::XmrLocked { .. })); - - let alice_state = alice::run(alice_swap).await.unwrap(); - - ctx.assert_alice_refunded(alice_state).await; - }) - .await; -} diff --git a/swap/tests/refund_restart_alice_cancelled.rs b/swap/tests/refund_restart_alice_cancelled.rs deleted file mode 100644 index 129887a1..00000000 --- a/swap/tests/refund_restart_alice_cancelled.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub mod testutils; - -use swap::{ - execution_params, - protocol::{alice, alice::AliceState, bob}, -}; -use testutils::alice_run_until::is_encsig_learned; - -/// Bob locks btc and Alice locks xmr. Alice fails to act so Bob refunds. Alice -/// is forced to refund even though she learned the secret and would be able to -/// redeem had the timelock not expired. -#[tokio::test] -async fn given_alice_restarts_after_enc_sig_learned_and_bob_already_cancelled_refund_swap() { - testutils::setup_test(execution_params::Regtest, |mut ctx| async move { - let (alice_swap, alice_join_handle) = ctx.new_swap_as_alice().await; - let (bob_swap, _) = ctx.new_swap_as_bob().await; - - let bob = bob::run(bob_swap); - let bob_handle = tokio::spawn(bob); - - let alice_state = alice::run_until(alice_swap, is_encsig_learned) - .await - .unwrap(); - assert!( - matches!(alice_state, AliceState::EncSigLearned { .. }), - "Alice state is not EncSigLearned: {:?}", - alice_state - ); - - // Wait for Bob to refund, because Alice does not act - let bob_state = bob_handle.await.unwrap(); - ctx.assert_bob_refunded(bob_state.unwrap()).await; - - // Once bob has finished Alice is restarted and refunds as well - let alice_swap = ctx.stop_and_resume_alice_from_db(alice_join_handle).await; - assert!( - matches!(alice_swap.state, AliceState::EncSigLearned { .. }), - "Alice state is not EncSigLearned: {:?}", - alice_state - ); - - let alice_state = alice::run(alice_swap).await.unwrap(); - - ctx.assert_alice_refunded(alice_state).await; - }) - .await; -} diff --git a/swap/tests/testutils/mod.rs b/swap/tests/testutils/mod.rs index 91a7949f..bec889af 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/testutils/mod.rs @@ -8,6 +8,7 @@ use std::{path::PathBuf, sync::Arc}; use swap::{ bitcoin, bitcoin::Timelock, + database::{Database, Swap}, execution_params, execution_params::{ExecutionParams, GetExecutionParams}, monero, @@ -30,31 +31,10 @@ pub struct StartingBalances { struct AliceParams { seed: Seed, execution_params: ExecutionParams, - swap_id: Uuid, - bitcoin_wallet: Arc, - monero_wallet: Arc, - db_path: PathBuf, + db: Arc, listen_address: Multiaddr, } -impl AliceParams { - pub fn builder(&self) -> alice::Builder { - alice::Builder::new( - self.seed, - self.execution_params, - self.swap_id, - self.bitcoin_wallet.clone(), - self.monero_wallet.clone(), - self.db_path.clone(), - self.listen_address.clone(), - ) - } - - fn peer_id(&self) -> PeerId { - self.builder().peer_id() - } -} - #[derive(Debug, Clone)] struct BobParams { seed: Seed, @@ -84,6 +64,12 @@ impl BobParams { pub struct BobEventLoopJoinHandle(JoinHandle<()>); +impl BobEventLoopJoinHandle { + pub fn abort(&self) { + self.0.abort() + } +} + pub struct AliceEventLoopJoinHandle(JoinHandle<()>); pub struct TestContext { @@ -101,20 +87,6 @@ pub struct TestContext { } impl TestContext { - pub async fn new_swap_as_alice(&mut self) -> (alice::Swap, AliceEventLoopJoinHandle) { - let (swap, mut event_loop) = self - .alice_params - .builder() - .with_init_params(self.swap_amounts) - .build() - .await - .unwrap(); - - let join_handle = tokio::spawn(async move { event_loop.run().await }); - - (swap, AliceEventLoopJoinHandle(join_handle)) - } - pub async fn new_swap_as_bob(&mut self) -> (bob::Swap, BobEventLoopJoinHandle) { let (swap, event_loop) = self .bob_params @@ -129,24 +101,11 @@ impl TestContext { (swap, BobEventLoopJoinHandle(join_handle)) } - pub async fn stop_and_resume_alice_from_db( - &mut self, - join_handle: AliceEventLoopJoinHandle, - ) -> alice::Swap { - join_handle.0.abort(); - - let (swap, mut event_loop) = self.alice_params.builder().build().await.unwrap(); - - tokio::spawn(async move { event_loop.run().await }); - - swap - } - pub async fn stop_and_resume_bob_from_db( &mut self, join_handle: BobEventLoopJoinHandle, ) -> (bob::Swap, BobEventLoopJoinHandle) { - join_handle.0.abort(); + join_handle.abort(); let (swap, event_loop) = self.bob_params.builder().build().await.unwrap(); @@ -155,7 +114,17 @@ impl TestContext { (swap, BobEventLoopJoinHandle(join_handle)) } - pub async fn assert_alice_redeemed(&self, state: AliceState) { + pub async fn assert_alice_redeemed(&self) { + let mut states = self.alice_params.db.all().unwrap(); + + assert_eq!(states.len(), 1, "Expected only one swap in Alice's db"); + + let (_swap_id, state) = states.pop().unwrap(); + let state = match state { + Swap::Alice(state) => state.into(), + Swap::Bob(_) => panic!("Bob state in Alice db is unexpected"), + }; + assert!(matches!(state, AliceState::BtcRedeemed)); let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap(); @@ -174,8 +143,22 @@ impl TestContext { assert!(xmr_balance_after_swap <= self.alice_starting_balances.xmr - self.swap_amounts.xmr); } - pub async fn assert_alice_refunded(&self, state: AliceState) { - assert!(matches!(state, AliceState::XmrRefunded)); + pub async fn assert_alice_refunded(&self) { + let mut states = self.alice_params.db.all().unwrap(); + + assert_eq!(states.len(), 1, "Expected only one swap in Alice's db"); + + let (_swap_id, state) = states.pop().unwrap(); + let state = match state { + Swap::Alice(state) => state.into(), + Swap::Bob(_) => panic!("Bob state in Alice db is unexpected"), + }; + + assert!( + matches!(state, AliceState::XmrRefunded), + "Alice state is not XmrRefunded: {}", + state + ); let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap(); assert_eq!(btc_balance_after_swap, self.alice_starting_balances.btc); @@ -342,13 +325,13 @@ where ) .await; + let db_path = tempdir().unwrap(); + let alice_db = Arc::new(Database::open(db_path.path()).unwrap()); + let alice_params = AliceParams { seed: Seed::random().unwrap(), execution_params, - swap_id: Uuid::new_v4(), - bitcoin_wallet: alice_bitcoin_wallet.clone(), - monero_wallet: alice_monero_wallet.clone(), - db_path: tempdir().unwrap().path().to_path_buf(), + db: alice_db.clone(), listen_address, }; @@ -365,6 +348,22 @@ where ) .await; + let mut alice_event_loop = alice::EventLoop::new( + alice_params.listen_address.clone(), + alice_params.seed, + alice_params.execution_params, + alice_bitcoin_wallet.clone(), + alice_monero_wallet.clone(), + alice_db, + ) + .unwrap(); + + let alice_peer_id = alice_event_loop.peer_id(); + + tokio::spawn(async move { + alice_event_loop.run().await; + }); + let bob_params = BobParams { seed: Seed::random().unwrap(), db_path: tempdir().unwrap().path().to_path_buf(), @@ -372,7 +371,7 @@ where bitcoin_wallet: bob_bitcoin_wallet.clone(), monero_wallet: bob_monero_wallet.clone(), alice_address: alice_params.listen_address.clone(), - alice_peer_id: alice_params.peer_id(), + alice_peer_id, execution_params, }; @@ -388,7 +387,7 @@ where bob_monero_wallet, }; - testfn(test).await + testfn(test).await; } async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) {