From 4ee82a5a2a8facac893815c2d3d72e806d062f8d Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Tue, 27 Oct 2020 12:11:03 +1100 Subject: [PATCH 01/21] Execute Alice's on-chain protocol after handshake Co-authored-by: Tobin C. Harding --- swap/Cargo.toml | 4 +- swap/src/alice.rs | 113 +++++++++++++++++++++-- swap/src/alice/message2.rs | 12 ++- swap/src/bitcoin.rs | 75 ++++++++++++++- swap/src/bob.rs | 11 ++- swap/src/bob/message2.rs | 28 ++++-- swap/src/lib.rs | 1 + swap/src/main.rs | 39 +++++--- swap/src/monero.rs | 132 +++++++++++++++++++++++++++ swap/src/network/request_response.rs | 1 - xmr-btc/src/alice.rs | 37 ++++---- xmr-btc/src/alice/message.rs | 2 +- xmr-btc/tests/on_chain.rs | 12 ++- 13 files changed, 402 insertions(+), 65 deletions(-) create mode 100644 swap/src/monero.rs diff --git a/swap/Cargo.toml b/swap/Cargo.toml index de572e12..3a2f295c 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -12,13 +12,15 @@ atty = "0.2" backoff = { version = "0.2", features = ["tokio"] } base64 = "0.12" bitcoin = { version = "0.23", features = ["rand", "use-serde"] } # TODO: Upgrade other crates in this repo to use this version. -bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "d402b36d3d6406150e3bfb71492ff4a0a7cb290e" } +bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "7ff30a559ab57cc3aa71189e71433ef6b2a6c3a2" } derivative = "2" futures = { version = "0.3", default-features = false } +genawaiter = "0.99.1" libp2p = { version = "0.29", default-features = false, features = ["tcp-tokio", "yamux", "mplex", "dns", "noise", "request-response"] } libp2p-tokio-socks5 = "0.4" log = { version = "0.4", features = ["serde"] } monero = "0.9" +monero-harness = { path = "../monero-harness" } rand = "0.7" reqwest = { version = "0.10", default-features = false, features = ["socks"] } serde = { version = "1", features = ["derive"] } diff --git a/swap/src/alice.rs b/swap/src/alice.rs index ca8a45e3..64ff4660 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -1,14 +1,18 @@ //! Run an XMR/BTC swap in the role of Alice. //! Alice holds XMR and wishes receive BTC. use anyhow::Result; +use async_trait::async_trait; +use genawaiter::GeneratorState; use libp2p::{ core::{identity::Keypair, Multiaddr}, request_response::ResponseChannel, NetworkBehaviour, PeerId, }; use rand::rngs::OsRng; -use std::thread; -use tracing::{debug, info}; +use std::sync::Arc; +use tokio::sync::Mutex; +use tracing::{debug, info, warn}; +use xmr_btc::alice; mod amounts; mod message0; @@ -17,6 +21,7 @@ mod message2; use self::{amounts::*, message0::*, message1::*, message2::*}; use crate::{ + bitcoin, monero, network::{ peer_tracker::{self, PeerTracker}, request_response::AliceToBob, @@ -24,16 +29,46 @@ use crate::{ }, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; -use xmr_btc::{alice::State0, bob, monero}; +use xmr_btc::{ + alice::{action_generator, Action, ReceiveBitcoinRedeemEncsig, State0}, + bitcoin::BroadcastSignedTransaction, + bob, + monero::{CreateWalletForOutput, Transfer}, +}; pub type Swarm = libp2p::Swarm; pub async fn swap( + bitcoin_wallet: Arc, + monero_wallet: Arc, listen: Multiaddr, local_port: Option, redeem_address: ::bitcoin::Address, punish_address: ::bitcoin::Address, ) -> Result<()> { + struct Network { + swarm: Swarm, + channel: Option>, + } + + impl Network { + pub fn send_message2(&mut self, proof: monero::TransferProof) { + match self.channel.take() { + None => warn!("Channel not found, did you call this twice?"), + Some(channel) => self.swarm.send_message2(channel, alice::Message2 { + tx_lock_proof: proof, + }), + } + } + } + + #[async_trait] + impl ReceiveBitcoinRedeemEncsig for Network { + async fn receive_bitcoin_redeem_encsig(&mut self) -> xmr_btc::bitcoin::EncryptedSignature { + todo!() + } + } + let mut swarm = new_swarm(listen, local_port)?; let message0: bob::Message0; let mut last_amounts: Option = None; @@ -96,15 +131,63 @@ pub async fn swap( let msg = state2.next_message(); swarm.send_message1(channel, msg); - let _state3 = match swarm.next().await { - OutEvent::Message2(msg) => state2.receive(msg)?, + let (state3, channel) = match swarm.next().await { + OutEvent::Message2 { msg, channel } => { + let state3 = state2.receive(msg)?; + (state3, channel) + } other => panic!("Unexpected event: {:?}", other), }; info!("Handshake complete, we now have State3 for Alice."); - thread::park(); - Ok(()) + let network = Arc::new(Mutex::new(Network { + swarm, + channel: Some(channel), + })); + + let mut action_generator = + action_generator(network.clone(), bitcoin_wallet.clone(), state3, 3600); + + loop { + let state = action_generator.async_resume().await; + + tracing::info!("resumed execution of alice generator, got: {:?}", state); + + match state { + GeneratorState::Yielded(Action::LockXmr { + amount, + public_spend_key, + public_view_key, + }) => { + let (transfer_proof, _) = monero_wallet + .transfer(public_spend_key, public_view_key, amount) + .await?; + + let mut guard = network.as_ref().lock().await; + guard.send_message2(transfer_proof); + } + + GeneratorState::Yielded(Action::RedeemBtc(tx)) => { + let _ = bitcoin_wallet.broadcast_signed_transaction(tx).await?; + } + GeneratorState::Yielded(Action::CancelBtc(tx)) => { + let _ = bitcoin_wallet.broadcast_signed_transaction(tx).await?; + } + GeneratorState::Yielded(Action::PunishBtc(tx)) => { + let _ = bitcoin_wallet.broadcast_signed_transaction(tx).await?; + } + GeneratorState::Yielded(Action::CreateMoneroWalletForOutput { + spend_key, + view_key, + }) => { + monero_wallet + .create_and_load_wallet_for_output(spend_key, view_key) + .await?; + } + GeneratorState::Complete(()) => return Ok(()), + } + } } fn new_swarm(listen: Multiaddr, port: Option) -> Result { @@ -155,7 +238,10 @@ pub enum OutEvent { msg: bob::Message1, channel: ResponseChannel, }, - Message2(bob::Message2), + Message2 { + msg: bob::Message2, + channel: ResponseChannel, + }, } impl From for OutEvent { @@ -193,7 +279,7 @@ impl From for OutEvent { impl From for OutEvent { fn from(event: message2::OutEvent) -> Self { match event { - message2::OutEvent::Msg(msg) => OutEvent::Message2(msg), + message2::OutEvent::Msg { msg, channel } => OutEvent::Message2 { msg, channel }, } } } @@ -240,6 +326,15 @@ impl Alice { ) { self.message1.send(channel, msg) } + + /// Send Message2 to Bob in response to receiving his Message2. + pub fn send_message2( + &mut self, + channel: ResponseChannel, + msg: xmr_btc::alice::Message2, + ) { + self.message2.send(channel, msg) + } } impl Default for Alice { diff --git a/swap/src/alice/message2.rs b/swap/src/alice/message2.rs index a8704c0d..28e5fc10 100644 --- a/swap/src/alice/message2.rs +++ b/swap/src/alice/message2.rs @@ -18,7 +18,12 @@ use xmr_btc::bob; #[derive(Debug)] pub enum OutEvent { - Msg(bob::Message2), + Msg { + /// Received message from Bob. + msg: bob::Message2, + /// Channel to send back Alice's message 2. + channel: ResponseChannel, + }, } /// A `NetworkBehaviour` that represents receiving of message 2 from Bob. @@ -78,10 +83,7 @@ impl NetworkBehaviourEventProcess> .. } => match request { BobToAlice::Message2(msg) => { - self.events.push_back(OutEvent::Msg(msg)); - // Send back empty response so that the request/response protocol completes. - let msg = AliceToBob::EmptyResponse; - self.rr.send_response(channel, msg); + self.events.push_back(OutEvent::Msg { msg, channel }); } other => debug!("got request: {:?}", other), }, diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index 2a4a8e8d..fb0f63e5 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -1,12 +1,15 @@ +use std::time::Duration; + use anyhow::Result; use async_trait::async_trait; use backoff::{future::FutureOperation as _, ExponentialBackoff}; use bitcoin::{util::psbt::PartiallySignedTransaction, Address, Transaction}; -use bitcoin_harness::bitcoind_rpc::PsbtBase64; +use bitcoin_harness::{bitcoind_rpc::PsbtBase64, Bitcoind}; use reqwest::Url; +use tokio::time; use xmr_btc::bitcoin::{ - Amount, BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, TxLock, Txid, - WatchForRawTransaction, + Amount, BlockHeight, BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, + TransactionBlockHeight, TxLock, Txid, WatchForRawTransaction, }; // This is cut'n'paste from xmr_btc/tests/harness/wallet/bitcoin.rs @@ -41,6 +44,22 @@ impl Wallet { } } +pub async fn make_wallet( + name: &str, + bitcoind: &Bitcoind<'_>, + fund_amount: Amount, +) -> Result { + let wallet = Wallet::new(name, &bitcoind.node_url).await?; + let buffer = Amount::from_btc(1.0).unwrap(); + let amount = fund_amount + buffer; + + let address = wallet.0.new_address().await.unwrap(); + + bitcoind.mint(address, amount).await.unwrap(); + + Ok(wallet) +} + #[async_trait] impl BuildTxLockPsbt for Wallet { async fn build_tx_lock_psbt( @@ -81,6 +100,13 @@ impl SignTxLock for Wallet { impl BroadcastSignedTransaction for Wallet { async fn broadcast_signed_transaction(&self, transaction: Transaction) -> Result { let txid = self.0.send_raw_transaction(transaction).await?; + + // TODO: Instead of guessing how long it will take for the transaction to be + // mined we should ask bitcoind for the number of confirmations on `txid` + + // give time for transaction to be mined + time::delay_for(Duration::from_millis(1100)).await; + Ok(txid) } } @@ -97,3 +123,46 @@ impl WatchForRawTransaction for Wallet { .expect("transient errors to be retried") } } + +#[async_trait] +impl BlockHeight for Wallet { + async fn block_height(&self) -> u32 { + (|| async { Ok(self.0.block_height().await?) }) + .retry(ExponentialBackoff { + max_elapsed_time: None, + ..Default::default() + }) + .await + .expect("transient errors to be retried") + } +} + +#[async_trait] +impl TransactionBlockHeight for Wallet { + async fn transaction_block_height(&self, txid: Txid) -> u32 { + #[derive(Debug)] + enum Error { + Io, + NotYetMined, + } + + (|| async { + let block_height = self + .0 + .transaction_block_height(txid) + .await + .map_err(|_| backoff::Error::Transient(Error::Io))?; + + let block_height = + block_height.ok_or_else(|| backoff::Error::Transient(Error::NotYetMined))?; + + Result::<_, backoff::Error>::Ok(block_height) + }) + .retry(ExponentialBackoff { + max_elapsed_time: None, + ..Default::default() + }) + .await + .expect("transient errors to be retried") + } +} diff --git a/swap/src/bob.rs b/swap/src/bob.rs index a955d0fb..9bcb302e 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -21,7 +21,7 @@ use crate::{ peer_tracker::{self, PeerTracker}, transport, TokioExecutor, }, - Cmd, Never, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, + Cmd, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; use xmr_btc::{ alice, @@ -143,6 +143,7 @@ pub enum OutEvent { Amounts(SwapAmounts), Message0(alice::Message0), Message1(alice::Message1), + Message2(alice::Message2), } impl From for OutEvent { @@ -179,9 +180,11 @@ impl From for OutEvent { } } -impl From for OutEvent { - fn from(_: Never) -> Self { - panic!("this never happens") +impl From for OutEvent { + fn from(event: message2::OutEvent) -> Self { + match event { + message2::OutEvent::Msg(msg) => OutEvent::Message2(msg), + } } } diff --git a/swap/src/bob/message2.rs b/swap/src/bob/message2.rs index e8fa295b..0898c583 100644 --- a/swap/src/bob/message2.rs +++ b/swap/src/bob/message2.rs @@ -7,23 +7,28 @@ use libp2p::{ NetworkBehaviour, PeerId, }; use std::{ + collections::VecDeque, task::{Context, Poll}, time::Duration, }; use tracing::{debug, error}; -use crate::{ - network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}, - Never, -}; -use xmr_btc::bob; +use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; +use xmr_btc::{alice, bob}; + +#[derive(Debug)] +pub enum OutEvent { + Msg(alice::Message2), +} /// A `NetworkBehaviour` that represents sending message 2 to Alice. #[derive(NetworkBehaviour)] -#[behaviour(out_event = "Never", poll_method = "poll")] +#[behaviour(out_event = "OutEvent", poll_method = "poll")] #[allow(missing_debug_implementations)] pub struct Message2 { rr: RequestResponse, + #[behaviour(ignore)] + events: VecDeque, } impl Message2 { @@ -32,13 +37,15 @@ impl Message2 { let _id = self.rr.send_request(&alice, msg); } - // TODO: Do we need a custom implementation if we are not bubbling any out - // events? fn poll( &mut self, _: &mut Context<'_>, _: &mut impl PollParameters, - ) -> Poll, Never>> { + ) -> Poll, OutEvent>> { + if let Some(event) = self.events.pop_front() { + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); + } + Poll::Pending } } @@ -55,6 +62,7 @@ impl Default for Message2 { vec![(Protocol, ProtocolSupport::Full)], config, ), + events: VecDeque::default(), } } } @@ -70,7 +78,7 @@ impl NetworkBehaviourEventProcess> message: RequestResponseMessage::Response { response, .. }, .. } => match response { - AliceToBob::EmptyResponse => debug!("Alice correctly responded to message 2"), + AliceToBob::Message2(msg) => self.events.push_back(OutEvent::Msg(msg)), other => debug!("unexpected response: {:?}", other), }, RequestResponseEvent::InboundFailure { error, .. } => { diff --git a/swap/src/lib.rs b/swap/src/lib.rs index 7a85eeed..386af1ef 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -4,6 +4,7 @@ use std::fmt::{self, Display}; pub mod alice; pub mod bitcoin; pub mod bob; +pub mod monero; pub mod network; pub mod storage; #[cfg(feature = "tor")] diff --git a/swap/src/main.rs b/swap/src/main.rs index e981dccd..6dc1410c 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -13,20 +13,21 @@ #![forbid(unsafe_code)] use anyhow::{bail, Context, Result}; -use cli::Options; use futures::{channel::mpsc, StreamExt}; use libp2p::Multiaddr; use log::LevelFilter; -use std::{io, io::Write, process}; +use std::{io, io::Write, process, sync::Arc}; use structopt::StructOpt; -use swap::{alice, bitcoin::Wallet, bob, Cmd, Rsp, SwapAmounts}; use tracing::info; use url::Url; -use xmr_btc::bitcoin::{BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock}; mod cli; mod trace; +use cli::Options; +use swap::{alice, bitcoin, bob, monero, Cmd, Rsp, SwapAmounts}; +use xmr_btc::bitcoin::{BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock}; + // TODO: Add root seed file instead of generating new seed each run. // TODO: Remove all instances of the todo! macro @@ -35,6 +36,7 @@ mod trace; pub const PORT: u16 = 9876; // Arbitrarily chosen. pub const ADDR: &str = "127.0.0.1"; pub const BITCOIND_JSON_RPC_URL: &str = "http://127.0.0.1:8332"; +pub const MONERO_WALLET_RPC_PORT: u16 = 18083; #[cfg(feature = "tor")] pub const TOR_PORT: u16 = PORT + 1; @@ -70,10 +72,12 @@ async fn main() -> Result<()> { } let url = Url::parse(BITCOIND_JSON_RPC_URL).expect("failed to parse url"); - let bitcoin_wallet = Wallet::new("alice", &url) + let bitcoin_wallet = bitcoin::Wallet::new("alice", &url) .await .expect("failed to create bitcoin wallet"); + let monero_wallet = Arc::new(monero::Wallet::localhost(MONERO_WALLET_RPC_PORT)); + let redeem = bitcoin_wallet .new_address() .await @@ -83,7 +87,8 @@ async fn main() -> Result<()> { .await .expect("failed to get new punish address"); - swap_as_alice(alice.clone(), redeem, punish).await?; + let bitcoin_wallet = Arc::new(bitcoin_wallet); + swap_as_alice(bitcoin_wallet, monero_wallet, alice.clone(), redeem, punish).await?; } else { info!("running swap node as Bob ..."); @@ -94,7 +99,7 @@ async fn main() -> Result<()> { let alice_address = multiaddr(&alice_address)?; let url = Url::parse(BITCOIND_JSON_RPC_URL).expect("failed to parse url"); - let bitcoin_wallet = Wallet::new("bob", &url) + let bitcoin_wallet = bitcoin::Wallet::new("bob", &url) .await .expect("failed to create bitcoin wallet"); @@ -135,24 +140,34 @@ async fn create_tor_service( } async fn swap_as_alice( + bitcoin_wallet: Arc, + monero_wallet: Arc, addr: Multiaddr, - redeem: bitcoin::Address, - punish: bitcoin::Address, + redeem: ::bitcoin::Address, + punish: ::bitcoin::Address, ) -> Result<()> { #[cfg(not(feature = "tor"))] { - alice::swap(addr, None, redeem, punish).await + alice::swap(bitcoin_wallet, monero_wallet, addr, None, redeem, punish).await } #[cfg(feature = "tor")] { - alice::swap(addr, Some(PORT), redeem, punish).await + alice::swap( + bitcoin_wallet, + monero_wallet, + addr, + Some(PORT), + redeem, + punish, + ) + .await } } async fn swap_as_bob( sats: u64, alice: Multiaddr, - refund: bitcoin::Address, + refund: ::bitcoin::Address, wallet: W, ) -> Result<()> where diff --git a/swap/src/monero.rs b/swap/src/monero.rs new file mode 100644 index 00000000..02e30118 --- /dev/null +++ b/swap/src/monero.rs @@ -0,0 +1,132 @@ +use anyhow::Result; +use async_trait::async_trait; +use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _}; +use monero::{Address, Network, PrivateKey}; +use monero_harness::rpc::wallet; +use std::{str::FromStr, time::Duration}; + +pub use xmr_btc::monero::{ + Amount, CreateWalletForOutput, InsufficientFunds, PrivateViewKey, PublicKey, PublicViewKey, + Transfer, TransferProof, TxHash, WatchForTransfer, *, +}; + +pub struct Wallet(pub wallet::Client); + +impl Wallet { + pub fn localhost(port: u16) -> Self { + Self(wallet::Client::localhost(port)) + } + + /// Get the balance of the primary account. + pub async fn get_balance(&self) -> Result { + let amount = self.0.get_balance(0).await?; + + Ok(Amount::from_piconero(amount)) + } +} + +#[async_trait] +impl Transfer for Wallet { + async fn transfer( + &self, + public_spend_key: PublicKey, + public_view_key: PublicViewKey, + amount: Amount, + ) -> Result<(TransferProof, Amount)> { + let destination_address = + Address::standard(Network::Mainnet, public_spend_key, public_view_key.into()); + + let res = self + .0 + .transfer(0, amount.as_piconero(), &destination_address.to_string()) + .await?; + + let tx_hash = TxHash(res.tx_hash); + let tx_key = PrivateKey::from_str(&res.tx_key)?; + + let fee = Amount::from_piconero(res.fee); + + Ok((TransferProof::new(tx_hash, tx_key), fee)) + } +} + +#[async_trait] +impl CreateWalletForOutput for Wallet { + async fn create_and_load_wallet_for_output( + &self, + private_spend_key: PrivateKey, + private_view_key: PrivateViewKey, + ) -> Result<()> { + let public_spend_key = PublicKey::from_private_key(&private_spend_key); + let public_view_key = PublicKey::from_private_key(&private_view_key.into()); + + let address = Address::standard(Network::Mainnet, public_spend_key, public_view_key); + + let _ = self + .0 + .generate_from_keys( + &address.to_string(), + &private_spend_key.to_string(), + &PrivateKey::from(private_view_key).to_string(), + ) + .await?; + + Ok(()) + } +} + +#[async_trait] +impl WatchForTransfer for Wallet { + async fn watch_for_transfer( + &self, + public_spend_key: PublicKey, + public_view_key: PublicViewKey, + transfer_proof: TransferProof, + expected_amount: Amount, + expected_confirmations: u32, + ) -> Result<(), InsufficientFunds> { + enum Error { + TxNotFound, + InsufficientConfirmations, + InsufficientFunds { expected: Amount, actual: Amount }, + } + + let address = Address::standard(Network::Mainnet, public_spend_key, public_view_key.into()); + + let res = (|| async { + // NOTE: Currently, this is conflating IO errors with the transaction not being + // in the blockchain yet, or not having enough confirmations on it. All these + // errors warrant a retry, but the strategy should probably differ per case + let proof = self + .0 + .check_tx_key( + &String::from(transfer_proof.tx_hash()), + &transfer_proof.tx_key().to_string(), + &address.to_string(), + ) + .await + .map_err(|_| backoff::Error::Transient(Error::TxNotFound))?; + + if proof.received != expected_amount.as_piconero() { + return Err(backoff::Error::Permanent(Error::InsufficientFunds { + expected: expected_amount, + actual: Amount::from_piconero(proof.received), + })); + } + + if proof.confirmations < expected_confirmations { + return Err(backoff::Error::Transient(Error::InsufficientConfirmations)); + } + + Ok(proof) + }) + .retry(ConstantBackoff::new(Duration::from_secs(1))) + .await; + + if let Err(Error::InsufficientFunds { expected, actual }) = res { + return Err(InsufficientFunds { expected, actual }); + }; + + Ok(()) + } +} diff --git a/swap/src/network/request_response.rs b/swap/src/network/request_response.rs index f8d44deb..709013b6 100644 --- a/swap/src/network/request_response.rs +++ b/swap/src/network/request_response.rs @@ -35,7 +35,6 @@ pub enum AliceToBob { Amounts(SwapAmounts), Message0(alice::Message0), Message1(alice::Message1), - EmptyResponse, // This is sent back as response to Message2 from Bob. Message2(alice::Message2), } diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 95e12ed6..2f06824b 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -24,7 +24,7 @@ use std::{ sync::Arc, time::Duration, }; -use tokio::time::timeout; +use tokio::{sync::Mutex, time::timeout}; use tracing::error; pub mod message; @@ -62,7 +62,7 @@ pub trait ReceiveBitcoinRedeemEncsig { /// The argument `bitcoin_tx_lock_timeout` is used to determine how long we will /// wait for Bob, the counterparty, to lock up the bitcoin. pub fn action_generator( - mut network: N, + network: Arc>, bitcoin_client: Arc, // TODO: Replace this with a new, slimmer struct? State3 { @@ -86,7 +86,7 @@ pub fn action_generator( bitcoin_tx_lock_timeout: u64, ) -> GenBoxed where - N: ReceiveBitcoinRedeemEncsig + Send + Sync + 'static, + N: ReceiveBitcoinRedeemEncsig + Send + 'static, B: bitcoin::BlockHeight + bitcoin::TransactionBlockHeight + bitcoin::WatchForRawTransaction @@ -158,19 +158,24 @@ where // TODO: Watch for LockXmr using watch-only wallet. Doing so will prevent Alice // from cancelling/refunding unnecessarily. - let tx_redeem_encsig = match select( - network.receive_bitcoin_redeem_encsig(), - poll_until_btc_has_expired.clone(), - ) - .await - { - Either::Left((encsig, _)) => encsig, - Either::Right(_) => { - return Err(SwapFailed::AfterXmrLock { - reason: Reason::BtcExpired, - tx_lock_height, - }) - } + let tx_redeem_encsig = { + let mut guard = network.as_ref().lock().await; + let tx_redeem_encsig = match select( + guard.receive_bitcoin_redeem_encsig(), + poll_until_btc_has_expired.clone(), + ) + .await + { + Either::Left((encsig, _)) => encsig, + Either::Right(_) => { + return Err(SwapFailed::AfterXmrLock { + reason: Reason::BtcExpired, + tx_lock_height, + }) + } + }; + + tx_redeem_encsig }; let (signed_tx_redeem, tx_redeem_txid) = { diff --git a/xmr-btc/src/alice/message.rs b/xmr-btc/src/alice/message.rs index 30958a83..b82f6f9f 100644 --- a/xmr-btc/src/alice/message.rs +++ b/xmr-btc/src/alice/message.rs @@ -31,7 +31,7 @@ pub struct Message1 { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Message2 { - pub(crate) tx_lock_proof: monero::TransferProof, + pub tx_lock_proof: monero::TransferProof, } impl_try_from_parent_enum!(Message0, Message); diff --git a/xmr-btc/tests/on_chain.rs b/xmr-btc/tests/on_chain.rs index f7293011..0d897b7a 100644 --- a/xmr-btc/tests/on_chain.rs +++ b/xmr-btc/tests/on_chain.rs @@ -1,7 +1,5 @@ pub mod harness; -use std::{convert::TryInto, sync::Arc}; - use anyhow::Result; use async_trait::async_trait; use futures::{ @@ -16,7 +14,9 @@ use harness::{ }; use monero_harness::Monero; use rand::rngs::OsRng; +use std::{convert::TryInto, sync::Arc}; use testcontainers::clients::Cli; +use tokio::sync::Mutex; use tracing::info; use tracing_subscriber::util::SubscriberInitExt; use xmr_btc::{ @@ -102,7 +102,7 @@ impl Default for BobBehaviour { } async fn swap_as_alice( - network: AliceNetwork, + network: Arc>, // FIXME: It would be more intuitive to have a single network/transport struct instead of // splitting into two, but Rust ownership rules make this tedious mut sender: Sender, @@ -274,6 +274,8 @@ async fn on_chain_happy_path() { let (alice_network, bob_sender) = Network::::new(); let (bob_network, alice_sender) = Network::::new(); + let alice_network = Arc::new(Mutex::new(alice_network)); + try_join( swap_as_alice( alice_network, @@ -365,6 +367,8 @@ async fn on_chain_both_refund_if_alice_never_redeems() { let (alice_network, bob_sender) = Network::::new(); let (bob_network, alice_sender) = Network::::new(); + let alice_network = Arc::new(Mutex::new(alice_network)); + try_join( swap_as_alice( alice_network, @@ -460,6 +464,8 @@ async fn on_chain_alice_punishes_if_bob_never_acts_after_fund() { let (alice_network, bob_sender) = Network::::new(); let (bob_network, alice_sender) = Network::::new(); + let alice_network = Arc::new(Mutex::new(alice_network)); + let alice_swap = swap_as_alice( alice_network, alice_sender, From a4e4c27bee8cbff95531b1d0da03578f7eca761e Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Tue, 27 Oct 2020 13:26:40 +1100 Subject: [PATCH 02/21] Execute Bob's on-chain protocol after handshake Co-authored-by: Tobin C. Harding --- swap/src/alice.rs | 27 +++++-- swap/src/alice/message3.rs | 94 ++++++++++++++++++++++++ swap/src/bitcoin.rs | 2 + swap/src/bob.rs | 106 ++++++++++++++++++++++----- swap/src/bob/message3.rs | 84 +++++++++++++++++++++ swap/src/main.rs | 30 +++++--- swap/src/network/request_response.rs | 2 + xmr-btc/src/bob.rs | 27 ++++--- xmr-btc/src/bob/message.rs | 4 +- 9 files changed, 329 insertions(+), 47 deletions(-) create mode 100644 swap/src/alice/message3.rs create mode 100644 swap/src/bob/message3.rs diff --git a/swap/src/alice.rs b/swap/src/alice.rs index 64ff4660..1b7a2505 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -18,10 +18,13 @@ mod amounts; mod message0; mod message1; mod message2; +mod message3; -use self::{amounts::*, message0::*, message1::*, message2::*}; +use self::{amounts::*, message0::*, message1::*, message2::*, message3::*}; use crate::{ - bitcoin, monero, + bitcoin, + bitcoin::TX_LOCK_MINE_TIMEOUT, + monero, network::{ peer_tracker::{self, PeerTracker}, request_response::AliceToBob, @@ -117,7 +120,6 @@ pub async fn swap( ); swarm.set_state0(state0.clone()); - // TODO: Can we verify message 0 before calling this so we never fail? let state1 = state0.receive(message0).expect("failed to receive msg 0"); let (state2, channel) = match swarm.next().await { @@ -146,8 +148,12 @@ pub async fn swap( channel: Some(channel), })); - let mut action_generator = - action_generator(network.clone(), bitcoin_wallet.clone(), state3, 3600); + let mut action_generator = action_generator( + network.clone(), + bitcoin_wallet.clone(), + state3, + TX_LOCK_MINE_TIMEOUT, + ); loop { let state = action_generator.async_resume().await; @@ -242,6 +248,7 @@ pub enum OutEvent { msg: bob::Message2, channel: ResponseChannel, }, + Message3(bob::Message3), } impl From for OutEvent { @@ -284,6 +291,14 @@ impl From for OutEvent { } } +impl From for OutEvent { + fn from(event: message3::OutEvent) -> Self { + match event { + message3::OutEvent::Msg(msg) => OutEvent::Message3(msg), + } + } +} + /// A `NetworkBehaviour` that represents an XMR/BTC swap node as Alice. #[derive(NetworkBehaviour)] #[behaviour(out_event = "OutEvent", event_process = false)] @@ -294,6 +309,7 @@ pub struct Alice { message0: Message0, message1: Message1, message2: Message2, + message3: Message3, #[behaviour(ignore)] identity: Keypair, } @@ -347,6 +363,7 @@ impl Default for Alice { message0: Message0::default(), message1: Message1::default(), message2: Message2::default(), + message3: Message3::default(), identity, } } diff --git a/swap/src/alice/message3.rs b/swap/src/alice/message3.rs new file mode 100644 index 00000000..42e1a600 --- /dev/null +++ b/swap/src/alice/message3.rs @@ -0,0 +1,94 @@ +use libp2p::{ + request_response::{ + handler::RequestProtocol, ProtocolSupport, RequestResponse, RequestResponseConfig, + RequestResponseEvent, RequestResponseMessage, + }, + swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}, + NetworkBehaviour, +}; +use std::{ + collections::VecDeque, + task::{Context, Poll}, + time::Duration, +}; +use tracing::{debug, error}; + +use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; +use xmr_btc::bob; + +#[derive(Debug)] +pub enum OutEvent { + Msg(bob::Message3), +} + +/// A `NetworkBehaviour` that represents receiving of message 3 from Bob. +#[derive(NetworkBehaviour)] +#[behaviour(out_event = "OutEvent", poll_method = "poll")] +#[allow(missing_debug_implementations)] +pub struct Message3 { + rr: RequestResponse, + #[behaviour(ignore)] + events: VecDeque, +} + +impl Message3 { + fn poll( + &mut self, + _: &mut Context<'_>, + _: &mut impl PollParameters, + ) -> Poll, OutEvent>> { + if let Some(event) = self.events.pop_front() { + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); + } + + Poll::Pending + } +} + +impl Default for Message3 { + fn default() -> Self { + let timeout = Duration::from_secs(TIMEOUT); + let mut config = RequestResponseConfig::default(); + config.set_request_timeout(timeout); + + Self { + rr: RequestResponse::new( + Codec::default(), + vec![(Protocol, ProtocolSupport::Full)], + config, + ), + events: Default::default(), + } + } +} + +impl NetworkBehaviourEventProcess> for Message3 { + fn inject_event(&mut self, event: RequestResponseEvent) { + match event { + RequestResponseEvent::Message { + message: + RequestResponseMessage::Request { + request, channel, .. + }, + .. + } => match request { + BobToAlice::Message3(msg) => { + self.events.push_back(OutEvent::Msg(msg)); + // Send back empty response so that the request/response protocol completes. + self.rr.send_response(channel, AliceToBob::Message3); + } + other => debug!("got request: {:?}", other), + }, + RequestResponseEvent::Message { + message: RequestResponseMessage::Response { .. }, + .. + } => panic!("Alice should not get a Response"), + RequestResponseEvent::InboundFailure { error, .. } => { + error!("Inbound failure: {:?}", error); + } + RequestResponseEvent::OutboundFailure { error, .. } => { + error!("Outbound failure: {:?}", error); + } + } + } +} diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index fb0f63e5..f59c5eda 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -12,6 +12,8 @@ use xmr_btc::bitcoin::{ TransactionBlockHeight, TxLock, Txid, WatchForRawTransaction, }; +pub const TX_LOCK_MINE_TIMEOUT: u64 = 3600; + // This is cut'n'paste from xmr_btc/tests/harness/wallet/bitcoin.rs #[derive(Debug)] diff --git a/swap/src/bob.rs b/swap/src/bob.rs index 9bcb302e..97327448 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -1,46 +1,61 @@ //! Run an XMR/BTC swap in the role of Bob. //! Bob holds BTC and wishes receive XMR. use anyhow::Result; +use async_trait::async_trait; use futures::{ channel::mpsc::{Receiver, Sender}, StreamExt, }; +use genawaiter::GeneratorState; use libp2p::{core::identity::Keypair, Multiaddr, NetworkBehaviour, PeerId}; use rand::rngs::OsRng; -use std::{process, thread}; +use std::{process, sync::Arc}; +use tokio::sync::Mutex; use tracing::{debug, info}; mod amounts; mod message0; mod message1; mod message2; +mod message3; -use self::{amounts::*, message0::*, message1::*, message2::*}; +use self::{amounts::*, message0::*, message1::*, message2::*, message3::*}; use crate::{ + bitcoin, + bitcoin::TX_LOCK_MINE_TIMEOUT, + monero, network::{ peer_tracker::{self, PeerTracker}, transport, TokioExecutor, }, - Cmd, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, + Cmd, Never, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; use xmr_btc::{ alice, - bitcoin::{BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock}, - bob::{self, State0}, + bitcoin::{BroadcastSignedTransaction, EncryptedSignature, SignTxLock}, + bob::{self, action_generator, ReceiveTransferProof, State0}, + monero::CreateWalletForOutput, }; // FIXME: This whole function is horrible, needs total re-write. -pub async fn swap( +pub async fn swap( + bitcoin_wallet: Arc, + monero_wallet: Arc, btc: u64, addr: Multiaddr, mut cmd_tx: Sender, mut rsp_rx: Receiver, refund_address: ::bitcoin::Address, - wallet: W, -) -> Result<()> -where - W: BuildTxLockPsbt + SignTxLock + BroadcastSignedTransaction + Send + Sync + 'static, -{ +) -> Result<()> { + struct Network(Swarm); + + #[async_trait] + impl ReceiveTransferProof for Network { + async fn receive_transfer_proof(&mut self) -> monero::TransferProof { + todo!() + } + } + let mut swarm = new_swarm()?; libp2p::Swarm::dial_addr(&mut swarm, addr)?; @@ -82,11 +97,7 @@ where swarm.send_message0(alice.clone(), state0.next_message(rng)); let state1 = match swarm.next().await { - OutEvent::Message0(msg) => { - // TODO: Verify the response message before calling receive() and handle any - // error gracefully. - state0.receive(&wallet, msg).await? - } + OutEvent::Message0(msg) => state0.receive(bitcoin_wallet.as_ref(), msg).await?, other => panic!("unexpected event: {:?}", other), }; @@ -102,8 +113,53 @@ where info!("Handshake complete, we now have State2 for Bob."); - thread::park(); - Ok(()) + let network = Arc::new(Mutex::new(Network(swarm))); + + let mut action_generator = action_generator( + network.clone(), + monero_wallet.clone(), + bitcoin_wallet.clone(), + state2, + TX_LOCK_MINE_TIMEOUT, + ); + + loop { + let state = action_generator.async_resume().await; + + info!("resumed execution of bob generator, got: {:?}", state); + + match state { + GeneratorState::Yielded(bob::Action::LockBtc(tx_lock)) => { + let signed_tx_lock = bitcoin_wallet.sign_tx_lock(tx_lock).await?; + let _ = bitcoin_wallet + .broadcast_signed_transaction(signed_tx_lock) + .await?; + } + GeneratorState::Yielded(bob::Action::SendBtcRedeemEncsig(tx_redeem_encsig)) => { + let mut guard = network.as_ref().lock().await; + guard.0.send_message3(alice.clone(), tx_redeem_encsig); + } + GeneratorState::Yielded(bob::Action::CreateXmrWalletForOutput { + spend_key, + view_key, + }) => { + monero_wallet + .create_and_load_wallet_for_output(spend_key, view_key) + .await?; + } + GeneratorState::Yielded(bob::Action::CancelBtc(tx_cancel)) => { + let _ = bitcoin_wallet + .broadcast_signed_transaction(tx_cancel) + .await?; + } + GeneratorState::Yielded(bob::Action::RefundBtc(tx_refund)) => { + let _ = bitcoin_wallet + .broadcast_signed_transaction(tx_refund) + .await?; + } + GeneratorState::Complete(()) => return Ok(()), + } + } } pub type Swarm = libp2p::Swarm; @@ -188,6 +244,12 @@ impl From for OutEvent { } } +impl From for OutEvent { + fn from(_: Never) -> Self { + panic!("not ever") + } +} + /// A `NetworkBehaviour` that represents an XMR/BTC swap node as Bob. #[derive(NetworkBehaviour)] #[behaviour(out_event = "OutEvent", event_process = false)] @@ -198,6 +260,7 @@ pub struct Bob { message0: Message0, message1: Message1, message2: Message2, + message3: Message3, #[behaviour(ignore)] identity: Keypair, } @@ -233,6 +296,12 @@ impl Bob { self.message2.send(alice, msg) } + /// Sends Bob's fourth message to Alice. + pub fn send_message3(&mut self, alice: PeerId, tx_redeem_encsig: EncryptedSignature) { + let msg = bob::Message3 { tx_redeem_encsig }; + self.message3.send(alice, msg) + } + /// Returns Alice's peer id if we are connected. pub fn peer_id_of_alice(&self) -> Option { self.pt.counterparty_peer_id() @@ -249,6 +318,7 @@ impl Default for Bob { message0: Message0::default(), message1: Message1::default(), message2: Message2::default(), + message3: Message3::default(), identity, } } diff --git a/swap/src/bob/message3.rs b/swap/src/bob/message3.rs new file mode 100644 index 00000000..b9567e81 --- /dev/null +++ b/swap/src/bob/message3.rs @@ -0,0 +1,84 @@ +use libp2p::{ + request_response::{ + handler::RequestProtocol, ProtocolSupport, RequestResponse, RequestResponseConfig, + RequestResponseEvent, RequestResponseMessage, + }, + swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}, + NetworkBehaviour, PeerId, +}; +use std::{ + task::{Context, Poll}, + time::Duration, +}; +use tracing::{debug, error}; + +use crate::{ + network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}, + Never, +}; +use xmr_btc::bob; + +/// A `NetworkBehaviour` that represents sending message 3 to Alice. +#[derive(NetworkBehaviour)] +#[behaviour(out_event = "Never", poll_method = "poll")] +#[allow(missing_debug_implementations)] +pub struct Message3 { + rr: RequestResponse, +} + +impl Message3 { + pub fn send(&mut self, alice: PeerId, msg: bob::Message3) { + let msg = BobToAlice::Message3(msg); + let _id = self.rr.send_request(&alice, msg); + } + + // TODO: Do we need a custom implementation if we are not bubbling any out + // events? + fn poll( + &mut self, + _: &mut Context<'_>, + _: &mut impl PollParameters, + ) -> Poll, Never>> { + Poll::Pending + } +} + +impl Default for Message3 { + fn default() -> Self { + let timeout = Duration::from_secs(TIMEOUT); + let mut config = RequestResponseConfig::default(); + config.set_request_timeout(timeout); + + Self { + rr: RequestResponse::new( + Codec::default(), + vec![(Protocol, ProtocolSupport::Full)], + config, + ), + } + } +} + +impl NetworkBehaviourEventProcess> for Message3 { + fn inject_event(&mut self, event: RequestResponseEvent) { + match event { + RequestResponseEvent::Message { + message: RequestResponseMessage::Request { .. }, + .. + } => panic!("Bob should never get a request from Alice"), + RequestResponseEvent::Message { + message: RequestResponseMessage::Response { response, .. }, + .. + } => match response { + AliceToBob::Message3 => debug!("Alice correctly responded to message 3"), + other => debug!("unexpected response: {:?}", other), + }, + RequestResponseEvent::InboundFailure { error, .. } => { + error!("Inbound failure: {:?}", error); + } + RequestResponseEvent::OutboundFailure { error, .. } => { + error!("Outbound failure: {:?}", error); + } + } + } +} diff --git a/swap/src/main.rs b/swap/src/main.rs index 6dc1410c..7c93159d 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -26,7 +26,6 @@ mod trace; use cli::Options; use swap::{alice, bitcoin, bob, monero, Cmd, Rsp, SwapAmounts}; -use xmr_btc::bitcoin::{BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock}; // TODO: Add root seed file instead of generating new seed each run. // TODO: Remove all instances of the todo! macro @@ -92,28 +91,31 @@ async fn main() -> Result<()> { } else { info!("running swap node as Bob ..."); - let alice_address = match opt.alice_address { + let alice = match opt.alice_address { Some(addr) => addr, None => bail!("Address required to dial"), }; - let alice_address = multiaddr(&alice_address)?; + let alice = multiaddr(&alice)?; let url = Url::parse(BITCOIND_JSON_RPC_URL).expect("failed to parse url"); let bitcoin_wallet = bitcoin::Wallet::new("bob", &url) .await .expect("failed to create bitcoin wallet"); + let monero_wallet = Arc::new(monero::Wallet::localhost(MONERO_WALLET_RPC_PORT)); + let refund = bitcoin_wallet .new_address() .await .expect("failed to get new address"); + let bitcoin_wallet = Arc::new(bitcoin_wallet); match (opt.piconeros, opt.satoshis) { (Some(_), Some(_)) => bail!("Please supply only a single amount to swap"), (None, None) => bail!("Please supply an amount to swap"), (Some(_picos), _) => todo!("support starting with picos"), (None, Some(sats)) => { - swap_as_bob(sats, alice_address, refund, bitcoin_wallet).await?; + swap_as_bob(bitcoin_wallet, monero_wallet, sats, alice, refund).await?; } }; } @@ -164,18 +166,24 @@ async fn swap_as_alice( } } -async fn swap_as_bob( +async fn swap_as_bob( + bitcoin_wallet: Arc, + monero_wallet: Arc, sats: u64, alice: Multiaddr, refund: ::bitcoin::Address, - wallet: W, -) -> Result<()> -where - W: BuildTxLockPsbt + SignTxLock + BroadcastSignedTransaction + Send + Sync + 'static, -{ +) -> Result<()> { let (cmd_tx, mut cmd_rx) = mpsc::channel(1); let (mut rsp_tx, rsp_rx) = mpsc::channel(1); - tokio::spawn(bob::swap(sats, alice, cmd_tx, rsp_rx, refund, wallet)); + tokio::spawn(bob::swap( + bitcoin_wallet, + monero_wallet, + sats, + alice, + cmd_tx, + rsp_rx, + refund, + )); loop { let read = cmd_rx.next().await; diff --git a/swap/src/network/request_response.rs b/swap/src/network/request_response.rs index 709013b6..697fa052 100644 --- a/swap/src/network/request_response.rs +++ b/swap/src/network/request_response.rs @@ -26,6 +26,7 @@ pub enum BobToAlice { Message0(bob::Message0), Message1(bob::Message1), Message2(bob::Message2), + Message3(bob::Message3), } /// Messages Alice sends to Bob. @@ -36,6 +37,7 @@ pub enum AliceToBob { Message0(alice::Message0), Message1(alice::Message1), Message2(alice::Message2), + Message3, // empty response } #[derive(Debug, Clone, Copy, Default)] diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index ac1180c9..f57d062c 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -28,7 +28,7 @@ use std::{ sync::Arc, time::Duration, }; -use tokio::time::timeout; +use tokio::{sync::Mutex, time::timeout}; use tracing::error; pub mod message; @@ -62,7 +62,7 @@ pub trait ReceiveTransferProof { /// The argument `bitcoin_tx_lock_timeout` is used to determine how long we will /// wait for Bob, the caller of this function, to lock up the bitcoin. pub fn action_generator( - mut network: N, + network: Arc>, monero_client: Arc, bitcoin_client: Arc, // TODO: Replace this with a new, slimmer struct? @@ -85,7 +85,7 @@ pub fn action_generator( bitcoin_tx_lock_timeout: u64, ) -> GenBoxed where - N: ReceiveTransferProof + Send + Sync + 'static, + N: ReceiveTransferProof + Send + 'static, M: monero::WatchForTransfer + Send + Sync + 'static, B: bitcoin::BlockHeight + bitcoin::TransactionBlockHeight @@ -140,14 +140,19 @@ where .shared(); pin_mut!(poll_until_btc_has_expired); - let transfer_proof = match select( - network.receive_transfer_proof(), - poll_until_btc_has_expired.clone(), - ) - .await - { - Either::Left((proof, _)) => proof, - Either::Right(_) => return Err(SwapFailed::AfterBtcLock(Reason::BtcExpired)), + let transfer_proof = { + let mut guard = network.as_ref().lock().await; + let transfer_proof = match select( + guard.receive_transfer_proof(), + poll_until_btc_has_expired.clone(), + ) + .await + { + Either::Left((proof, _)) => proof, + Either::Right(_) => return Err(SwapFailed::AfterBtcLock(Reason::BtcExpired)), + }; + + transfer_proof }; let S_b_monero = monero::PublicKey::from_private_key(&monero::PrivateKey::from_scalar( diff --git a/xmr-btc/src/bob/message.rs b/xmr-btc/src/bob/message.rs index b6bed872..178c0218 100644 --- a/xmr-btc/src/bob/message.rs +++ b/xmr-btc/src/bob/message.rs @@ -33,9 +33,9 @@ pub struct Message2 { pub(crate) tx_cancel_sig: Signature, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Message3 { - pub(crate) tx_redeem_encsig: EncryptedSignature, + pub tx_redeem_encsig: EncryptedSignature, } impl_try_from_parent_enum!(Message0, Message); From 3f43581da7094ab4ca1ace28ae3b3ff17c9274c5 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Tue, 27 Oct 2020 17:18:19 +1100 Subject: [PATCH 03/21] Implement traits Receive{BitcoinRedeemEncsig, TransferProof} Unfortunately, I had to put the wrap the swarm in Alice's `Network` struct in an `Arc>` in order to be able to use `backoff` to control the retry mechanism. This is because the stream of events cannot be turned into a `SharedFuture` (unlike Bob's). It would be good to find an alternative solution. --- swap/src/alice.rs | 40 +++++++++++++++++++++++++++++++-------- swap/src/bob.rs | 30 +++++++++++++++++++++++++---- xmr-btc/tests/on_chain.rs | 20 +++++++------------- 3 files changed, 65 insertions(+), 25 deletions(-) diff --git a/swap/src/alice.rs b/swap/src/alice.rs index 1b7a2505..7149e06f 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -2,6 +2,7 @@ //! Alice holds XMR and wishes receive BTC. use anyhow::Result; use async_trait::async_trait; +use backoff::{future::FutureOperation as _, ExponentialBackoff}; use genawaiter::GeneratorState; use libp2p::{ core::{identity::Keypair, Multiaddr}, @@ -50,17 +51,20 @@ pub async fn swap( punish_address: ::bitcoin::Address, ) -> Result<()> { struct Network { - swarm: Swarm, + swarm: Arc>, channel: Option>, } impl Network { - pub fn send_message2(&mut self, proof: monero::TransferProof) { + pub async fn send_message2(&mut self, proof: monero::TransferProof) { match self.channel.take() { None => warn!("Channel not found, did you call this twice?"), - Some(channel) => self.swarm.send_message2(channel, alice::Message2 { - tx_lock_proof: proof, - }), + Some(channel) => { + let mut guard = self.swarm.lock().await; + guard.send_message2(channel, alice::Message2 { + tx_lock_proof: proof, + }) + } } } } @@ -68,7 +72,27 @@ pub async fn swap( #[async_trait] impl ReceiveBitcoinRedeemEncsig for Network { async fn receive_bitcoin_redeem_encsig(&mut self) -> xmr_btc::bitcoin::EncryptedSignature { - todo!() + #[derive(Debug)] + struct UnexpectedMessage; + + (|| async { + let mut guard = self.swarm.lock().await; + let encsig = match guard.next().await { + OutEvent::Message3(msg) => msg.tx_redeem_encsig, + other => { + warn!("Expected Bob's Message3, got: {:?}", other); + return Err(backoff::Error::Transient(UnexpectedMessage)); + } + }; + + Result::<_, backoff::Error>::Ok(encsig) + }) + .retry(ExponentialBackoff { + max_elapsed_time: None, + ..Default::default() + }) + .await + .expect("transient errors to be retried") } } @@ -144,7 +168,7 @@ pub async fn swap( info!("Handshake complete, we now have State3 for Alice."); let network = Arc::new(Mutex::new(Network { - swarm, + swarm: Arc::new(Mutex::new(swarm)), channel: Some(channel), })); @@ -171,7 +195,7 @@ pub async fn swap( .await?; let mut guard = network.as_ref().lock().await; - guard.send_message2(transfer_proof); + guard.send_message2(transfer_proof).await; } GeneratorState::Yielded(Action::RedeemBtc(tx)) => { diff --git a/swap/src/bob.rs b/swap/src/bob.rs index 97327448..4194a7bd 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -2,16 +2,17 @@ //! Bob holds BTC and wishes receive XMR. use anyhow::Result; use async_trait::async_trait; +use backoff::{future::FutureOperation as _, ExponentialBackoff}; use futures::{ channel::mpsc::{Receiver, Sender}, - StreamExt, + FutureExt, StreamExt, }; use genawaiter::GeneratorState; use libp2p::{core::identity::Keypair, Multiaddr, NetworkBehaviour, PeerId}; use rand::rngs::OsRng; use std::{process, sync::Arc}; use tokio::sync::Mutex; -use tracing::{debug, info}; +use tracing::{debug, info, warn}; mod amounts; mod message0; @@ -52,7 +53,28 @@ pub async fn swap( #[async_trait] impl ReceiveTransferProof for Network { async fn receive_transfer_proof(&mut self) -> monero::TransferProof { - todo!() + #[derive(Debug)] + struct UnexpectedMessage; + + let future = self.0.next().shared(); + + (|| async { + let proof = match future.clone().await { + OutEvent::Message2(msg) => msg.tx_lock_proof, + other => { + warn!("Expected Alice's Message2, got: {:?}", other); + return Err(backoff::Error::Transient(UnexpectedMessage)); + } + }; + + Result::<_, backoff::Error>::Ok(proof) + }) + .retry(ExponentialBackoff { + max_elapsed_time: None, + ..Default::default() + }) + .await + .expect("transient errors to be retried") } } @@ -193,7 +215,7 @@ fn new_swarm() -> Result { } #[allow(clippy::large_enum_variant)] -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum OutEvent { ConnectionEstablished(PeerId), Amounts(SwapAmounts), diff --git a/xmr-btc/tests/on_chain.rs b/xmr-btc/tests/on_chain.rs index 0d897b7a..89eaf1f1 100644 --- a/xmr-btc/tests/on_chain.rs +++ b/xmr-btc/tests/on_chain.rs @@ -168,7 +168,7 @@ async fn swap_as_alice( } async fn swap_as_bob( - network: BobNetwork, + network: Arc>, mut sender: Sender, monero_wallet: Arc, bitcoin_wallet: Arc, @@ -274,11 +274,9 @@ async fn on_chain_happy_path() { let (alice_network, bob_sender) = Network::::new(); let (bob_network, alice_sender) = Network::::new(); - let alice_network = Arc::new(Mutex::new(alice_network)); - try_join( swap_as_alice( - alice_network, + Arc::new(Mutex::new(alice_network)), alice_sender, alice_monero_wallet.clone(), alice_bitcoin_wallet.clone(), @@ -286,7 +284,7 @@ async fn on_chain_happy_path() { alice, ), swap_as_bob( - bob_network, + Arc::new(Mutex::new(bob_network)), bob_sender, bob_monero_wallet.clone(), bob_bitcoin_wallet.clone(), @@ -367,11 +365,9 @@ async fn on_chain_both_refund_if_alice_never_redeems() { let (alice_network, bob_sender) = Network::::new(); let (bob_network, alice_sender) = Network::::new(); - let alice_network = Arc::new(Mutex::new(alice_network)); - try_join( swap_as_alice( - alice_network, + Arc::new(Mutex::new(alice_network)), alice_sender, alice_monero_wallet.clone(), alice_bitcoin_wallet.clone(), @@ -382,7 +378,7 @@ async fn on_chain_both_refund_if_alice_never_redeems() { alice, ), swap_as_bob( - bob_network, + Arc::new(Mutex::new(bob_network)), bob_sender, bob_monero_wallet.clone(), bob_bitcoin_wallet.clone(), @@ -464,10 +460,8 @@ async fn on_chain_alice_punishes_if_bob_never_acts_after_fund() { let (alice_network, bob_sender) = Network::::new(); let (bob_network, alice_sender) = Network::::new(); - let alice_network = Arc::new(Mutex::new(alice_network)); - let alice_swap = swap_as_alice( - alice_network, + Arc::new(Mutex::new(alice_network)), alice_sender, alice_monero_wallet.clone(), alice_bitcoin_wallet.clone(), @@ -475,7 +469,7 @@ async fn on_chain_alice_punishes_if_bob_never_acts_after_fund() { alice, ); let bob_swap = swap_as_bob( - bob_network, + Arc::new(Mutex::new(bob_network)), bob_sender, bob_monero_wallet.clone(), bob_bitcoin_wallet.clone(), From 9e30bd51513d018249a207f429585ae659c2d0d0 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Wed, 28 Oct 2020 10:22:09 +1100 Subject: [PATCH 04/21] Generate addresses as late as possible --- swap/src/alice.rs | 5 +++-- swap/src/bob.rs | 3 ++- swap/src/main.rs | 38 ++++++-------------------------------- 3 files changed, 11 insertions(+), 35 deletions(-) diff --git a/swap/src/alice.rs b/swap/src/alice.rs index 7149e06f..b394436c 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -47,8 +47,6 @@ pub async fn swap( monero_wallet: Arc, listen: Multiaddr, local_port: Option, - redeem_address: ::bitcoin::Address, - punish_address: ::bitcoin::Address, ) -> Result<()> { struct Network { swarm: Arc>, @@ -131,6 +129,9 @@ pub async fn swap( None => unreachable!("should have amounts by here"), }; + let redeem_address = bitcoin_wallet.as_ref().new_address().await?; + let punish_address = redeem_address.clone(); + // TODO: Pass this in using let rng = &mut OsRng; let state0 = State0::new( diff --git a/swap/src/bob.rs b/swap/src/bob.rs index 4194a7bd..2da7350c 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -46,7 +46,6 @@ pub async fn swap( addr: Multiaddr, mut cmd_tx: Sender, mut rsp_rx: Receiver, - refund_address: ::bitcoin::Address, ) -> Result<()> { struct Network(Swarm); @@ -106,6 +105,8 @@ pub async fn swap( other => panic!("unexpected event: {:?}", other), }; + let refund_address = bitcoin_wallet.new_address().await?; + // TODO: Pass this in using let rng = &mut OsRng; let state0 = State0::new( diff --git a/swap/src/main.rs b/swap/src/main.rs index 7c93159d..a813aa62 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -74,20 +74,11 @@ async fn main() -> Result<()> { let bitcoin_wallet = bitcoin::Wallet::new("alice", &url) .await .expect("failed to create bitcoin wallet"); + let bitcoin_wallet = Arc::new(bitcoin_wallet); let monero_wallet = Arc::new(monero::Wallet::localhost(MONERO_WALLET_RPC_PORT)); - let redeem = bitcoin_wallet - .new_address() - .await - .expect("failed to get new redeem address"); - let punish = bitcoin_wallet - .new_address() - .await - .expect("failed to get new punish address"); - - let bitcoin_wallet = Arc::new(bitcoin_wallet); - swap_as_alice(bitcoin_wallet, monero_wallet, alice.clone(), redeem, punish).await?; + swap_as_alice(bitcoin_wallet, monero_wallet, alice.clone()).await?; } else { info!("running swap node as Bob ..."); @@ -101,21 +92,16 @@ async fn main() -> Result<()> { let bitcoin_wallet = bitcoin::Wallet::new("bob", &url) .await .expect("failed to create bitcoin wallet"); + let bitcoin_wallet = Arc::new(bitcoin_wallet); let monero_wallet = Arc::new(monero::Wallet::localhost(MONERO_WALLET_RPC_PORT)); - let refund = bitcoin_wallet - .new_address() - .await - .expect("failed to get new address"); - - let bitcoin_wallet = Arc::new(bitcoin_wallet); match (opt.piconeros, opt.satoshis) { (Some(_), Some(_)) => bail!("Please supply only a single amount to swap"), (None, None) => bail!("Please supply an amount to swap"), (Some(_picos), _) => todo!("support starting with picos"), (None, Some(sats)) => { - swap_as_bob(bitcoin_wallet, monero_wallet, sats, alice, refund).await?; + swap_as_bob(bitcoin_wallet, monero_wallet, sats, alice).await?; } }; } @@ -145,24 +131,14 @@ async fn swap_as_alice( bitcoin_wallet: Arc, monero_wallet: Arc, addr: Multiaddr, - redeem: ::bitcoin::Address, - punish: ::bitcoin::Address, ) -> Result<()> { #[cfg(not(feature = "tor"))] { - alice::swap(bitcoin_wallet, monero_wallet, addr, None, redeem, punish).await + alice::swap(bitcoin_wallet, monero_wallet, addr, None).await } #[cfg(feature = "tor")] { - alice::swap( - bitcoin_wallet, - monero_wallet, - addr, - Some(PORT), - redeem, - punish, - ) - .await + alice::swap(bitcoin_wallet, monero_wallet, addr, Some(PORT)).await } } @@ -171,7 +147,6 @@ async fn swap_as_bob( monero_wallet: Arc, sats: u64, alice: Multiaddr, - refund: ::bitcoin::Address, ) -> Result<()> { let (cmd_tx, mut cmd_rx) = mpsc::channel(1); let (mut rsp_tx, rsp_rx) = mpsc::channel(1); @@ -182,7 +157,6 @@ async fn swap_as_bob( alice, cmd_tx, rsp_rx, - refund, )); loop { From 9384b0cf3c4c66017d0cd0f7e1e465dd28359b36 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Wed, 28 Oct 2020 11:46:45 +1100 Subject: [PATCH 05/21] [WIP] Swap app e2e test --- swap/Cargo.toml | 1 + swap/src/alice.rs | 1 + swap/src/alice/amounts.rs | 19 ++--------- swap/src/alice/message0.rs | 3 +- swap/src/bob.rs | 3 +- swap/tests/e2e.rs | 70 ++++++++++++++++++++++++++++++++++++++ xmr-btc/src/alice.rs | 3 +- xmr-btc/tests/on_chain.rs | 6 ---- 8 files changed, 81 insertions(+), 25 deletions(-) create mode 100644 swap/tests/e2e.rs diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 3a2f295c..6b57107e 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -47,6 +47,7 @@ hyper = "0.13" port_check = "0.1" spectral = "0.6" tempfile = "3" +testcontainers = "0.10" [features] default = [] diff --git a/swap/src/alice.rs b/swap/src/alice.rs index b394436c..d9a48a6e 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -356,6 +356,7 @@ impl Alice { /// Message0 gets sent within the network layer using this state0. pub fn set_state0(&mut self, state: State0) { + info!("Set state 0"); let _ = self.message0.set_state(state); } diff --git a/swap/src/alice/amounts.rs b/swap/src/alice/amounts.rs index 39396861..ffa87cf7 100644 --- a/swap/src/alice/amounts.rs +++ b/swap/src/alice/amounts.rs @@ -1,11 +1,10 @@ -use anyhow::Result; use libp2p::{ request_response::{ - handler::RequestProtocol, ProtocolSupport, RequestId, RequestResponse, - RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, ResponseChannel, + handler::RequestProtocol, ProtocolSupport, RequestResponse, RequestResponseConfig, + RequestResponseEvent, RequestResponseMessage, ResponseChannel, }, swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}, - NetworkBehaviour, PeerId, + NetworkBehaviour, }; use std::{ collections::VecDeque, @@ -40,18 +39,6 @@ impl Amounts { self.rr.send_response(channel, msg); } - pub async fn request_amounts( - &mut self, - alice: PeerId, - btc: ::bitcoin::Amount, - ) -> Result { - let msg = BobToAlice::AmountsFromBtc(btc); - let id = self.rr.send_request(&alice, msg); - debug!("Request sent to: {}", alice); - - Ok(id) - } - fn poll( &mut self, _: &mut Context<'_>, diff --git a/swap/src/alice/message0.rs b/swap/src/alice/message0.rs index 460c63e4..1ec54365 100644 --- a/swap/src/alice/message0.rs +++ b/swap/src/alice/message0.rs @@ -13,7 +13,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::{debug, error}; +use tracing::{debug, error, info}; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; use xmr_btc::{alice::State0, bob}; @@ -87,6 +87,7 @@ impl NetworkBehaviourEventProcess> .. } => match request { BobToAlice::Message0(msg) => { + info!("Got Alice's first message"); let response = match &self.state { None => panic!("No state, did you forget to set it?"), Some(state) => { diff --git a/swap/src/bob.rs b/swap/src/bob.rs index 2da7350c..4c01bbd4 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -306,7 +306,8 @@ impl Bob { /// Sends Bob's first message to Alice. pub fn send_message0(&mut self, alice: PeerId, msg: bob::Message0) { - self.message0.send(alice, msg) + self.message0.send(alice, msg); + info!("Sent first message to Alice"); } /// Sends Bob's second message to Alice. diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs new file mode 100644 index 00000000..8ed81970 --- /dev/null +++ b/swap/tests/e2e.rs @@ -0,0 +1,70 @@ +use bitcoin_harness::Bitcoind; +use futures::{channel::mpsc, future::try_join}; +use libp2p::Multiaddr; +use monero_harness::Monero; +use std::sync::Arc; +use swap::{alice, bob}; +use testcontainers::clients::Cli; +use tracing_subscriber::util::SubscriberInitExt; + +#[tokio::test] +async fn swap() { + let _guard = tracing_subscriber::fmt() + .with_env_filter("info") + .with_ansi(false) + .set_default(); + + let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" + .parse() + .expect("failed to parse Alice's address"); + + let cli = Cli::default(); + let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); + let _ = bitcoind.init(5).await; + + let btc = bitcoin::Amount::ONE_BTC; + let _btc_alice = bitcoin::Amount::ZERO; + let btc_bob = btc * 10; + + let xmr = 1_000_000_000_000; + let xmr_alice = xmr * 10; + let xmr_bob = 0; + + let alice_btc_wallet = Arc::new( + swap::bitcoin::Wallet::new("alice", &bitcoind.node_url) + .await + .unwrap(), + ); + let bob_btc_wallet = Arc::new( + swap::bitcoin::Wallet::new("bob", &bitcoind.node_url) + .await + .unwrap(), + ); + bitcoind + .mint(bob_btc_wallet.0.new_address().await.unwrap(), btc_bob) + .await + .unwrap(); + + let (monero, _container) = Monero::new(&cli).unwrap(); + monero.init(xmr_alice, xmr_bob).await.unwrap(); + + let alice_xmr_wallet = Arc::new(swap::monero::Wallet(monero.alice_wallet_rpc_client())); + let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.bob_wallet_rpc_client())); + + let alice_swap = alice::swap(alice_btc_wallet, alice_xmr_wallet, alice_multiaddr.clone()); + + let (cmd_tx, mut _cmd_rx) = mpsc::channel(1); + let (mut rsp_tx, rsp_rx) = mpsc::channel(1); + let bob_swap = bob::swap( + bob_btc_wallet, + bob_xmr_wallet, + btc.as_sat(), + alice_multiaddr, + cmd_tx, + rsp_rx, + ); + + rsp_tx.try_send(swap::Rsp::VerifiedAmounts).unwrap(); + + try_join(alice_swap, bob_swap).await.unwrap(); +} diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 2f06824b..43d79633 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -25,7 +25,7 @@ use std::{ time::Duration, }; use tokio::{sync::Mutex, time::timeout}; -use tracing::error; +use tracing::{error, info}; pub mod message; pub use message::{Message, Message0, Message1, Message2}; @@ -524,6 +524,7 @@ impl State0 { } pub fn next_message(&self, rng: &mut R) -> Message0 { + info!("Producing first message"); let dleq_proof_s_a = cross_curve_dleq::Proof::new(rng, &self.s_a); Message0 { diff --git a/xmr-btc/tests/on_chain.rs b/xmr-btc/tests/on_chain.rs index 89eaf1f1..e61db076 100644 --- a/xmr-btc/tests/on_chain.rs +++ b/xmr-btc/tests/on_chain.rs @@ -18,7 +18,6 @@ use std::{convert::TryInto, sync::Arc}; use testcontainers::clients::Cli; use tokio::sync::Mutex; use tracing::info; -use tracing_subscriber::util::SubscriberInitExt; use xmr_btc::{ alice::{self, ReceiveBitcoinRedeemEncsig}, bitcoin::{BroadcastSignedTransaction, EncryptedSignature, SignTxLock}, @@ -419,11 +418,6 @@ async fn on_chain_both_refund_if_alice_never_redeems() { #[tokio::test] async fn on_chain_alice_punishes_if_bob_never_acts_after_fund() { - let _guard = tracing_subscriber::fmt() - .with_env_filter("info") - .with_ansi(false) - .set_default(); - let cli = Cli::default(); let (monero, _container) = Monero::new(&cli).unwrap(); let bitcoind = init_bitcoind(&cli).await; From be377253037607bd0708fdb8f5f40f3602e1cf00 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 28 Oct 2020 14:19:47 +1100 Subject: [PATCH 06/21] monero-harness: Use tracing --- monero-harness/src/rpc/monerod.rs | 7 +------ monero-harness/src/rpc/wallet.rs | 4 +--- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/monero-harness/src/rpc/monerod.rs b/monero-harness/src/rpc/monerod.rs index 9e32c645..aa00a62d 100644 --- a/monero-harness/src/rpc/monerod.rs +++ b/monero-harness/src/rpc/monerod.rs @@ -6,12 +6,7 @@ use crate::{ use anyhow::Result; use reqwest::Url; use serde::{Deserialize, Serialize}; - -// #[cfg(not(test))] -// use tracing::debug; -// -// #[cfg(test)] -use std::eprintln as debug; +use tracing::debug; /// RPC client for monerod and monero-wallet-rpc. #[derive(Debug, Clone)] diff --git a/monero-harness/src/rpc/wallet.rs b/monero-harness/src/rpc/wallet.rs index 7afaa2c8..a81702ac 100644 --- a/monero-harness/src/rpc/wallet.rs +++ b/monero-harness/src/rpc/wallet.rs @@ -3,9 +3,7 @@ use crate::rpc::{Request, Response}; use anyhow::Result; use reqwest::Url; use serde::{Deserialize, Serialize}; - -// TODO: Either use println! directly or import tracing also? -use std::println as debug; +use tracing::debug; /// JSON RPC client for monero-wallet-rpc. #[derive(Debug)] From 2bd4977fe4be2c4bc7bc169d3794db78e3ea2257 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 29 Oct 2020 09:39:55 +1100 Subject: [PATCH 07/21] Increase read buffer size Our messages are quite big, increase the read buffer to 1 megabyte to accommodate them. --- swap/src/network/request_response.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/swap/src/network/request_response.rs b/swap/src/network/request_response.rs index 697fa052..6ae8c00c 100644 --- a/swap/src/network/request_response.rs +++ b/swap/src/network/request_response.rs @@ -13,6 +13,9 @@ use xmr_btc::{alice, bob, monero}; /// Time to wait for a response back once we send a request. pub const TIMEOUT: u64 = 3600; // One hour. +/// Message receive buffer. +const BUF_SIZE: usize = 1024 * 1024; + // TODO: Think about whether there is a better way to do this, e.g., separate // Codec for each Message and a macro that implements them. @@ -62,7 +65,7 @@ impl RequestResponseCodec for Codec { where T: AsyncRead + Unpin + Send, { - let message = upgrade::read_one(io, 1024) + let message = upgrade::read_one(io, BUF_SIZE) .await .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; let mut de = serde_json::Deserializer::from_slice(&message); @@ -79,7 +82,7 @@ impl RequestResponseCodec for Codec { where T: AsyncRead + Unpin + Send, { - let message = upgrade::read_one(io, 1024) + let message = upgrade::read_one(io, BUF_SIZE) .await .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; let mut de = serde_json::Deserializer::from_slice(&message); From 39f86154ce4e811994ca65b87caca716bd10c5d1 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 29 Oct 2020 09:48:54 +1100 Subject: [PATCH 08/21] Use serde_cbor instead of serde_json We have serde trait implementations that use `serde_cbor` which is a superset of josn. We cannot use `serder_json` to ser/deser these objects. --- swap/src/network/request_response.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/swap/src/network/request_response.rs b/swap/src/network/request_response.rs index 6ae8c00c..4950b818 100644 --- a/swap/src/network/request_response.rs +++ b/swap/src/network/request_response.rs @@ -68,8 +68,9 @@ impl RequestResponseCodec for Codec { let message = upgrade::read_one(io, BUF_SIZE) .await .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - let mut de = serde_json::Deserializer::from_slice(&message); - let msg = BobToAlice::deserialize(&mut de)?; + let mut de = serde_cbor::Deserializer::from_slice(&message); + let msg = BobToAlice::deserialize(&mut de) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; Ok(msg) } @@ -85,8 +86,9 @@ impl RequestResponseCodec for Codec { let message = upgrade::read_one(io, BUF_SIZE) .await .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - let mut de = serde_json::Deserializer::from_slice(&message); - let msg = AliceToBob::deserialize(&mut de)?; + let mut de = serde_cbor::Deserializer::from_slice(&message); + let msg = AliceToBob::deserialize(&mut de) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; Ok(msg) } @@ -100,7 +102,8 @@ impl RequestResponseCodec for Codec { where T: AsyncWrite + Unpin + Send, { - let bytes = serde_json::to_vec(&req)?; + let bytes = + serde_cbor::to_vec(&req).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; upgrade::write_one(io, &bytes).await?; Ok(()) @@ -115,7 +118,8 @@ impl RequestResponseCodec for Codec { where T: AsyncWrite + Unpin + Send, { - let bytes = serde_json::to_vec(&res)?; + let bytes = + serde_cbor::to_vec(&res).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; upgrade::write_one(io, &bytes).await?; Ok(()) From c464555f5ee3cc3c7876ff2f34c7851596f586c1 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 29 Oct 2020 09:53:46 +1100 Subject: [PATCH 09/21] Enable trace output --- swap/src/bob.rs | 2 ++ swap/tests/e2e.rs | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/swap/src/bob.rs b/swap/src/bob.rs index 4c01bbd4..aefb3478 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -77,6 +77,8 @@ pub async fn swap( } } + debug!("swapping ..."); + let mut swarm = new_swarm()?; libp2p::Swarm::dial_addr(&mut swarm, addr)?; diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 8ed81970..07651538 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -1,6 +1,7 @@ use bitcoin_harness::Bitcoind; use futures::{channel::mpsc, future::try_join}; use libp2p::Multiaddr; +use log::LevelFilter; use monero_harness::Monero; use std::sync::Arc; use swap::{alice, bob}; @@ -10,7 +11,12 @@ use tracing_subscriber::util::SubscriberInitExt; #[tokio::test] async fn swap() { let _guard = tracing_subscriber::fmt() - .with_env_filter("info") + .with_env_filter(format!( + "swap={},xmr_btc={},libp2p={}", + LevelFilter::Debug, + LevelFilter::Debug, + LevelFilter::Debug + )) .with_ansi(false) .set_default(); From 3e2f0b74a2a9c1187690a0ccb9d92d2514ad45d9 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 29 Oct 2020 10:22:31 +1100 Subject: [PATCH 10/21] Do not trace events meant for other NBs --- swap/src/alice.rs | 3 +++ swap/src/alice/amounts.rs | 9 ++++----- swap/src/alice/message0.rs | 9 ++++----- swap/src/alice/message1.rs | 9 ++++----- swap/src/alice/message2.rs | 9 ++++----- swap/src/alice/message3.rs | 9 ++++----- swap/src/bob/amounts.rs | 11 ++++++----- swap/src/bob/message0.rs | 11 ++++++----- swap/src/bob/message1.rs | 11 ++++++----- swap/src/bob/message2.rs | 11 ++++++----- swap/src/bob/message3.rs | 9 +++++---- swap/tests/e2e.rs | 7 ++++++- 12 files changed, 58 insertions(+), 50 deletions(-) diff --git a/swap/src/alice.rs b/swap/src/alice.rs index d9a48a6e..dd095be3 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -94,6 +94,8 @@ pub async fn swap( } } + debug!("swapping ..."); + let mut swarm = new_swarm(listen, local_port)?; let message0: bob::Message0; let mut last_amounts: Option = None; @@ -112,6 +114,7 @@ pub async fn swap( swarm.send_amounts(channel, amounts); } OutEvent::Message0(msg) => { + debug!("got message 0 from Bob"); // We don't want Bob to be able to crash us by sending an out of // order message. Keep looping if Bob has not requested amounts. if last_amounts.is_some() { diff --git a/swap/src/alice/amounts.rs b/swap/src/alice/amounts.rs index ffa87cf7..70dd6b8a 100644 --- a/swap/src/alice/amounts.rs +++ b/swap/src/alice/amounts.rs @@ -11,7 +11,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::{debug, error}; +use tracing::error; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; @@ -79,12 +79,11 @@ impl NetworkBehaviourEventProcess> request, channel, .. }, .. - } => match request { - BobToAlice::AmountsFromBtc(btc) => { + } => { + if let BobToAlice::AmountsFromBtc(btc) = request { self.events.push_back(OutEvent::Btc { btc, channel }) } - other => debug!("got request: {:?}", other), - }, + } RequestResponseEvent::Message { message: RequestResponseMessage::Response { .. }, .. diff --git a/swap/src/alice/message0.rs b/swap/src/alice/message0.rs index 1ec54365..20ca05db 100644 --- a/swap/src/alice/message0.rs +++ b/swap/src/alice/message0.rs @@ -13,7 +13,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::{debug, error, info}; +use tracing::{error, info}; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; use xmr_btc::{alice::State0, bob}; @@ -85,8 +85,8 @@ impl NetworkBehaviourEventProcess> request, channel, .. }, .. - } => match request { - BobToAlice::Message0(msg) => { + } => { + if let BobToAlice::Message0(msg) = request { info!("Got Alice's first message"); let response = match &self.state { None => panic!("No state, did you forget to set it?"), @@ -98,8 +98,7 @@ impl NetworkBehaviourEventProcess> self.rr.send_response(channel, response); self.events.push_back(OutEvent::Msg(msg)); } - other => debug!("got request: {:?}", other), - }, + } RequestResponseEvent::Message { message: RequestResponseMessage::Response { .. }, .. diff --git a/swap/src/alice/message1.rs b/swap/src/alice/message1.rs index 27df9e20..d9831439 100644 --- a/swap/src/alice/message1.rs +++ b/swap/src/alice/message1.rs @@ -11,7 +11,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::{debug, error}; +use tracing::error; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; use xmr_btc::bob; @@ -81,12 +81,11 @@ impl NetworkBehaviourEventProcess> request, channel, .. }, .. - } => match request { - BobToAlice::Message1(msg) => { + } => { + if let BobToAlice::Message1(msg) = request { self.events.push_back(OutEvent::Msg { msg, channel }); } - other => debug!("got request: {:?}", other), - }, + } RequestResponseEvent::Message { message: RequestResponseMessage::Response { .. }, .. diff --git a/swap/src/alice/message2.rs b/swap/src/alice/message2.rs index 28e5fc10..584a16bf 100644 --- a/swap/src/alice/message2.rs +++ b/swap/src/alice/message2.rs @@ -11,7 +11,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::{debug, error}; +use tracing::error; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; use xmr_btc::bob; @@ -81,12 +81,11 @@ impl NetworkBehaviourEventProcess> request, channel, .. }, .. - } => match request { - BobToAlice::Message2(msg) => { + } => { + if let BobToAlice::Message2(msg) = request { self.events.push_back(OutEvent::Msg { msg, channel }); } - other => debug!("got request: {:?}", other), - }, + } RequestResponseEvent::Message { message: RequestResponseMessage::Response { .. }, .. diff --git a/swap/src/alice/message3.rs b/swap/src/alice/message3.rs index 42e1a600..4ee197c3 100644 --- a/swap/src/alice/message3.rs +++ b/swap/src/alice/message3.rs @@ -11,7 +11,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::{debug, error}; +use tracing::error; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; use xmr_btc::bob; @@ -71,14 +71,13 @@ impl NetworkBehaviourEventProcess> request, channel, .. }, .. - } => match request { - BobToAlice::Message3(msg) => { + } => { + if let BobToAlice::Message3(msg) = request { self.events.push_back(OutEvent::Msg(msg)); // Send back empty response so that the request/response protocol completes. self.rr.send_response(channel, AliceToBob::Message3); } - other => debug!("got request: {:?}", other), - }, + } RequestResponseEvent::Message { message: RequestResponseMessage::Response { .. }, .. diff --git a/swap/src/bob/amounts.rs b/swap/src/bob/amounts.rs index 46c71920..6c4c0395 100644 --- a/swap/src/bob/amounts.rs +++ b/swap/src/bob/amounts.rs @@ -12,7 +12,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::{debug, error}; +use tracing::error; use crate::{ network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}, @@ -83,10 +83,11 @@ impl NetworkBehaviourEventProcess> RequestResponseEvent::Message { message: RequestResponseMessage::Response { response, .. }, .. - } => match response { - AliceToBob::Amounts(p) => self.events.push_back(OutEvent::Amounts(p)), - other => debug!("got response: {:?}", other), - }, + } => { + if let AliceToBob::Amounts(p) = response { + self.events.push_back(OutEvent::Amounts(p)); + } + } RequestResponseEvent::InboundFailure { error, .. } => { error!("Inbound failure: {:?}", error); } diff --git a/swap/src/bob/message0.rs b/swap/src/bob/message0.rs index 268244a1..ff6c9420 100644 --- a/swap/src/bob/message0.rs +++ b/swap/src/bob/message0.rs @@ -11,7 +11,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::{debug, error}; +use tracing::error; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; use xmr_btc::{alice, bob}; @@ -77,10 +77,11 @@ impl NetworkBehaviourEventProcess> RequestResponseEvent::Message { message: RequestResponseMessage::Response { response, .. }, .. - } => match response { - AliceToBob::Message0(msg) => self.events.push_back(OutEvent::Msg(msg)), - other => debug!("got response: {:?}", other), - }, + } => { + if let AliceToBob::Message0(msg) = response { + self.events.push_back(OutEvent::Msg(msg)); + } + } RequestResponseEvent::InboundFailure { error, .. } => { error!("Inbound failure: {:?}", error); } diff --git a/swap/src/bob/message1.rs b/swap/src/bob/message1.rs index 9e85ce3d..0a821ec2 100644 --- a/swap/src/bob/message1.rs +++ b/swap/src/bob/message1.rs @@ -11,7 +11,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::{debug, error}; +use tracing::error; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; use xmr_btc::{alice, bob}; @@ -77,10 +77,11 @@ impl NetworkBehaviourEventProcess> RequestResponseEvent::Message { message: RequestResponseMessage::Response { response, .. }, .. - } => match response { - AliceToBob::Message1(msg) => self.events.push_back(OutEvent::Msg(msg)), - other => debug!("got response: {:?}", other), - }, + } => { + if let AliceToBob::Message1(msg) = response { + self.events.push_back(OutEvent::Msg(msg)); + } + } RequestResponseEvent::InboundFailure { error, .. } => { error!("Inbound failure: {:?}", error); } diff --git a/swap/src/bob/message2.rs b/swap/src/bob/message2.rs index 0898c583..e90755b7 100644 --- a/swap/src/bob/message2.rs +++ b/swap/src/bob/message2.rs @@ -11,7 +11,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::{debug, error}; +use tracing::error; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; use xmr_btc::{alice, bob}; @@ -77,10 +77,11 @@ impl NetworkBehaviourEventProcess> RequestResponseEvent::Message { message: RequestResponseMessage::Response { response, .. }, .. - } => match response { - AliceToBob::Message2(msg) => self.events.push_back(OutEvent::Msg(msg)), - other => debug!("unexpected response: {:?}", other), - }, + } => { + if let AliceToBob::Message2(msg) = response { + self.events.push_back(OutEvent::Msg(msg)); + } + } RequestResponseEvent::InboundFailure { error, .. } => { error!("Inbound failure: {:?}", error); } diff --git a/swap/src/bob/message3.rs b/swap/src/bob/message3.rs index b9567e81..53648460 100644 --- a/swap/src/bob/message3.rs +++ b/swap/src/bob/message3.rs @@ -69,10 +69,11 @@ impl NetworkBehaviourEventProcess> RequestResponseEvent::Message { message: RequestResponseMessage::Response { response, .. }, .. - } => match response { - AliceToBob::Message3 => debug!("Alice correctly responded to message 3"), - other => debug!("unexpected response: {:?}", other), - }, + } => { + if let AliceToBob::Message3 = response { + debug!("Alice correctly responded to message 3"); + } + } RequestResponseEvent::InboundFailure { error, .. } => { error!("Inbound failure: {:?}", error); } diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 07651538..61f6aabd 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -57,7 +57,12 @@ async fn swap() { let alice_xmr_wallet = Arc::new(swap::monero::Wallet(monero.alice_wallet_rpc_client())); let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.bob_wallet_rpc_client())); - let alice_swap = alice::swap(alice_btc_wallet, alice_xmr_wallet, alice_multiaddr.clone()); + let alice_swap = alice::swap( + alice_btc_wallet, + alice_xmr_wallet, + alice_multiaddr.clone(), + None, + ); let (cmd_tx, mut _cmd_rx) = mpsc::channel(1); let (mut rsp_tx, rsp_rx) = mpsc::channel(1); From b8ab4b4eee0692f98db55426a3010db6ef9d1e72 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 29 Oct 2020 11:36:57 +1100 Subject: [PATCH 11/21] wip: add env filter --- swap/Cargo.toml | 2 +- swap/src/network/request_response.rs | 9 ++++++--- swap/tests/e2e.rs | 12 ++++-------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 6b57107e..475fb6a0 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -12,7 +12,7 @@ atty = "0.2" backoff = { version = "0.2", features = ["tokio"] } base64 = "0.12" bitcoin = { version = "0.23", features = ["rand", "use-serde"] } # TODO: Upgrade other crates in this repo to use this version. -bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "7ff30a559ab57cc3aa71189e71433ef6b2a6c3a2" } +bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "b56ac243eade11e817aa670d546892852a609e7d" } derivative = "2" futures = { version = "0.3", default-features = false } genawaiter = "0.99.1" diff --git a/swap/src/network/request_response.rs b/swap/src/network/request_response.rs index 4950b818..7026626e 100644 --- a/swap/src/network/request_response.rs +++ b/swap/src/network/request_response.rs @@ -70,7 +70,7 @@ impl RequestResponseCodec for Codec { .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; let mut de = serde_cbor::Deserializer::from_slice(&message); let msg = BobToAlice::deserialize(&mut de) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; Ok(msg) } @@ -102,8 +102,11 @@ impl RequestResponseCodec for Codec { where T: AsyncWrite + Unpin + Send, { - let bytes = - serde_cbor::to_vec(&req).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let bytes = serde_cbor::to_vec(&req).map_err(|e| { + tracing::debug!("yes Lucas we are actually here"); + io::Error::new(io::ErrorKind::InvalidData, e) + })?; + upgrade::write_one(io, &bytes).await?; Ok(()) diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 61f6aabd..8a4c0a34 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -1,7 +1,6 @@ use bitcoin_harness::Bitcoind; use futures::{channel::mpsc, future::try_join}; use libp2p::Multiaddr; -use log::LevelFilter; use monero_harness::Monero; use std::sync::Arc; use swap::{alice, bob}; @@ -11,13 +10,10 @@ use tracing_subscriber::util::SubscriberInitExt; #[tokio::test] async fn swap() { let _guard = tracing_subscriber::fmt() - .with_env_filter(format!( - "swap={},xmr_btc={},libp2p={}", - LevelFilter::Debug, - LevelFilter::Debug, - LevelFilter::Debug - )) - .with_ansi(false) + .with_env_filter( + "swap=debug,hyper=off,reqwest=off,monero-harness=info,testcontainers=info,libp2p=debug", + ) + .with_ansi(true) .set_default(); let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" From 119f2a7c543a0e965d6bdd352bbd439433dc08cb Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 29 Oct 2020 13:59:57 +1100 Subject: [PATCH 12/21] Add error logging to request_response --- swap/src/alice/amounts.rs | 1 + swap/src/alice/message0.rs | 3 ++- swap/src/network/request_response.rs | 25 ++++++++++++++++++------- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/swap/src/alice/amounts.rs b/swap/src/alice/amounts.rs index 70dd6b8a..e0fb3bcd 100644 --- a/swap/src/alice/amounts.rs +++ b/swap/src/alice/amounts.rs @@ -80,6 +80,7 @@ impl NetworkBehaviourEventProcess> }, .. } => { + tracing::debug!("amounts: Request from Bob received"); if let BobToAlice::AmountsFromBtc(btc) = request { self.events.push_back(OutEvent::Btc { btc, channel }) } diff --git a/swap/src/alice/message0.rs b/swap/src/alice/message0.rs index 20ca05db..04c5222d 100644 --- a/swap/src/alice/message0.rs +++ b/swap/src/alice/message0.rs @@ -86,8 +86,9 @@ impl NetworkBehaviourEventProcess> }, .. } => { + tracing::debug!("message0: Request from Bob received"); if let BobToAlice::Message0(msg) = request { - info!("Got Alice's first message"); + info!("Got Bob's first message"); let response = match &self.state { None => panic!("No state, did you forget to set it?"), Some(state) => { diff --git a/swap/src/network/request_response.rs b/swap/src/network/request_response.rs index 7026626e..6f033ae6 100644 --- a/swap/src/network/request_response.rs +++ b/swap/src/network/request_response.rs @@ -6,6 +6,7 @@ use libp2p::{ }; use serde::{Deserialize, Serialize}; use std::{fmt::Debug, io}; +use tracing::debug; use crate::SwapAmounts; use xmr_btc::{alice, bob, monero}; @@ -65,12 +66,15 @@ impl RequestResponseCodec for Codec { where T: AsyncRead + Unpin + Send, { + debug!("enter read_request"); let message = upgrade::read_one(io, BUF_SIZE) .await .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; let mut de = serde_cbor::Deserializer::from_slice(&message); - let msg = BobToAlice::deserialize(&mut de) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + let msg = BobToAlice::deserialize(&mut de).map_err(|e| { + tracing::debug!("serde read_request error: {:?}", e); + io::Error::new(io::ErrorKind::Other, e) + })?; Ok(msg) } @@ -83,12 +87,15 @@ impl RequestResponseCodec for Codec { where T: AsyncRead + Unpin + Send, { + debug!("enter read_response"); let message = upgrade::read_one(io, BUF_SIZE) .await .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; let mut de = serde_cbor::Deserializer::from_slice(&message); - let msg = AliceToBob::deserialize(&mut de) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let msg = AliceToBob::deserialize(&mut de).map_err(|e| { + tracing::debug!("serde read_response error: {:?}", e); + io::Error::new(io::ErrorKind::InvalidData, e) + })?; Ok(msg) } @@ -102,8 +109,9 @@ impl RequestResponseCodec for Codec { where T: AsyncWrite + Unpin + Send, { + debug!("enter write_request"); let bytes = serde_cbor::to_vec(&req).map_err(|e| { - tracing::debug!("yes Lucas we are actually here"); + tracing::debug!("serde write_request error: {:?}", e); io::Error::new(io::ErrorKind::InvalidData, e) })?; @@ -121,8 +129,11 @@ impl RequestResponseCodec for Codec { where T: AsyncWrite + Unpin + Send, { - let bytes = - serde_cbor::to_vec(&res).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + debug!("enter write_response"); + let bytes = serde_cbor::to_vec(&res).map_err(|e| { + tracing::debug!("serde write_reponse error: {:?}", e); + io::Error::new(io::ErrorKind::InvalidData, e) + })?; upgrade::write_one(io, &bytes).await?; Ok(()) From ae87c10cae3be60041b46b440d4409a5aadb9378 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Thu, 29 Oct 2020 14:00:00 +1100 Subject: [PATCH 13/21] wip: fixed message0 --- swap/src/alice.rs | 50 ++++++++++++---------- swap/src/alice/amounts.rs | 8 ++-- swap/src/alice/message0.rs | 8 ++-- swap/src/alice/message1.rs | 8 ++-- swap/src/alice/message2.rs | 8 ++-- swap/src/alice/message3.rs | 8 ++-- swap/src/bob.rs | 13 +++++- swap/src/bob/amounts.rs | 8 ++-- swap/src/bob/message0.rs | 8 ++-- swap/src/bob/message1.rs | 8 ++-- swap/src/bob/message2.rs | 8 ++-- swap/src/bob/message3.rs | 8 ++-- swap/src/network/request_response.rs | 63 ++++++++++++++++++++++------ swap/tests/e2e.rs | 2 +- 14 files changed, 130 insertions(+), 78 deletions(-) diff --git a/swap/src/alice.rs b/swap/src/alice.rs index dd095be3..1d45ca5e 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -98,6 +98,7 @@ pub async fn swap( let mut swarm = new_swarm(listen, local_port)?; let message0: bob::Message0; + let mut state0: Option = None; let mut last_amounts: Option = None; loop { @@ -112,6 +113,29 @@ pub async fn swap( // verification of message 0. last_amounts = Some(amounts); swarm.send_amounts(channel, amounts); + + let (xmr, btc) = match last_amounts { + Some(p) => (p.xmr, p.btc), + None => unreachable!("should have amounts by here"), + }; + + let redeem_address = bitcoin_wallet.as_ref().new_address().await?; + let punish_address = redeem_address.clone(); + + // TODO: Pass this in using + let rng = &mut OsRng; + let state = State0::new( + rng, + btc, + xmr, + REFUND_TIMELOCK, + PUNISH_TIMELOCK, + redeem_address, + punish_address, + ); + swarm.set_state0(state.clone()); + + state0 = Some(state) } OutEvent::Message0(msg) => { debug!("got message 0 from Bob"); @@ -127,28 +151,10 @@ pub async fn swap( }; } - let (xmr, btc) = match last_amounts { - Some(p) => (p.xmr, p.btc), - None => unreachable!("should have amounts by here"), - }; - - let redeem_address = bitcoin_wallet.as_ref().new_address().await?; - let punish_address = redeem_address.clone(); - - // TODO: Pass this in using - let rng = &mut OsRng; - let state0 = State0::new( - rng, - btc, - xmr, - REFUND_TIMELOCK, - PUNISH_TIMELOCK, - redeem_address, - punish_address, - ); - swarm.set_state0(state0.clone()); - - let state1 = state0.receive(message0).expect("failed to receive msg 0"); + let state1 = state0 + .expect("to be set") + .receive(message0) + .expect("failed to receive msg 0"); let (state2, channel) = match swarm.next().await { OutEvent::Message1 { msg, channel } => { diff --git a/swap/src/alice/amounts.rs b/swap/src/alice/amounts.rs index e0fb3bcd..85dd5b92 100644 --- a/swap/src/alice/amounts.rs +++ b/swap/src/alice/amounts.rs @@ -13,7 +13,7 @@ use std::{ }; use tracing::error; -use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; +use crate::network::request_response::{AliceToBob, AmountsProtocol, BobToAlice, Codec, TIMEOUT}; #[derive(Debug)] pub enum OutEvent { @@ -28,7 +28,7 @@ pub enum OutEvent { #[behaviour(out_event = "OutEvent", poll_method = "poll")] #[allow(missing_debug_implementations)] pub struct Amounts { - rr: RequestResponse, + rr: RequestResponse>, #[behaviour(ignore)] events: VecDeque, } @@ -43,7 +43,7 @@ impl Amounts { &mut self, _: &mut Context<'_>, _: &mut impl PollParameters, - ) -> Poll, OutEvent>> { + ) -> Poll>, OutEvent>> { if let Some(event) = self.events.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); } @@ -62,7 +62,7 @@ impl Default for Amounts { Self { rr: RequestResponse::new( Codec::default(), - vec![(Protocol, ProtocolSupport::Full)], + vec![(AmountsProtocol, ProtocolSupport::Full)], config, ), events: Default::default(), diff --git a/swap/src/alice/message0.rs b/swap/src/alice/message0.rs index 04c5222d..45bb248c 100644 --- a/swap/src/alice/message0.rs +++ b/swap/src/alice/message0.rs @@ -15,7 +15,7 @@ use std::{ }; use tracing::{error, info}; -use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; +use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message0Protocol, TIMEOUT}; use xmr_btc::{alice::State0, bob}; #[derive(Debug)] @@ -28,7 +28,7 @@ pub enum OutEvent { #[behaviour(out_event = "OutEvent", poll_method = "poll")] #[allow(missing_debug_implementations)] pub struct Message0 { - rr: RequestResponse, + rr: RequestResponse>, #[behaviour(ignore)] events: VecDeque, #[behaviour(ignore)] @@ -49,7 +49,7 @@ impl Message0 { &mut self, _: &mut Context<'_>, _: &mut impl PollParameters, - ) -> Poll, OutEvent>> { + ) -> Poll>, OutEvent>> { if let Some(event) = self.events.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); } @@ -67,7 +67,7 @@ impl Default for Message0 { Self { rr: RequestResponse::new( Codec::default(), - vec![(Protocol, ProtocolSupport::Full)], + vec![(Message0Protocol, ProtocolSupport::Full)], config, ), events: Default::default(), diff --git a/swap/src/alice/message1.rs b/swap/src/alice/message1.rs index d9831439..11e578ad 100644 --- a/swap/src/alice/message1.rs +++ b/swap/src/alice/message1.rs @@ -13,7 +13,7 @@ use std::{ }; use tracing::error; -use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; +use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message1Protocol, TIMEOUT}; use xmr_btc::bob; #[derive(Debug)] @@ -31,7 +31,7 @@ pub enum OutEvent { #[behaviour(out_event = "OutEvent", poll_method = "poll")] #[allow(missing_debug_implementations)] pub struct Message1 { - rr: RequestResponse, + rr: RequestResponse>, #[behaviour(ignore)] events: VecDeque, } @@ -46,7 +46,7 @@ impl Message1 { &mut self, _: &mut Context<'_>, _: &mut impl PollParameters, - ) -> Poll, OutEvent>> { + ) -> Poll>, OutEvent>> { if let Some(event) = self.events.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); } @@ -64,7 +64,7 @@ impl Default for Message1 { Self { rr: RequestResponse::new( Codec::default(), - vec![(Protocol, ProtocolSupport::Full)], + vec![(Message1Protocol, ProtocolSupport::Full)], config, ), events: Default::default(), diff --git a/swap/src/alice/message2.rs b/swap/src/alice/message2.rs index 584a16bf..e1493ba4 100644 --- a/swap/src/alice/message2.rs +++ b/swap/src/alice/message2.rs @@ -13,7 +13,7 @@ use std::{ }; use tracing::error; -use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; +use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message2Protocol, TIMEOUT}; use xmr_btc::bob; #[derive(Debug)] @@ -31,7 +31,7 @@ pub enum OutEvent { #[behaviour(out_event = "OutEvent", poll_method = "poll")] #[allow(missing_debug_implementations)] pub struct Message2 { - rr: RequestResponse, + rr: RequestResponse>, #[behaviour(ignore)] events: VecDeque, } @@ -46,7 +46,7 @@ impl Message2 { &mut self, _: &mut Context<'_>, _: &mut impl PollParameters, - ) -> Poll, OutEvent>> { + ) -> Poll>, OutEvent>> { if let Some(event) = self.events.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); } @@ -64,7 +64,7 @@ impl Default for Message2 { Self { rr: RequestResponse::new( Codec::default(), - vec![(Protocol, ProtocolSupport::Full)], + vec![(Message2Protocol, ProtocolSupport::Full)], config, ), events: Default::default(), diff --git a/swap/src/alice/message3.rs b/swap/src/alice/message3.rs index 4ee197c3..48afcc5d 100644 --- a/swap/src/alice/message3.rs +++ b/swap/src/alice/message3.rs @@ -13,7 +13,7 @@ use std::{ }; use tracing::error; -use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; +use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message3Protocol, TIMEOUT}; use xmr_btc::bob; #[derive(Debug)] @@ -26,7 +26,7 @@ pub enum OutEvent { #[behaviour(out_event = "OutEvent", poll_method = "poll")] #[allow(missing_debug_implementations)] pub struct Message3 { - rr: RequestResponse, + rr: RequestResponse>, #[behaviour(ignore)] events: VecDeque, } @@ -36,7 +36,7 @@ impl Message3 { &mut self, _: &mut Context<'_>, _: &mut impl PollParameters, - ) -> Poll, OutEvent>> { + ) -> Poll>, OutEvent>> { if let Some(event) = self.events.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); } @@ -54,7 +54,7 @@ impl Default for Message3 { Self { rr: RequestResponse::new( Codec::default(), - vec![(Protocol, ProtocolSupport::Full)], + vec![(Message3Protocol, ProtocolSupport::Full)], config, ), events: Default::default(), diff --git a/swap/src/bob.rs b/swap/src/bob.rs index aefb3478..a09e99b6 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -90,7 +90,7 @@ pub async fn swap( swarm.request_amounts(alice.clone(), btc); - let (btc, xmr) = match swarm.next().await { + let (btc_amount, xmr) = match swarm.next().await { OutEvent::Amounts(amounts) => { debug!("Got amounts from Alice: {:?}", amounts); let cmd = Cmd::VerifyAmounts(amounts); @@ -107,13 +107,22 @@ pub async fn swap( other => panic!("unexpected event: {:?}", other), }; + swarm.request_amounts(alice.clone(), btc); + + match swarm.next().await { + OutEvent::Amounts(amounts) => { + debug!("Got amounts from Alice: {:?}", amounts); + } + other => panic!("unexpected event: {:?}", other), + }; + let refund_address = bitcoin_wallet.new_address().await?; // TODO: Pass this in using let rng = &mut OsRng; let state0 = State0::new( rng, - btc, + btc_amount, xmr, REFUND_TIMELOCK, PUNISH_TIMELOCK, diff --git a/swap/src/bob/amounts.rs b/swap/src/bob/amounts.rs index 6c4c0395..f2fd0266 100644 --- a/swap/src/bob/amounts.rs +++ b/swap/src/bob/amounts.rs @@ -15,7 +15,7 @@ use std::{ use tracing::error; use crate::{ - network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}, + network::request_response::{AliceToBob, AmountsProtocol, BobToAlice, Codec, TIMEOUT}, SwapAmounts, }; @@ -29,7 +29,7 @@ pub enum OutEvent { #[behaviour(out_event = "OutEvent", poll_method = "poll")] #[allow(missing_debug_implementations)] pub struct Amounts { - rr: RequestResponse, + rr: RequestResponse>, #[behaviour(ignore)] events: VecDeque, } @@ -46,7 +46,7 @@ impl Amounts { &mut self, _: &mut Context<'_>, _: &mut impl PollParameters, - ) -> Poll, OutEvent>> { + ) -> Poll>, OutEvent>> { if let Some(event) = self.events.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); } @@ -65,7 +65,7 @@ impl Default for Amounts { Self { rr: RequestResponse::new( Codec::default(), - vec![(Protocol, ProtocolSupport::Full)], + vec![(AmountsProtocol, ProtocolSupport::Full)], config, ), events: Default::default(), diff --git a/swap/src/bob/message0.rs b/swap/src/bob/message0.rs index ff6c9420..455b7452 100644 --- a/swap/src/bob/message0.rs +++ b/swap/src/bob/message0.rs @@ -13,7 +13,7 @@ use std::{ }; use tracing::error; -use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; +use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message0Protocol, TIMEOUT}; use xmr_btc::{alice, bob}; #[derive(Debug)] @@ -26,7 +26,7 @@ pub enum OutEvent { #[behaviour(out_event = "OutEvent", poll_method = "poll")] #[allow(missing_debug_implementations)] pub struct Message0 { - rr: RequestResponse, + rr: RequestResponse>, #[behaviour(ignore)] events: VecDeque, } @@ -41,7 +41,7 @@ impl Message0 { &mut self, _: &mut Context<'_>, _: &mut impl PollParameters, - ) -> Poll, OutEvent>> { + ) -> Poll>, OutEvent>> { if let Some(event) = self.events.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); } @@ -59,7 +59,7 @@ impl Default for Message0 { Self { rr: RequestResponse::new( Codec::default(), - vec![(Protocol, ProtocolSupport::Full)], + vec![(Message0Protocol, ProtocolSupport::Full)], config, ), events: Default::default(), diff --git a/swap/src/bob/message1.rs b/swap/src/bob/message1.rs index 0a821ec2..e3a67de9 100644 --- a/swap/src/bob/message1.rs +++ b/swap/src/bob/message1.rs @@ -13,7 +13,7 @@ use std::{ }; use tracing::error; -use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; +use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message1Protocol, TIMEOUT}; use xmr_btc::{alice, bob}; #[derive(Debug)] @@ -26,7 +26,7 @@ pub enum OutEvent { #[behaviour(out_event = "OutEvent", poll_method = "poll")] #[allow(missing_debug_implementations)] pub struct Message1 { - rr: RequestResponse, + rr: RequestResponse>, #[behaviour(ignore)] events: VecDeque, } @@ -41,7 +41,7 @@ impl Message1 { &mut self, _: &mut Context<'_>, _: &mut impl PollParameters, - ) -> Poll, OutEvent>> { + ) -> Poll>, OutEvent>> { if let Some(event) = self.events.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); } @@ -59,7 +59,7 @@ impl Default for Message1 { Self { rr: RequestResponse::new( Codec::default(), - vec![(Protocol, ProtocolSupport::Full)], + vec![(Message1Protocol, ProtocolSupport::Full)], config, ), events: Default::default(), diff --git a/swap/src/bob/message2.rs b/swap/src/bob/message2.rs index e90755b7..b141b240 100644 --- a/swap/src/bob/message2.rs +++ b/swap/src/bob/message2.rs @@ -13,7 +13,7 @@ use std::{ }; use tracing::error; -use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}; +use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message2Protocol, TIMEOUT}; use xmr_btc::{alice, bob}; #[derive(Debug)] @@ -26,7 +26,7 @@ pub enum OutEvent { #[behaviour(out_event = "OutEvent", poll_method = "poll")] #[allow(missing_debug_implementations)] pub struct Message2 { - rr: RequestResponse, + rr: RequestResponse>, #[behaviour(ignore)] events: VecDeque, } @@ -41,7 +41,7 @@ impl Message2 { &mut self, _: &mut Context<'_>, _: &mut impl PollParameters, - ) -> Poll, OutEvent>> { + ) -> Poll>, OutEvent>> { if let Some(event) = self.events.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); } @@ -59,7 +59,7 @@ impl Default for Message2 { Self { rr: RequestResponse::new( Codec::default(), - vec![(Protocol, ProtocolSupport::Full)], + vec![(Message2Protocol, ProtocolSupport::Full)], config, ), events: VecDeque::default(), diff --git a/swap/src/bob/message3.rs b/swap/src/bob/message3.rs index 53648460..02fcd0c4 100644 --- a/swap/src/bob/message3.rs +++ b/swap/src/bob/message3.rs @@ -13,7 +13,7 @@ use std::{ use tracing::{debug, error}; use crate::{ - network::request_response::{AliceToBob, BobToAlice, Codec, Protocol, TIMEOUT}, + network::request_response::{AliceToBob, BobToAlice, Codec, Message3Protocol, TIMEOUT}, Never, }; use xmr_btc::bob; @@ -23,7 +23,7 @@ use xmr_btc::bob; #[behaviour(out_event = "Never", poll_method = "poll")] #[allow(missing_debug_implementations)] pub struct Message3 { - rr: RequestResponse, + rr: RequestResponse>, } impl Message3 { @@ -38,7 +38,7 @@ impl Message3 { &mut self, _: &mut Context<'_>, _: &mut impl PollParameters, - ) -> Poll, Never>> { + ) -> Poll>, Never>> { Poll::Pending } } @@ -52,7 +52,7 @@ impl Default for Message3 { Self { rr: RequestResponse::new( Codec::default(), - vec![(Protocol, ProtocolSupport::Full)], + vec![(Message3Protocol, ProtocolSupport::Full)], config, ), } diff --git a/swap/src/network/request_response.rs b/swap/src/network/request_response.rs index 6f033ae6..a5950e14 100644 --- a/swap/src/network/request_response.rs +++ b/swap/src/network/request_response.rs @@ -5,8 +5,7 @@ use libp2p::{ request_response::{ProtocolName, RequestResponseCodec}, }; use serde::{Deserialize, Serialize}; -use std::{fmt::Debug, io}; -use tracing::debug; +use std::{fmt::Debug, io, marker::PhantomData}; use crate::SwapAmounts; use xmr_btc::{alice, bob, monero}; @@ -45,20 +44,61 @@ pub enum AliceToBob { } #[derive(Debug, Clone, Copy, Default)] -pub struct Protocol; +pub struct AmountsProtocol; -impl ProtocolName for Protocol { +#[derive(Debug, Clone, Copy, Default)] +pub struct Message0Protocol; + +#[derive(Debug, Clone, Copy, Default)] +pub struct Message1Protocol; + +#[derive(Debug, Clone, Copy, Default)] +pub struct Message2Protocol; + +#[derive(Debug, Clone, Copy, Default)] +pub struct Message3Protocol; + +impl ProtocolName for AmountsProtocol { fn protocol_name(&self) -> &[u8] { - b"/xmr/btc/1.0.0" + b"/xmr/btc/amounts/1.0.0" + } +} + +impl ProtocolName for Message0Protocol { + fn protocol_name(&self) -> &[u8] { + b"/xmr/btc/message0/1.0.0" + } +} + +impl ProtocolName for Message1Protocol { + fn protocol_name(&self) -> &[u8] { + b"/xmr/btc/message1/1.0.0" + } +} + +impl ProtocolName for Message2Protocol { + fn protocol_name(&self) -> &[u8] { + b"/xmr/btc/message2/1.0.0" + } +} + +impl ProtocolName for Message3Protocol { + fn protocol_name(&self) -> &[u8] { + b"/xmr/btc/message3/1.0.0" } } #[derive(Clone, Copy, Debug, Default)] -pub struct Codec; +pub struct Codec

{ + phantom: PhantomData

, +} #[async_trait] -impl RequestResponseCodec for Codec { - type Protocol = Protocol; +impl

RequestResponseCodec for Codec

+where + P: Send + Sync + Clone + ProtocolName, +{ + type Protocol = P; type Request = BobToAlice; type Response = AliceToBob; @@ -109,11 +149,8 @@ impl RequestResponseCodec for Codec { where T: AsyncWrite + Unpin + Send, { - debug!("enter write_request"); - let bytes = serde_cbor::to_vec(&req).map_err(|e| { - tracing::debug!("serde write_request error: {:?}", e); - io::Error::new(io::ErrorKind::InvalidData, e) - })?; + let bytes = + serde_cbor::to_vec(&req).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; upgrade::write_one(io, &bytes).await?; diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 8a4c0a34..66c26857 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -13,7 +13,7 @@ async fn swap() { .with_env_filter( "swap=debug,hyper=off,reqwest=off,monero-harness=info,testcontainers=info,libp2p=debug", ) - .with_ansi(true) + .with_ansi(false) .set_default(); let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" From 62c450192697d955214d0521658dc02ff2e4e1f9 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Thu, 29 Oct 2020 14:57:20 +1100 Subject: [PATCH 14/21] wip: Add xmr_btc tracing filter --- swap/tests/e2e.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 66c26857..7aeb8a24 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -11,7 +11,7 @@ use tracing_subscriber::util::SubscriberInitExt; async fn swap() { let _guard = tracing_subscriber::fmt() .with_env_filter( - "swap=debug,hyper=off,reqwest=off,monero-harness=info,testcontainers=info,libp2p=debug", + "swap=debug,xmr_btc=debug,hyper=off,reqwest=off,monero_harness=info,testcontainers=info,libp2p=debug", ) .with_ansi(false) .set_default(); From 9f32cd988e0588bc96713ace61690ecf656f03c3 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Thu, 29 Oct 2020 14:58:34 +1100 Subject: [PATCH 15/21] wip: Use same timelock value for refund and punish --- swap/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swap/src/lib.rs b/swap/src/lib.rs index 386af1ef..36f5d033 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -13,7 +13,7 @@ pub mod tor; pub const ONE_BTC: u64 = 100_000_000; const REFUND_TIMELOCK: u32 = 10; // Relative timelock, this is number of blocks. TODO: What should it be? -const PUNISH_TIMELOCK: u32 = 20; // FIXME: What should this be? +const PUNISH_TIMELOCK: u32 = 10; // FIXME: What should this be? pub type Never = std::convert::Infallible; From eb6bbe6180bede2fc9097e5f8f38b201610af7db Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Thu, 29 Oct 2020 14:58:51 +1100 Subject: [PATCH 16/21] wip: Fix bug where bob was sending two amount request --- swap/src/bob.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/swap/src/bob.rs b/swap/src/bob.rs index a09e99b6..524948a7 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -107,15 +107,6 @@ pub async fn swap( other => panic!("unexpected event: {:?}", other), }; - swarm.request_amounts(alice.clone(), btc); - - match swarm.next().await { - OutEvent::Amounts(amounts) => { - debug!("Got amounts from Alice: {:?}", amounts); - } - other => panic!("unexpected event: {:?}", other), - }; - let refund_address = bitcoin_wallet.new_address().await?; // TODO: Pass this in using From a37f43a1bae72b00a9269eea2858aa7c981f9260 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Thu, 29 Oct 2020 21:17:00 +1100 Subject: [PATCH 17/21] wip: Provide enough funds to both parties Also use cosntant backoff retry strategy as opposed to exponential backoff. This is in case retrying several times quickly causes the retry intervals to become large enough that the test is very slow and/or the Bitcoin lock transaction expires. The current problem occurs on the last message i.e. Bob sending tx_redeem_encsig to Alice. The action is yielded for Bob to do it, but Alice appears to never receive it (unconfirmed claim, requires more logging). --- swap/src/alice.rs | 20 ++++++++--------- swap/src/bitcoin.rs | 20 ++++++----------- swap/src/bob.rs | 14 ++++++------ swap/src/lib.rs | 2 -- swap/src/monero.rs | 3 +++ swap/src/network/request_response.rs | 1 + swap/tests/e2e.rs | 32 ++++++++++++++++++++++------ 7 files changed, 55 insertions(+), 37 deletions(-) diff --git a/swap/src/alice.rs b/swap/src/alice.rs index 1d45ca5e..f5c65000 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -2,7 +2,7 @@ //! Alice holds XMR and wishes receive BTC. use anyhow::Result; use async_trait::async_trait; -use backoff::{future::FutureOperation as _, ExponentialBackoff}; +use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _}; use genawaiter::GeneratorState; use libp2p::{ core::{identity::Keypair, Multiaddr}, @@ -10,9 +10,9 @@ use libp2p::{ NetworkBehaviour, PeerId, }; use rand::rngs::OsRng; -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; use tokio::sync::Mutex; -use tracing::{debug, info, warn}; +use tracing::{info, warn}; use xmr_btc::alice; mod amounts; @@ -55,6 +55,8 @@ pub async fn swap( impl Network { pub async fn send_message2(&mut self, proof: monero::TransferProof) { + tracing::debug!("Sending transfer proof"); + match self.channel.take() { None => warn!("Channel not found, did you call this twice?"), Some(channel) => { @@ -67,6 +69,9 @@ pub async fn swap( } } + // TODO: For retry, use `backoff::ExponentialBackoff` in production as opposed + // to `ConstantBackoff`. + #[async_trait] impl ReceiveBitcoinRedeemEncsig for Network { async fn receive_bitcoin_redeem_encsig(&mut self) -> xmr_btc::bitcoin::EncryptedSignature { @@ -85,16 +90,13 @@ pub async fn swap( Result::<_, backoff::Error>::Ok(encsig) }) - .retry(ExponentialBackoff { - max_elapsed_time: None, - ..Default::default() - }) + .retry(ConstantBackoff::new(Duration::from_secs(1))) .await .expect("transient errors to be retried") } } - debug!("swapping ..."); + tracing::debug!("swapping ..."); let mut swarm = new_swarm(listen, local_port)?; let message0: bob::Message0; @@ -107,7 +109,6 @@ pub async fn swap( info!("Connection established with: {}", id); } OutEvent::Request(amounts::OutEvent::Btc { btc, channel }) => { - debug!("Got request from Bob to swap {}", btc); let amounts = calculate_amounts(btc); // TODO: We cache the last amounts returned, this needs improving along with // verification of message 0. @@ -138,7 +139,6 @@ pub async fn swap( state0 = Some(state) } OutEvent::Message0(msg) => { - debug!("got message 0 from Bob"); // We don't want Bob to be able to crash us by sending an out of // order message. Keep looping if Bob has not requested amounts. if last_amounts.is_some() { diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index f59c5eda..f9e1493d 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -2,7 +2,7 @@ use std::time::Duration; use anyhow::Result; use async_trait::async_trait; -use backoff::{future::FutureOperation as _, ExponentialBackoff}; +use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _}; use bitcoin::{util::psbt::PartiallySignedTransaction, Address, Transaction}; use bitcoin_harness::{bitcoind_rpc::PsbtBase64, Bitcoind}; use reqwest::Url; @@ -113,14 +113,14 @@ impl BroadcastSignedTransaction for Wallet { } } +// TODO: For retry, use `backoff::ExponentialBackoff` in production as opposed +// to `ConstantBackoff`. + #[async_trait] impl WatchForRawTransaction for Wallet { async fn watch_for_raw_transaction(&self, txid: Txid) -> Transaction { (|| async { Ok(self.0.get_raw_transaction(txid).await?) }) - .retry(ExponentialBackoff { - max_elapsed_time: None, - ..Default::default() - }) + .retry(ConstantBackoff::new(Duration::from_secs(1))) .await .expect("transient errors to be retried") } @@ -130,10 +130,7 @@ impl WatchForRawTransaction for Wallet { impl BlockHeight for Wallet { async fn block_height(&self) -> u32 { (|| async { Ok(self.0.block_height().await?) }) - .retry(ExponentialBackoff { - max_elapsed_time: None, - ..Default::default() - }) + .retry(ConstantBackoff::new(Duration::from_secs(1))) .await .expect("transient errors to be retried") } @@ -160,10 +157,7 @@ impl TransactionBlockHeight for Wallet { Result::<_, backoff::Error>::Ok(block_height) }) - .retry(ExponentialBackoff { - max_elapsed_time: None, - ..Default::default() - }) + .retry(ConstantBackoff::new(Duration::from_secs(1))) .await .expect("transient errors to be retried") } diff --git a/swap/src/bob.rs b/swap/src/bob.rs index 524948a7..07324243 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -2,7 +2,7 @@ //! Bob holds BTC and wishes receive XMR. use anyhow::Result; use async_trait::async_trait; -use backoff::{future::FutureOperation as _, ExponentialBackoff}; +use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _}; use futures::{ channel::mpsc::{Receiver, Sender}, FutureExt, StreamExt, @@ -10,7 +10,7 @@ use futures::{ use genawaiter::GeneratorState; use libp2p::{core::identity::Keypair, Multiaddr, NetworkBehaviour, PeerId}; use rand::rngs::OsRng; -use std::{process, sync::Arc}; +use std::{process, sync::Arc, time::Duration}; use tokio::sync::Mutex; use tracing::{debug, info, warn}; @@ -49,12 +49,17 @@ pub async fn swap( ) -> Result<()> { struct Network(Swarm); + // TODO: For retry, use `backoff::ExponentialBackoff` in production as opposed + // to `ConstantBackoff`. + #[async_trait] impl ReceiveTransferProof for Network { async fn receive_transfer_proof(&mut self) -> monero::TransferProof { #[derive(Debug)] struct UnexpectedMessage; + tracing::debug!("Receiving transfer proof"); + let future = self.0.next().shared(); (|| async { @@ -68,10 +73,7 @@ pub async fn swap( Result::<_, backoff::Error>::Ok(proof) }) - .retry(ExponentialBackoff { - max_elapsed_time: None, - ..Default::default() - }) + .retry(ConstantBackoff::new(Duration::from_secs(1))) .await .expect("transient errors to be retried") } diff --git a/swap/src/lib.rs b/swap/src/lib.rs index 36f5d033..0d682e77 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -10,8 +10,6 @@ pub mod storage; #[cfg(feature = "tor")] pub mod tor; -pub const ONE_BTC: u64 = 100_000_000; - const REFUND_TIMELOCK: u32 = 10; // Relative timelock, this is number of blocks. TODO: What should it be? const PUNISH_TIMELOCK: u32 = 10; // FIXME: What should this be? diff --git a/swap/src/monero.rs b/swap/src/monero.rs index 02e30118..0d215738 100644 --- a/swap/src/monero.rs +++ b/swap/src/monero.rs @@ -75,6 +75,9 @@ impl CreateWalletForOutput for Wallet { } } +// TODO: For retry, use `backoff::ExponentialBackoff` in production as opposed +// to `ConstantBackoff`. + #[async_trait] impl WatchForTransfer for Wallet { async fn watch_for_transfer( diff --git a/swap/src/network/request_response.rs b/swap/src/network/request_response.rs index a5950e14..a042debc 100644 --- a/swap/src/network/request_response.rs +++ b/swap/src/network/request_response.rs @@ -6,6 +6,7 @@ use libp2p::{ }; use serde::{Deserialize, Serialize}; use std::{fmt::Debug, io, marker::PhantomData}; +use tracing::debug; use crate::SwapAmounts; use xmr_btc::{alice, bob, monero}; diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 7aeb8a24..3d9b2148 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -24,10 +24,12 @@ async fn swap() { let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); let _ = bitcoind.init(5).await; - let btc = bitcoin::Amount::ONE_BTC; - let _btc_alice = bitcoin::Amount::ZERO; + let btc = bitcoin::Amount::from_sat(1_000_000); + let btc_alice = bitcoin::Amount::ZERO; let btc_bob = btc * 10; + // this xmr value matches the logic of alice::calculate_amounts i.e. btc * + // 10_000 * 100 let xmr = 1_000_000_000_000; let xmr_alice = xmr * 10; let xmr_bob = 0; @@ -54,8 +56,8 @@ async fn swap() { let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.bob_wallet_rpc_client())); let alice_swap = alice::swap( - alice_btc_wallet, - alice_xmr_wallet, + alice_btc_wallet.clone(), + alice_xmr_wallet.clone(), alice_multiaddr.clone(), None, ); @@ -63,15 +65,33 @@ async fn swap() { let (cmd_tx, mut _cmd_rx) = mpsc::channel(1); let (mut rsp_tx, rsp_rx) = mpsc::channel(1); let bob_swap = bob::swap( - bob_btc_wallet, - bob_xmr_wallet, + bob_btc_wallet.clone(), + bob_xmr_wallet.clone(), btc.as_sat(), alice_multiaddr, cmd_tx, rsp_rx, ); + // automate the verification step by accepting any amounts sent over by Alice rsp_tx.try_send(swap::Rsp::VerifiedAmounts).unwrap(); try_join(alice_swap, bob_swap).await.unwrap(); + + let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap(); + let btc_bob_final = bob_btc_wallet.as_ref().balance().await.unwrap(); + + let xmr_alice_final = alice_xmr_wallet.as_ref().get_balance().await.unwrap(); + + monero.wait_for_bob_wallet_block_height().await.unwrap(); + let xmr_bob_final = bob_xmr_wallet.as_ref().get_balance().await.unwrap(); + + assert_eq!( + btc_alice_final, + btc_alice + btc - bitcoin::Amount::from_sat(xmr_btc::bitcoin::TX_FEE) + ); + assert!(btc_bob_final <= btc_bob - btc); + + assert!(xmr_alice_final.as_piconero() <= xmr_alice - xmr); + assert_eq!(xmr_bob_final.as_piconero(), xmr_bob + xmr); } From 4d4acde4767b4997f68e28a4a2d503c7c8e78da1 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Fri, 30 Oct 2020 09:26:52 +1100 Subject: [PATCH 18/21] Bubble up an event when Bob receives message 3 response Before this patch Bob is not sending message 3. This is because we are not polling Bob's swarm correctly. To fix it we can just mimic the other NB's and bubble up an event when Bob receives message 3 response from Alice, this way we can `await` upon this event which triggers polling, making Bob's swarm send the message. --- swap/src/alice.rs | 7 ++++++- swap/src/alice/message3.rs | 1 + swap/src/bob.rs | 23 ++++++++++++++++++----- swap/src/bob/message3.rs | 30 ++++++++++++++++++++---------- xmr-btc/src/alice.rs | 2 ++ xmr-btc/src/bob.rs | 2 ++ 6 files changed, 49 insertions(+), 16 deletions(-) diff --git a/swap/src/alice.rs b/swap/src/alice.rs index f5c65000..8fa41f0c 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -78,10 +78,15 @@ pub async fn swap( #[derive(Debug)] struct UnexpectedMessage; + tracing::debug!("Receiving bitcoin redeem encsig"); + (|| async { let mut guard = self.swarm.lock().await; let encsig = match guard.next().await { - OutEvent::Message3(msg) => msg.tx_redeem_encsig, + OutEvent::Message3(msg) => { + tracing::debug!("Got redeem encsig from Bob"); + msg.tx_redeem_encsig + } other => { warn!("Expected Bob's Message3, got: {:?}", other); return Err(backoff::Error::Transient(UnexpectedMessage)); diff --git a/swap/src/alice/message3.rs b/swap/src/alice/message3.rs index 48afcc5d..808f5639 100644 --- a/swap/src/alice/message3.rs +++ b/swap/src/alice/message3.rs @@ -73,6 +73,7 @@ impl NetworkBehaviourEventProcess> .. } => { if let BobToAlice::Message3(msg) = request { + tracing::debug!("Alice: got message 3 from Bob"); self.events.push_back(OutEvent::Msg(msg)); // Send back empty response so that the request/response protocol completes. self.rr.send_response(channel, AliceToBob::Message3); diff --git a/swap/src/bob.rs b/swap/src/bob.rs index 07324243..aa4f2e7c 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -29,7 +29,7 @@ use crate::{ peer_tracker::{self, PeerTracker}, transport, TokioExecutor, }, - Cmd, Never, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, + Cmd, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; use xmr_btc::{ alice, @@ -64,7 +64,10 @@ pub async fn swap( (|| async { let proof = match future.clone().await { - OutEvent::Message2(msg) => msg.tx_lock_proof, + OutEvent::Message2(msg) => { + debug!("Got transfer proof from Alice"); + msg.tx_lock_proof + } other => { warn!("Expected Alice's Message2, got: {:?}", other); return Err(backoff::Error::Transient(UnexpectedMessage)); @@ -164,7 +167,14 @@ pub async fn swap( } GeneratorState::Yielded(bob::Action::SendBtcRedeemEncsig(tx_redeem_encsig)) => { let mut guard = network.as_ref().lock().await; + debug!("Bob: sending message 3"); guard.0.send_message3(alice.clone(), tx_redeem_encsig); + match guard.0.next().shared().await { + OutEvent::Message3 => { + debug!("Got message 3 response from Alice"); + } + other => panic!("unexpected event: {:?}", other), + }; } GeneratorState::Yielded(bob::Action::CreateXmrWalletForOutput { spend_key, @@ -227,6 +237,7 @@ pub enum OutEvent { Message0(alice::Message0), Message1(alice::Message1), Message2(alice::Message2), + Message3, } impl From for OutEvent { @@ -271,9 +282,11 @@ impl From for OutEvent { } } -impl From for OutEvent { - fn from(_: Never) -> Self { - panic!("not ever") +impl From for OutEvent { + fn from(event: message3::OutEvent) -> Self { + match event { + message3::OutEvent::Msg => OutEvent::Message3, + } } } diff --git a/swap/src/bob/message3.rs b/swap/src/bob/message3.rs index 02fcd0c4..525912d1 100644 --- a/swap/src/bob/message3.rs +++ b/swap/src/bob/message3.rs @@ -7,38 +7,46 @@ use libp2p::{ NetworkBehaviour, PeerId, }; use std::{ + collections::VecDeque, task::{Context, Poll}, time::Duration, }; -use tracing::{debug, error}; +use tracing::error; -use crate::{ - network::request_response::{AliceToBob, BobToAlice, Codec, Message3Protocol, TIMEOUT}, - Never, -}; +use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message3Protocol, TIMEOUT}; use xmr_btc::bob; +#[derive(Debug)] +pub enum OutEvent { + Msg, +} + /// A `NetworkBehaviour` that represents sending message 3 to Alice. #[derive(NetworkBehaviour)] -#[behaviour(out_event = "Never", poll_method = "poll")] +#[behaviour(out_event = "OutEvent", poll_method = "poll")] #[allow(missing_debug_implementations)] pub struct Message3 { rr: RequestResponse>, + #[behaviour(ignore)] + events: VecDeque, } impl Message3 { pub fn send(&mut self, alice: PeerId, msg: bob::Message3) { let msg = BobToAlice::Message3(msg); + tracing::debug!("sending ..."); let _id = self.rr.send_request(&alice, msg); } - // TODO: Do we need a custom implementation if we are not bubbling any out - // events? fn poll( &mut self, _: &mut Context<'_>, _: &mut impl PollParameters, - ) -> Poll>, Never>> { + ) -> Poll>, OutEvent>> { + if let Some(event) = self.events.pop_front() { + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); + } + Poll::Pending } } @@ -55,6 +63,7 @@ impl Default for Message3 { vec![(Message3Protocol, ProtocolSupport::Full)], config, ), + events: Default::default(), } } } @@ -71,7 +80,8 @@ impl NetworkBehaviourEventProcess> .. } => { if let AliceToBob::Message3 = response { - debug!("Alice correctly responded to message 3"); + self.events.push_back(OutEvent::Msg); + tracing::debug!("Alice correctly responded to message 3"); } } RequestResponseEvent::InboundFailure { error, .. } => { diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 43d79633..b6392b04 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -175,6 +175,8 @@ where } }; + tracing::debug!("select returned redeem encsig from message"); + tx_redeem_encsig }; diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index f57d062c..8039a250 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -152,6 +152,8 @@ where Either::Right(_) => return Err(SwapFailed::AfterBtcLock(Reason::BtcExpired)), }; + tracing::debug!("select returned transfer proof from message"); + transfer_proof }; From 7fa7641febd22b4f1475896bc2e1f5d32c57ac92 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Fri, 30 Oct 2020 15:51:46 +1100 Subject: [PATCH 19/21] Clean up some logs and comments --- swap/src/alice.rs | 76 +++++++++++++++++++------------------- swap/src/alice/amounts.rs | 4 +- swap/src/alice/message0.rs | 7 ++-- swap/src/alice/message1.rs | 3 +- swap/src/alice/message2.rs | 3 +- swap/src/alice/message3.rs | 4 +- swap/src/bob.rs | 58 +++++++++++++++-------------- swap/src/bob/amounts.rs | 3 +- swap/src/bob/message0.rs | 3 +- swap/src/bob/message1.rs | 3 +- swap/src/bob/message2.rs | 3 +- swap/src/bob/message3.rs | 2 - swap/src/main.rs | 1 - 13 files changed, 88 insertions(+), 82 deletions(-) diff --git a/swap/src/alice.rs b/swap/src/alice.rs index 8fa41f0c..4e57c135 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -12,8 +12,7 @@ use libp2p::{ use rand::rngs::OsRng; use std::{sync::Arc, time::Duration}; use tokio::sync::Mutex; -use tracing::{info, warn}; -use xmr_btc::alice; +use tracing::{debug, info, warn}; mod amounts; mod message0; @@ -34,14 +33,12 @@ use crate::{ SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; use xmr_btc::{ - alice::{action_generator, Action, ReceiveBitcoinRedeemEncsig, State0}, + alice::{self, action_generator, Action, ReceiveBitcoinRedeemEncsig, State0}, bitcoin::BroadcastSignedTransaction, bob, monero::{CreateWalletForOutput, Transfer}, }; -pub type Swarm = libp2p::Swarm; - pub async fn swap( bitcoin_wallet: Arc, monero_wallet: Arc, @@ -55,15 +52,14 @@ pub async fn swap( impl Network { pub async fn send_message2(&mut self, proof: monero::TransferProof) { - tracing::debug!("Sending transfer proof"); - match self.channel.take() { None => warn!("Channel not found, did you call this twice?"), Some(channel) => { let mut guard = self.swarm.lock().await; guard.send_message2(channel, alice::Message2 { tx_lock_proof: proof, - }) + }); + info!("Sent transfer proof"); } } } @@ -78,17 +74,12 @@ pub async fn swap( #[derive(Debug)] struct UnexpectedMessage; - tracing::debug!("Receiving bitcoin redeem encsig"); - - (|| async { + let encsig = (|| async { let mut guard = self.swarm.lock().await; let encsig = match guard.next().await { - OutEvent::Message3(msg) => { - tracing::debug!("Got redeem encsig from Bob"); - msg.tx_redeem_encsig - } + OutEvent::Message3(msg) => msg.tx_redeem_encsig, other => { - warn!("Expected Bob's Message3, got: {:?}", other); + warn!("Expected Bob's Bitcoin redeem encsig, got: {:?}", other); return Err(backoff::Error::Transient(UnexpectedMessage)); } }; @@ -97,33 +88,35 @@ pub async fn swap( }) .retry(ConstantBackoff::new(Duration::from_secs(1))) .await - .expect("transient errors to be retried") + .expect("transient errors to be retried"); + + info!("Received Bitcoin redeem encsig"); + + encsig } } - tracing::debug!("swapping ..."); - let mut swarm = new_swarm(listen, local_port)?; let message0: bob::Message0; let mut state0: Option = None; let mut last_amounts: Option = None; + // TODO: This loop is a neat idea for local development, as it allows us to keep + // Alice up and let Bob keep trying to connect, request amounts and/or send the + // first message of the handshake, but it comes at the cost of needing to handle + // mutable state, which has already been the source of a bug at one point. This + // is an obvious candidate for refactoring loop { match swarm.next().await { - OutEvent::ConnectionEstablished(id) => { - info!("Connection established with: {}", id); + OutEvent::ConnectionEstablished(bob) => { + info!("Connection established with: {}", bob); } OutEvent::Request(amounts::OutEvent::Btc { btc, channel }) => { let amounts = calculate_amounts(btc); - // TODO: We cache the last amounts returned, this needs improving along with - // verification of message 0. last_amounts = Some(amounts); swarm.send_amounts(channel, amounts); - let (xmr, btc) = match last_amounts { - Some(p) => (p.xmr, p.btc), - None => unreachable!("should have amounts by here"), - }; + let SwapAmounts { btc, xmr } = amounts; let redeem_address = bitcoin_wallet.as_ref().new_address().await?; let punish_address = redeem_address.clone(); @@ -139,6 +132,8 @@ pub async fn swap( redeem_address, punish_address, ); + + info!("Commencing handshake"); swarm.set_state0(state.clone()); state0 = Some(state) @@ -156,10 +151,7 @@ pub async fn swap( }; } - let state1 = state0 - .expect("to be set") - .receive(message0) - .expect("failed to receive msg 0"); + let state1 = state0.expect("to be set").receive(message0)?; let (state2, channel) = match swarm.next().await { OutEvent::Message1 { msg, channel } => { @@ -197,7 +189,7 @@ pub async fn swap( loop { let state = action_generator.async_resume().await; - tracing::info!("resumed execution of alice generator, got: {:?}", state); + tracing::info!("Resumed execution of generator, got: {:?}", state); match state { GeneratorState::Yielded(Action::LockXmr { @@ -211,6 +203,7 @@ pub async fn swap( let mut guard = network.as_ref().lock().await; guard.send_message2(transfer_proof).await; + info!("Sent transfer proof"); } GeneratorState::Yielded(Action::RedeemBtc(tx)) => { @@ -235,6 +228,8 @@ pub async fn swap( } } +pub type Swarm = libp2p::Swarm; + fn new_swarm(listen: Multiaddr, port: Option) -> Result { use anyhow::Context as _; @@ -366,11 +361,12 @@ impl Alice { pub fn send_amounts(&mut self, channel: ResponseChannel, amounts: SwapAmounts) { let msg = AliceToBob::Amounts(amounts); self.amounts.send(channel, msg); + info!("Sent amounts response"); } /// Message0 gets sent within the network layer using this state0. pub fn set_state0(&mut self, state: State0) { - info!("Set state 0"); + debug!("Set state 0"); let _ = self.message0.set_state(state); } @@ -380,7 +376,8 @@ impl Alice { channel: ResponseChannel, msg: xmr_btc::alice::Message1, ) { - self.message1.send(channel, msg) + self.message1.send(channel, msg); + debug!("Sent Message1"); } /// Send Message2 to Bob in response to receiving his Message2. @@ -389,7 +386,8 @@ impl Alice { channel: ResponseChannel, msg: xmr_btc::alice::Message2, ) { - self.message2.send(channel, msg) + self.message2.send(channel, msg); + debug!("Sent Message2"); } } @@ -410,11 +408,11 @@ impl Default for Alice { } fn calculate_amounts(btc: ::bitcoin::Amount) -> SwapAmounts { - const XMR_PER_BTC: u64 = 100; // TODO: Get this from an exchange. + // TODO: Get this from an exchange. + // This value corresponds to 100 XMR per BTC + const PICONERO_PER_SAT: u64 = 1_000_000; - // TODO: Check that this is correct. - // XMR uses 12 zerose BTC uses 8. - let picos = (btc.as_sat() * 10000) * XMR_PER_BTC; + let picos = btc.as_sat() * PICONERO_PER_SAT; let xmr = monero::Amount::from_piconero(picos); SwapAmounts { btc, xmr } diff --git a/swap/src/alice/amounts.rs b/swap/src/alice/amounts.rs index 85dd5b92..33e230d5 100644 --- a/swap/src/alice/amounts.rs +++ b/swap/src/alice/amounts.rs @@ -11,7 +11,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::error; +use tracing::{debug, error}; use crate::network::request_response::{AliceToBob, AmountsProtocol, BobToAlice, Codec, TIMEOUT}; @@ -80,8 +80,8 @@ impl NetworkBehaviourEventProcess> }, .. } => { - tracing::debug!("amounts: Request from Bob received"); if let BobToAlice::AmountsFromBtc(btc) = request { + debug!("Received amounts request"); self.events.push_back(OutEvent::Btc { btc, channel }) } } diff --git a/swap/src/alice/message0.rs b/swap/src/alice/message0.rs index 45bb248c..b10b3aca 100644 --- a/swap/src/alice/message0.rs +++ b/swap/src/alice/message0.rs @@ -13,7 +13,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::{error, info}; +use tracing::{debug, error}; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message0Protocol, TIMEOUT}; use xmr_btc::{alice::State0, bob}; @@ -86,9 +86,8 @@ impl NetworkBehaviourEventProcess> }, .. } => { - tracing::debug!("message0: Request from Bob received"); if let BobToAlice::Message0(msg) = request { - info!("Got Bob's first message"); + debug!("Received Message0"); let response = match &self.state { None => panic!("No state, did you forget to set it?"), Some(state) => { @@ -97,6 +96,8 @@ impl NetworkBehaviourEventProcess> } }; self.rr.send_response(channel, response); + debug!("Sent Message0"); + self.events.push_back(OutEvent::Msg(msg)); } } diff --git a/swap/src/alice/message1.rs b/swap/src/alice/message1.rs index 11e578ad..4d44b0a2 100644 --- a/swap/src/alice/message1.rs +++ b/swap/src/alice/message1.rs @@ -11,7 +11,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::error; +use tracing::{debug, error}; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message1Protocol, TIMEOUT}; use xmr_btc::bob; @@ -83,6 +83,7 @@ impl NetworkBehaviourEventProcess> .. } => { if let BobToAlice::Message1(msg) = request { + debug!("Received Message1"); self.events.push_back(OutEvent::Msg { msg, channel }); } } diff --git a/swap/src/alice/message2.rs b/swap/src/alice/message2.rs index e1493ba4..bab62d3e 100644 --- a/swap/src/alice/message2.rs +++ b/swap/src/alice/message2.rs @@ -11,7 +11,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::error; +use tracing::{debug, error}; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message2Protocol, TIMEOUT}; use xmr_btc::bob; @@ -83,6 +83,7 @@ impl NetworkBehaviourEventProcess> .. } => { if let BobToAlice::Message2(msg) = request { + debug!("Received Message2"); self.events.push_back(OutEvent::Msg { msg, channel }); } } diff --git a/swap/src/alice/message3.rs b/swap/src/alice/message3.rs index 808f5639..0e4bd773 100644 --- a/swap/src/alice/message3.rs +++ b/swap/src/alice/message3.rs @@ -11,7 +11,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::error; +use tracing::{debug, error}; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message3Protocol, TIMEOUT}; use xmr_btc::bob; @@ -73,7 +73,7 @@ impl NetworkBehaviourEventProcess> .. } => { if let BobToAlice::Message3(msg) = request { - tracing::debug!("Alice: got message 3 from Bob"); + debug!("Received Message3"); self.events.push_back(OutEvent::Msg(msg)); // Send back empty response so that the request/response protocol completes. self.rr.send_response(channel, AliceToBob::Message3); diff --git a/swap/src/bob.rs b/swap/src/bob.rs index aa4f2e7c..451fc200 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -38,7 +38,6 @@ use xmr_btc::{ monero::CreateWalletForOutput, }; -// FIXME: This whole function is horrible, needs total re-write. pub async fn swap( bitcoin_wallet: Arc, monero_wallet: Arc, @@ -58,18 +57,13 @@ pub async fn swap( #[derive(Debug)] struct UnexpectedMessage; - tracing::debug!("Receiving transfer proof"); - let future = self.0.next().shared(); - (|| async { + let proof = (|| async { let proof = match future.clone().await { - OutEvent::Message2(msg) => { - debug!("Got transfer proof from Alice"); - msg.tx_lock_proof - } + OutEvent::Message2(msg) => msg.tx_lock_proof, other => { - warn!("Expected Alice's Message2, got: {:?}", other); + warn!("Expected transfer proof, got: {:?}", other); return Err(backoff::Error::Transient(UnexpectedMessage)); } }; @@ -78,12 +72,14 @@ pub async fn swap( }) .retry(ConstantBackoff::new(Duration::from_secs(1))) .await - .expect("transient errors to be retried") + .expect("transient errors to be retried"); + + info!("Received transfer proof"); + + proof } } - debug!("swapping ..."); - let mut swarm = new_swarm()?; libp2p::Swarm::dial_addr(&mut swarm, addr)?; @@ -91,22 +87,22 @@ pub async fn swap( OutEvent::ConnectionEstablished(alice) => alice, other => panic!("unexpected event: {:?}", other), }; - info!("Connection established."); + info!("Connection established with: {}", alice); swarm.request_amounts(alice.clone(), btc); - let (btc_amount, xmr) = match swarm.next().await { + let (btc, xmr) = match swarm.next().await { OutEvent::Amounts(amounts) => { - debug!("Got amounts from Alice: {:?}", amounts); + info!("Got amounts from Alice: {:?}", amounts); let cmd = Cmd::VerifyAmounts(amounts); cmd_tx.try_send(cmd)?; let response = rsp_rx.next().await; if response == Some(Rsp::Abort) { - info!("Amounts no good, aborting ..."); + info!("User rejected amounts proposed by Alice, aborting..."); process::exit(0); } - info!("User verified amounts, continuing with swap ..."); + info!("User accepted amounts proposed by Alice"); (amounts.btc, amounts.xmr) } other => panic!("unexpected event: {:?}", other), @@ -118,13 +114,15 @@ pub async fn swap( let rng = &mut OsRng; let state0 = State0::new( rng, - btc_amount, + btc, xmr, REFUND_TIMELOCK, PUNISH_TIMELOCK, refund_address, ); + info!("Commencing handshake"); + swarm.send_message0(alice.clone(), state0.next_message(rng)); let state1 = match swarm.next().await { OutEvent::Message0(msg) => state0.receive(bitcoin_wallet.as_ref(), msg).await?, @@ -141,7 +139,7 @@ pub async fn swap( swarm.send_message2(alice.clone(), state2.next_message()); - info!("Handshake complete, we now have State2 for Bob."); + info!("Handshake complete"); let network = Arc::new(Mutex::new(Network(swarm))); @@ -156,7 +154,7 @@ pub async fn swap( loop { let state = action_generator.async_resume().await; - info!("resumed execution of bob generator, got: {:?}", state); + info!("Resumed execution of generator, got: {:?}", state); match state { GeneratorState::Yielded(bob::Action::LockBtc(tx_lock)) => { @@ -167,11 +165,14 @@ pub async fn swap( } GeneratorState::Yielded(bob::Action::SendBtcRedeemEncsig(tx_redeem_encsig)) => { let mut guard = network.as_ref().lock().await; - debug!("Bob: sending message 3"); guard.0.send_message3(alice.clone(), tx_redeem_encsig); + info!("Sent Bitcoin redeem encsig"); + + // TODO: Does Bob need to wait for Alice to send an empty response, or can we + // just continue? match guard.0.next().shared().await { OutEvent::Message3 => { - debug!("Got message 3 response from Alice"); + debug!("Got Message3 empty response"); } other => panic!("unexpected event: {:?}", other), }; @@ -318,29 +319,32 @@ impl Bob { pub fn request_amounts(&mut self, alice: PeerId, btc: u64) { let btc = ::bitcoin::Amount::from_sat(btc); let _id = self.amounts.request_amounts(alice.clone(), btc); - debug!("Requesting amounts from: {}", alice); + info!("Requesting amounts from: {}", alice); } /// Sends Bob's first message to Alice. pub fn send_message0(&mut self, alice: PeerId, msg: bob::Message0) { self.message0.send(alice, msg); - info!("Sent first message to Alice"); + debug!("Sent Message0"); } /// Sends Bob's second message to Alice. pub fn send_message1(&mut self, alice: PeerId, msg: bob::Message1) { - self.message1.send(alice, msg) + self.message1.send(alice, msg); + debug!("Sent Message1"); } /// Sends Bob's third message to Alice. pub fn send_message2(&mut self, alice: PeerId, msg: bob::Message2) { - self.message2.send(alice, msg) + self.message2.send(alice, msg); + debug!("Sent Message2"); } /// Sends Bob's fourth message to Alice. pub fn send_message3(&mut self, alice: PeerId, tx_redeem_encsig: EncryptedSignature) { let msg = bob::Message3 { tx_redeem_encsig }; - self.message3.send(alice, msg) + self.message3.send(alice, msg); + debug!("Sent Message3"); } /// Returns Alice's peer id if we are connected. diff --git a/swap/src/bob/amounts.rs b/swap/src/bob/amounts.rs index f2fd0266..7428ad30 100644 --- a/swap/src/bob/amounts.rs +++ b/swap/src/bob/amounts.rs @@ -12,7 +12,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::error; +use tracing::{debug, error}; use crate::{ network::request_response::{AliceToBob, AmountsProtocol, BobToAlice, Codec, TIMEOUT}, @@ -85,6 +85,7 @@ impl NetworkBehaviourEventProcess> .. } => { if let AliceToBob::Amounts(p) = response { + debug!("Received amounts response"); self.events.push_back(OutEvent::Amounts(p)); } } diff --git a/swap/src/bob/message0.rs b/swap/src/bob/message0.rs index 455b7452..89504fbb 100644 --- a/swap/src/bob/message0.rs +++ b/swap/src/bob/message0.rs @@ -11,7 +11,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::error; +use tracing::{debug, error}; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message0Protocol, TIMEOUT}; use xmr_btc::{alice, bob}; @@ -79,6 +79,7 @@ impl NetworkBehaviourEventProcess> .. } => { if let AliceToBob::Message0(msg) = response { + debug!("Received Message0"); self.events.push_back(OutEvent::Msg(msg)); } } diff --git a/swap/src/bob/message1.rs b/swap/src/bob/message1.rs index e3a67de9..32fd6c52 100644 --- a/swap/src/bob/message1.rs +++ b/swap/src/bob/message1.rs @@ -11,7 +11,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::error; +use tracing::{debug, error}; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message1Protocol, TIMEOUT}; use xmr_btc::{alice, bob}; @@ -79,6 +79,7 @@ impl NetworkBehaviourEventProcess> .. } => { if let AliceToBob::Message1(msg) = response { + debug!("Received Message1"); self.events.push_back(OutEvent::Msg(msg)); } } diff --git a/swap/src/bob/message2.rs b/swap/src/bob/message2.rs index b141b240..17425227 100644 --- a/swap/src/bob/message2.rs +++ b/swap/src/bob/message2.rs @@ -11,7 +11,7 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tracing::error; +use tracing::{debug, error}; use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Message2Protocol, TIMEOUT}; use xmr_btc::{alice, bob}; @@ -79,6 +79,7 @@ impl NetworkBehaviourEventProcess> .. } => { if let AliceToBob::Message2(msg) = response { + debug!("Received Message2"); self.events.push_back(OutEvent::Msg(msg)); } } diff --git a/swap/src/bob/message3.rs b/swap/src/bob/message3.rs index 525912d1..20873bfa 100644 --- a/swap/src/bob/message3.rs +++ b/swap/src/bob/message3.rs @@ -34,7 +34,6 @@ pub struct Message3 { impl Message3 { pub fn send(&mut self, alice: PeerId, msg: bob::Message3) { let msg = BobToAlice::Message3(msg); - tracing::debug!("sending ..."); let _id = self.rr.send_request(&alice, msg); } @@ -81,7 +80,6 @@ impl NetworkBehaviourEventProcess> } => { if let AliceToBob::Message3 = response { self.events.push_back(OutEvent::Msg); - tracing::debug!("Alice correctly responded to message 3"); } } RequestResponseEvent::InboundFailure { error, .. } => { diff --git a/swap/src/main.rs b/swap/src/main.rs index a813aa62..c2b0b4b6 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -28,7 +28,6 @@ use cli::Options; use swap::{alice, bitcoin, bob, monero, Cmd, Rsp, SwapAmounts}; // TODO: Add root seed file instead of generating new seed each run. -// TODO: Remove all instances of the todo! macro // TODO: Add a config file with these in it. // Alice's address and port until we have a config file. From 792fa351c81ff46e545a30f14bb23dc4e051a9e4 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Fri, 30 Oct 2020 15:55:10 +1100 Subject: [PATCH 20/21] Upgrade bitcoin-harness dependency From dev-branch to master. --- swap/Cargo.toml | 2 +- xmr-btc/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 475fb6a0..fa283dd7 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -12,7 +12,7 @@ atty = "0.2" backoff = { version = "0.2", features = ["tokio"] } base64 = "0.12" bitcoin = { version = "0.23", features = ["rand", "use-serde"] } # TODO: Upgrade other crates in this repo to use this version. -bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "b56ac243eade11e817aa670d546892852a609e7d" } +bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "f1bbe6a4540d0741f1f4f22577cfeeadbfd7aaaf" } derivative = "2" futures = { version = "0.3", default-features = false } genawaiter = "0.99.1" diff --git a/xmr-btc/Cargo.toml b/xmr-btc/Cargo.toml index 22dfc30b..7d13407d 100644 --- a/xmr-btc/Cargo.toml +++ b/xmr-btc/Cargo.toml @@ -28,7 +28,7 @@ tracing = "0.1" [dev-dependencies] backoff = { version = "0.2", features = ["tokio"] } base64 = "0.12" -bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "7ff30a559ab57cc3aa71189e71433ef6b2a6c3a2" } +bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "f1bbe6a4540d0741f1f4f22577cfeeadbfd7aaaf" } futures = "0.3" monero-harness = { path = "../monero-harness" } reqwest = { version = "0.10", default-features = false } From 92c7e8e84f5d4dca70964291d1d37073b08c3f65 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Mon, 2 Nov 2020 10:31:53 +1100 Subject: [PATCH 21/21] Run swap e2e test without tor feature Not worth automatically testing both `tor` and `not(tor)`, it should not make a difference. --- swap/tests/e2e.rs | 181 +++++++++++++++++++++++----------------------- 1 file changed, 92 insertions(+), 89 deletions(-) diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 3d9b2148..09ba8fb0 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -1,97 +1,100 @@ -use bitcoin_harness::Bitcoind; -use futures::{channel::mpsc, future::try_join}; -use libp2p::Multiaddr; -use monero_harness::Monero; -use std::sync::Arc; -use swap::{alice, bob}; -use testcontainers::clients::Cli; -use tracing_subscriber::util::SubscriberInitExt; - -#[tokio::test] -async fn swap() { - let _guard = tracing_subscriber::fmt() +#[cfg(not(feature = "tor"))] +mod e2e_test { + use bitcoin_harness::Bitcoind; + use futures::{channel::mpsc, future::try_join}; + use libp2p::Multiaddr; + use monero_harness::Monero; + use std::sync::Arc; + use swap::{alice, bob}; + use testcontainers::clients::Cli; + use tracing_subscriber::util::SubscriberInitExt; + + #[tokio::test] + async fn swap() { + let _guard = tracing_subscriber::fmt() .with_env_filter( "swap=debug,xmr_btc=debug,hyper=off,reqwest=off,monero_harness=info,testcontainers=info,libp2p=debug", ) .with_ansi(false) .set_default(); - let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" - .parse() - .expect("failed to parse Alice's address"); - - let cli = Cli::default(); - let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); - let _ = bitcoind.init(5).await; - - let btc = bitcoin::Amount::from_sat(1_000_000); - let btc_alice = bitcoin::Amount::ZERO; - let btc_bob = btc * 10; - - // this xmr value matches the logic of alice::calculate_amounts i.e. btc * - // 10_000 * 100 - let xmr = 1_000_000_000_000; - let xmr_alice = xmr * 10; - let xmr_bob = 0; - - let alice_btc_wallet = Arc::new( - swap::bitcoin::Wallet::new("alice", &bitcoind.node_url) - .await - .unwrap(), - ); - let bob_btc_wallet = Arc::new( - swap::bitcoin::Wallet::new("bob", &bitcoind.node_url) + let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" + .parse() + .expect("failed to parse Alice's address"); + + let cli = Cli::default(); + let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); + let _ = bitcoind.init(5).await; + + let btc = bitcoin::Amount::from_sat(1_000_000); + let btc_alice = bitcoin::Amount::ZERO; + let btc_bob = btc * 10; + + // this xmr value matches the logic of alice::calculate_amounts i.e. btc * + // 10_000 * 100 + let xmr = 1_000_000_000_000; + let xmr_alice = xmr * 10; + let xmr_bob = 0; + + let alice_btc_wallet = Arc::new( + swap::bitcoin::Wallet::new("alice", &bitcoind.node_url) + .await + .unwrap(), + ); + let bob_btc_wallet = Arc::new( + swap::bitcoin::Wallet::new("bob", &bitcoind.node_url) + .await + .unwrap(), + ); + bitcoind + .mint(bob_btc_wallet.0.new_address().await.unwrap(), btc_bob) .await - .unwrap(), - ); - bitcoind - .mint(bob_btc_wallet.0.new_address().await.unwrap(), btc_bob) - .await - .unwrap(); - - let (monero, _container) = Monero::new(&cli).unwrap(); - monero.init(xmr_alice, xmr_bob).await.unwrap(); - - let alice_xmr_wallet = Arc::new(swap::monero::Wallet(monero.alice_wallet_rpc_client())); - let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.bob_wallet_rpc_client())); - - let alice_swap = alice::swap( - alice_btc_wallet.clone(), - alice_xmr_wallet.clone(), - alice_multiaddr.clone(), - None, - ); - - let (cmd_tx, mut _cmd_rx) = mpsc::channel(1); - let (mut rsp_tx, rsp_rx) = mpsc::channel(1); - let bob_swap = bob::swap( - bob_btc_wallet.clone(), - bob_xmr_wallet.clone(), - btc.as_sat(), - alice_multiaddr, - cmd_tx, - rsp_rx, - ); - - // automate the verification step by accepting any amounts sent over by Alice - rsp_tx.try_send(swap::Rsp::VerifiedAmounts).unwrap(); - - try_join(alice_swap, bob_swap).await.unwrap(); - - let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap(); - let btc_bob_final = bob_btc_wallet.as_ref().balance().await.unwrap(); - - let xmr_alice_final = alice_xmr_wallet.as_ref().get_balance().await.unwrap(); - - monero.wait_for_bob_wallet_block_height().await.unwrap(); - let xmr_bob_final = bob_xmr_wallet.as_ref().get_balance().await.unwrap(); - - assert_eq!( - btc_alice_final, - btc_alice + btc - bitcoin::Amount::from_sat(xmr_btc::bitcoin::TX_FEE) - ); - assert!(btc_bob_final <= btc_bob - btc); - - assert!(xmr_alice_final.as_piconero() <= xmr_alice - xmr); - assert_eq!(xmr_bob_final.as_piconero(), xmr_bob + xmr); + .unwrap(); + + let (monero, _container) = Monero::new(&cli).unwrap(); + monero.init(xmr_alice, xmr_bob).await.unwrap(); + + let alice_xmr_wallet = Arc::new(swap::monero::Wallet(monero.alice_wallet_rpc_client())); + let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.bob_wallet_rpc_client())); + + let alice_swap = alice::swap( + alice_btc_wallet.clone(), + alice_xmr_wallet.clone(), + alice_multiaddr.clone(), + None, + ); + + let (cmd_tx, mut _cmd_rx) = mpsc::channel(1); + let (mut rsp_tx, rsp_rx) = mpsc::channel(1); + let bob_swap = bob::swap( + bob_btc_wallet.clone(), + bob_xmr_wallet.clone(), + btc.as_sat(), + alice_multiaddr, + cmd_tx, + rsp_rx, + ); + + // automate the verification step by accepting any amounts sent over by Alice + rsp_tx.try_send(swap::Rsp::VerifiedAmounts).unwrap(); + + try_join(alice_swap, bob_swap).await.unwrap(); + + let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap(); + let btc_bob_final = bob_btc_wallet.as_ref().balance().await.unwrap(); + + let xmr_alice_final = alice_xmr_wallet.as_ref().get_balance().await.unwrap(); + + monero.wait_for_bob_wallet_block_height().await.unwrap(); + let xmr_bob_final = bob_xmr_wallet.as_ref().get_balance().await.unwrap(); + + assert_eq!( + btc_alice_final, + btc_alice + btc - bitcoin::Amount::from_sat(xmr_btc::bitcoin::TX_FEE) + ); + assert!(btc_bob_final <= btc_bob - btc); + + assert!(xmr_alice_final.as_piconero() <= xmr_alice - xmr); + assert_eq!(xmr_bob_final.as_piconero(), xmr_bob + xmr); + } }