diff --git a/swap/Cargo.toml b/swap/Cargo.toml index aa9973df..2c217182 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -9,7 +9,10 @@ description = "XMR/BTC trustless atomic swaps." anyhow = "1" async-trait = "0.1" atty = "0.2" -bitcoin = { version = "0.25", features = ["rand", "use-serde"] } # TODO: Upgrade other crates in this repo to use this version. +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" } derivative = "2" futures = { version = "0.3", default-features = false } libp2p = { version = "0.28", default-features = false, features = ["tcp-tokio", "yamux", "mplex", "dns", "noise", "request-response"] } @@ -17,6 +20,7 @@ libp2p-tokio-socks5 = "0.3" log = { version = "0.4", features = ["serde"] } monero = "0.9" rand = "0.7" +reqwest = { version = "0.10", default-features = false } serde = { version = "1", features = ["derive"] } serde_derive = "1.0" serde_json = "1" diff --git a/swap/src/alice.rs b/swap/src/alice.rs index fde9daa3..734cbed5 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -6,25 +6,35 @@ use libp2p::{ request_response::ResponseChannel, NetworkBehaviour, PeerId, }; -use std::time::Duration; +use rand::{CryptoRng, RngCore}; +use std::{thread, time::Duration}; use tracing::debug; mod amounts; +mod message0; -use self::amounts::*; +use self::{amounts::*, message0::*}; use crate::{ - bitcoin, monero, network::{ peer_tracker::{self, PeerTracker}, request_response::{AliceToBob, TIMEOUT}, transport, TokioExecutor, }, - SwapParams, + SwapParams, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; +use xmr_btc::{alice::State0, bob, monero}; pub type Swarm = libp2p::Swarm; -pub async fn swap(listen: Multiaddr) -> Result<()> { +pub async fn swap( + listen: Multiaddr, + rng: &mut R, + redeem_address: ::bitcoin::Address, + punish_address: ::bitcoin::Address, +) -> Result<()> { + let mut message0: Option = None; + let mut last_amounts: Option = None; + let mut swarm = new_swarm(listen)?; loop { @@ -35,10 +45,47 @@ pub async fn swap(listen: Multiaddr) -> Result<()> { OutEvent::Request(amounts::OutEvent::Btc { btc, channel }) => { debug!("Got request from Bob to swap {}", btc); let p = calculate_amounts(btc); + last_amounts = Some(p); swarm.send(channel, AliceToBob::Amounts(p)); } - } + OutEvent::Message0(msg) => { + debug!("Got message0 from Bob"); + // TODO: Do this in a more Rusty/functional way. + message0 = Some(msg); + break; + } + other => panic!("unexpected event: {:?}", other), + }; } + + let (xmr, btc) = match last_amounts { + Some(p) => (p.xmr, p.btc), + None => unreachable!("should have amounts by here"), + }; + + // FIXME: Too many `bitcoin` crates/modules. + let xmr = monero::Amount::from_piconero(xmr.as_piconero()); + let btc = ::bitcoin::Amount::from_sat(btc.as_sat()); + + let state0 = State0::new( + rng, + btc, + xmr, + REFUND_TIMELOCK, + PUNISH_TIMELOCK, + redeem_address, + punish_address, + ); + swarm.set_state0(state0.clone()); + + let state1 = match message0 { + Some(msg) => state0.receive(msg), + None => unreachable!("should have msg by here"), + }; + + tracing::warn!("parking thread ..."); + thread::park(); + Ok(()) } fn new_swarm(listen: Multiaddr) -> Result { @@ -68,14 +115,9 @@ fn new_swarm(listen: Multiaddr) -> Result { #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum OutEvent { - Request(amounts::OutEvent), ConnectionEstablished(PeerId), -} - -impl From for OutEvent { - fn from(event: amounts::OutEvent) -> Self { - OutEvent::Request(event) - } + Request(amounts::OutEvent), + Message0(bob::Message0), } impl From for OutEvent { @@ -88,13 +130,28 @@ impl From for OutEvent { } } +impl From for OutEvent { + fn from(event: amounts::OutEvent) -> Self { + OutEvent::Request(event) + } +} + +impl From for OutEvent { + fn from(event: message0::OutEvent) -> Self { + match event { + message0::OutEvent::Msg(msg) => OutEvent::Message0(msg), + } + } +} + /// A `NetworkBehaviour` that represents an XMR/BTC swap node as Alice. #[derive(NetworkBehaviour)] #[behaviour(out_event = "OutEvent", event_process = false)] #[allow(missing_debug_implementations)] pub struct Alice { - amounts: Amounts, pt: PeerTracker, + amounts: Amounts, + message0: Message0, #[behaviour(ignore)] identity: Keypair, } @@ -112,6 +169,10 @@ impl Alice { pub fn send(&mut self, channel: ResponseChannel, msg: AliceToBob) { self.amounts.send(channel, msg); } + + pub fn set_state0(&mut self, state: State0) { + self.message0.set_state(state); + } } impl Default for Alice { @@ -120,15 +181,16 @@ impl Default for Alice { let timeout = Duration::from_secs(TIMEOUT); Self { - amounts: Amounts::new(timeout), pt: PeerTracker::default(), + amounts: Amounts::new(timeout), + message0: Message0::new(timeout), identity, } } } // TODO: Check that this is correct. -fn calculate_amounts(btc: bitcoin::Amount) -> SwapParams { +fn calculate_amounts(btc: ::bitcoin::Amount) -> SwapParams { const XMR_PER_BTC: u64 = 100; // TODO: Get this from an exchange. // XMR uses 12 zerose BTC uses 8. @@ -147,7 +209,7 @@ mod tests { #[test] fn one_bitcoin_equals_a_hundred_moneroj() { - let btc = bitcoin::Amount::from_sat(ONE_BTC); + let btc = ::bitcoin::Amount::from_sat(ONE_BTC); let want = monero::Amount::from_piconero(HUNDRED_XMR); let SwapParams { xmr: got, .. } = calculate_amounts(btc); diff --git a/swap/src/alice/amounts.rs b/swap/src/alice/amounts.rs index dbe100b7..fb4872bc 100644 --- a/swap/src/alice/amounts.rs +++ b/swap/src/alice/amounts.rs @@ -14,20 +14,17 @@ use std::{ }; use tracing::{debug, error}; -use crate::{ - bitcoin, - network::request_response::{AliceToBob, BobToAlice, Codec, Protocol}, -}; +use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol}; #[derive(Debug)] pub enum OutEvent { Btc { - btc: bitcoin::Amount, + btc: ::bitcoin::Amount, channel: ResponseChannel, }, } -/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Bob. +/// A `NetworkBehaviour` that represents getting the amounts of an XMR/BTC swap. #[derive(NetworkBehaviour)] #[behaviour(out_event = "OutEvent", poll_method = "poll")] #[allow(missing_debug_implementations)] @@ -60,7 +57,7 @@ impl Amounts { pub async fn request_amounts( &mut self, alice: PeerId, - btc: bitcoin::Amount, + btc: ::bitcoin::Amount, ) -> Result { let msg = BobToAlice::AmountsFromBtc(btc); let id = self.rr.send_request(&alice, msg); diff --git a/swap/src/alice/message0.rs b/swap/src/alice/message0.rs new file mode 100644 index 00000000..c1631bb9 --- /dev/null +++ b/swap/src/alice/message0.rs @@ -0,0 +1,125 @@ +use anyhow::{bail, Result}; +use libp2p::{ + request_response::{ + handler::RequestProtocol, ProtocolSupport, RequestResponse, RequestResponseConfig, + RequestResponseEvent, RequestResponseMessage, + }, + swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}, + NetworkBehaviour, +}; +use rand::rngs::OsRng; +use std::{ + collections::VecDeque, + task::{Context, Poll}, + time::Duration, +}; +use tracing::error; + +use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol}; +use xmr_btc::{alice::State0, bob}; + +#[derive(Debug)] +pub enum OutEvent { + Msg(bob::Message0), +} + +/// A `NetworkBehaviour` that represents getting the amounts of an XMR/BTC swap. +#[derive(NetworkBehaviour)] +#[behaviour(out_event = "OutEvent", poll_method = "poll")] +#[allow(missing_debug_implementations)] +pub struct Message0 { + rr: RequestResponse, + #[behaviour(ignore)] + events: VecDeque, + #[behaviour(ignore)] + state: Option, +} + +impl Message0 { + pub fn new(timeout: Duration) -> Self { + let mut config = RequestResponseConfig::default(); + config.set_request_timeout(timeout); + + Self { + rr: RequestResponse::new( + Codec::default(), + vec![(Protocol, ProtocolSupport::Full)], + config, + ), + events: Default::default(), + state: None, + } + } + + pub fn set_state(&mut self, state: State0) -> Result<()> { + if self.state.is_some() { + bail!("Trying to set state a second time"); + } + self.state = Some(state); + + Ok(()) + } + + 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 NetworkBehaviourEventProcess> for Message0 { + fn inject_event(&mut self, event: RequestResponseEvent) { + match event { + RequestResponseEvent::Message { + peer: _, + message: + RequestResponseMessage::Request { + request, + request_id: _, + channel, + }, + } => match request { + BobToAlice::Message0(msg) => { + let response = match self.state { + None => panic!("No state, did you forget to set it?"), + Some(state) => { + // TODO: Get OsRng from somewhere? + AliceToBob::Message0(state.next_message(&mut OsRng)) + } + }; + self.rr.send_response(channel, response); + self.events.push_back(OutEvent::Msg(msg)); + } + _ => panic!("unexpected request"), + }, + RequestResponseEvent::Message { + peer: _, + message: + RequestResponseMessage::Response { + response: _, + request_id: _, + }, + } => panic!("unexpected response"), + RequestResponseEvent::InboundFailure { + peer: _, + request_id: _, + error, + } => { + error!("Inbound failure: {:?}", error); + } + RequestResponseEvent::OutboundFailure { + peer: _, + request_id: _, + error, + } => { + error!("Outbound failure: {:?}", error); + } + } + } +} diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index 70bed6cf..1b3495ea 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -1 +1,114 @@ -pub use bitcoin::Amount; +use anyhow::Result; +use async_trait::async_trait; +use backoff::{future::FutureOperation as _, ExponentialBackoff}; +use bitcoin::{util::psbt::PartiallySignedTransaction, Address, Amount, Transaction, Txid}; +use bitcoin_harness::bitcoind_rpc::PsbtBase64; +use reqwest::Url; +use xmr_btc::{ + bitcoin::{ + BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, TxLock, WatchForRawTransaction, + }, + MedianTime, +}; + +#[derive(Debug)] +pub struct Wallet(pub bitcoin_harness::Wallet); + +impl Wallet { + pub async fn new(name: &str, url: &Url) -> Result { + let wallet = bitcoin_harness::Wallet::new(name, url.clone()).await?; + + Ok(Self(wallet)) + } + + pub async fn balance(&self) -> Result { + let balance = self.0.balance().await?; + Ok(balance) + } + + pub async fn new_address(&self) -> Result
{ + self.0.new_address().await.map_err(Into::into) + } + + pub async fn transaction_fee(&self, txid: Txid) -> Result { + let fee = self + .0 + .get_wallet_transaction(txid) + .await + .map(|res| bitcoin::Amount::from_btc(-res.fee))??; + + // FIXME: Handle re-export of bitcoin::Amount correctly. + let fee = Amount::from_sat(fee.as_sat()); + Ok(fee) + } +} + +#[async_trait] +impl BuildTxLockPsbt for Wallet { + async fn build_tx_lock_psbt( + &self, + output_address: Address, + output_amount: Amount, + ) -> Result { + let psbt = self.0.fund_psbt(output_address, output_amount).await?; + let as_hex = base64::decode(psbt)?; + + let psbt = bitcoin::consensus::deserialize(&as_hex)?; + + Ok(psbt) + } +} + +#[async_trait] +impl SignTxLock for Wallet { + async fn sign_tx_lock(&self, tx_lock: TxLock) -> Result { + let psbt = PartiallySignedTransaction::from(tx_lock); + + let psbt = bitcoin::consensus::serialize(&psbt); + let as_base64 = base64::encode(psbt); + + let psbt = self.0.wallet_process_psbt(PsbtBase64(as_base64)).await?; + let PsbtBase64(signed_psbt) = PsbtBase64::from(psbt); + + let as_hex = base64::decode(signed_psbt)?; + let psbt: PartiallySignedTransaction = bitcoin::consensus::deserialize(&as_hex)?; + + let tx = psbt.extract_tx(); + + Ok(tx) + } +} + +#[async_trait] +impl BroadcastSignedTransaction for Wallet { + async fn broadcast_signed_transaction(&self, transaction: Transaction) -> Result { + let txid = self.0.send_raw_transaction(transaction).await?; + Ok(txid) + } +} + +#[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() + }) + .await + .expect("transient errors to be retried") + } +} + +#[async_trait] +impl MedianTime for Wallet { + async fn median_time(&self) -> u32 { + (|| async { Ok(self.0.median_time().await?) }) + .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 da1403bc..41db2b36 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -6,42 +6,55 @@ use futures::{ StreamExt, }; use libp2p::{core::identity::Keypair, Multiaddr, NetworkBehaviour, PeerId}; +use rand::{CryptoRng, RngCore}; use std::{process, thread, time::Duration}; use tracing::{debug, info, warn}; mod amounts; +mod message0; -use self::amounts::*; +use self::{amounts::*, message0::*}; use crate::{ - bitcoin, network::{ peer_tracker::{self, PeerTracker}, request_response::TIMEOUT, transport, TokioExecutor, }, - Cmd, Rsp, + Cmd, Rsp, PUNISH_TIMELOCK, REFUND_TIMELOCK, +}; +use xmr_btc::{ + alice, + bitcoin::BuildTxLockPsbt, + bob::{self, State0}, }; -pub async fn swap( +pub async fn swap( btc: u64, addr: Multiaddr, mut cmd_tx: Sender, mut rsp_rx: Receiver, -) -> Result<()> { + rng: &mut R, + refund_address: ::bitcoin::Address, + wallet: &W, +) -> Result<()> +where + W: BuildTxLockPsbt, + R: RngCore + CryptoRng, +{ let mut swarm = new_swarm()?; libp2p::Swarm::dial_addr(&mut swarm, addr)?; - let id = match swarm.next().await { - OutEvent::ConnectionEstablished(id) => id, + let alice = match swarm.next().await { + OutEvent::ConnectionEstablished(alice) => alice, other => panic!("unexpected event: {:?}", other), }; info!("Connection established."); - swarm.request_amounts(id, btc).await; + swarm.request_amounts(alice.clone(), btc); - match swarm.next().await { - OutEvent::Response(amounts::OutEvent::Amounts(p)) => { - debug!("Got response from Alice: {:?}", p); + let (btc, xmr) = match swarm.next().await { + OutEvent::Amounts(amounts::OutEvent::Amounts(p)) => { + debug!("Got amounts from Alice: {:?}", p); let cmd = Cmd::VerifyAmounts(p); cmd_tx.try_send(cmd)?; let response = rsp_rx.next().await; @@ -49,10 +62,32 @@ pub async fn swap( info!("Amounts no good, aborting ..."); process::exit(0); } + info!("User verified amounts, continuing with swap ..."); + (p.btc, p.xmr) } other => panic!("unexpected event: {:?}", other), - } + }; + + // FIXME: Too many `bitcoin` crates/modules. + let xmr = xmr_btc::monero::Amount::from_piconero(xmr.as_piconero()); + let btc = ::bitcoin::Amount::from_sat(btc.as_sat()); + + let state0 = State0::new( + rng, + btc, + xmr, + REFUND_TIMELOCK, + PUNISH_TIMELOCK, + refund_address, + ); + swarm.send_message0(alice.clone(), state0.next_message(rng)); + let state1 = match swarm.next().await { + OutEvent::Message0(msg) => { + state0.receive(wallet, msg) // TODO: More graceful error handling. + } + other => panic!("unexpected event: {:?}", other), + }; warn!("parking thread ..."); thread::park(); @@ -83,14 +118,9 @@ fn new_swarm() -> Result { #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum OutEvent { - Response(amounts::OutEvent), ConnectionEstablished(PeerId), -} - -impl From for OutEvent { - fn from(event: amounts::OutEvent) -> Self { - OutEvent::Response(event) - } + Amounts(amounts::OutEvent), + Message0(alice::Message0), } impl From for OutEvent { @@ -103,13 +133,28 @@ impl From for OutEvent { } } +impl From for OutEvent { + fn from(event: amounts::OutEvent) -> Self { + OutEvent::Amounts(event) + } +} + +impl From for OutEvent { + fn from(event: message0::OutEvent) -> Self { + match event { + message0::OutEvent::Msg(msg) => OutEvent::Message0(msg), + } + } +} + /// A `NetworkBehaviour` that represents an XMR/BTC swap node as Bob. #[derive(NetworkBehaviour)] #[behaviour(out_event = "OutEvent", event_process = false)] #[allow(missing_debug_implementations)] pub struct Bob { - amounts: Amounts, pt: PeerTracker, + amounts: Amounts, + message0: Message0, #[behaviour(ignore)] identity: Keypair, } @@ -124,12 +169,17 @@ impl Bob { } /// Sends a message to Alice to get current amounts based on `btc`. - pub async 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).await; + 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); } + /// Sends Bob's first state message to Alice. + pub fn send_message0(&mut self, alice: PeerId, msg: bob::Message0) { + self.message0.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() @@ -142,8 +192,9 @@ impl Default for Bob { let timeout = Duration::from_secs(TIMEOUT); Self { - amounts: Amounts::new(timeout), pt: PeerTracker::default(), + amounts: Amounts::new(timeout), + message0: Message0::new(timeout), identity, } } diff --git a/swap/src/bob/amounts.rs b/swap/src/bob/amounts.rs index 527fdb32..6a329a00 100644 --- a/swap/src/bob/amounts.rs +++ b/swap/src/bob/amounts.rs @@ -15,7 +15,6 @@ use std::{ use tracing::error; use crate::{ - bitcoin, network::request_response::{AliceToBob, BobToAlice, Codec, Protocol}, SwapParams, }; @@ -25,7 +24,7 @@ pub enum OutEvent { Amounts(SwapParams), } -/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Bob. +/// A `NetworkBehaviour` that represents getting the amounts of an XMR/BTC swap. #[derive(NetworkBehaviour)] #[behaviour(out_event = "OutEvent", poll_method = "poll")] #[allow(missing_debug_implementations)] @@ -50,11 +49,7 @@ impl Amounts { } } - pub async fn request_amounts( - &mut self, - alice: PeerId, - btc: bitcoin::Amount, - ) -> Result { + pub fn request_amounts(&mut self, alice: PeerId, btc: ::bitcoin::Amount) -> Result { let msg = BobToAlice::AmountsFromBtc(btc); let id = self.rr.send_request(&alice, msg); diff --git a/swap/src/bob/message0.rs b/swap/src/bob/message0.rs new file mode 100644 index 00000000..e5d3d215 --- /dev/null +++ b/swap/src/bob/message0.rs @@ -0,0 +1,97 @@ +use libp2p::{ + request_response::{ + handler::RequestProtocol, ProtocolSupport, RequestResponse, RequestResponseConfig, + RequestResponseEvent, RequestResponseMessage, + }, + swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}, + NetworkBehaviour, PeerId, +}; +use std::{ + collections::VecDeque, + task::{Context, Poll}, + time::Duration, +}; +use tracing::error; + +use crate::network::request_response::{AliceToBob, BobToAlice, Codec, Protocol}; +use xmr_btc::{alice, bob}; + +#[derive(Debug)] +pub enum OutEvent { + Msg(alice::Message0), +} + +/// A `NetworkBehaviour` that represents send/recv of message 0. +#[derive(NetworkBehaviour)] +#[behaviour(out_event = "OutEvent", poll_method = "poll")] +#[allow(missing_debug_implementations)] +pub struct Message0 { + rr: RequestResponse, + #[behaviour(ignore)] + events: VecDeque, +} + +impl Message0 { + pub fn new(timeout: Duration) -> Self { + let mut config = RequestResponseConfig::default(); + config.set_request_timeout(timeout); + + Self { + rr: RequestResponse::new( + Codec::default(), + vec![(Protocol, ProtocolSupport::Full)], + config, + ), + events: Default::default(), + } + } + + pub fn send(&mut self, alice: PeerId, msg: bob::Message0) { + let msg = BobToAlice::Message0(msg); + let _id = self.rr.send_request(&alice, msg); + } + + 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 NetworkBehaviourEventProcess> for Message0 { + fn inject_event(&mut self, event: RequestResponseEvent) { + match event { + RequestResponseEvent::Message { + peer: _, + message: RequestResponseMessage::Request { .. }, + } => panic!("Bob should never get a request from Alice"), + RequestResponseEvent::Message { + peer: _, + message: + RequestResponseMessage::Response { + response, + request_id: _, + }, + } => match response { + AliceToBob::Message0(msg) => self.events.push_back(OutEvent::Msg(msg)), + }, + + RequestResponseEvent::InboundFailure { .. } => { + panic!("Bob should never get a request from Alice, so should never get an InboundFailure"); + } + RequestResponseEvent::OutboundFailure { + peer: _, + request_id: _, + error, + } => { + error!("Outbound failure: {:?}", error); + } + } + } +} diff --git a/swap/src/lib.rs b/swap/src/lib.rs index 9fded336..9f98db70 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -9,6 +9,9 @@ pub mod network; pub const ONE_BTC: u64 = 100_000_000; +const REFUND_TIMELOCK: u32 = 10; // FIXME: What should this be? +const PUNISH_TIMELOCK: u32 = 20; // FIXME: What should this be? + pub type Never = std::convert::Infallible; /// Commands sent from Bob to the main task. @@ -29,13 +32,19 @@ pub enum Rsp { pub struct SwapParams { /// Amount of BTC to swap. #[serde(with = "::bitcoin::util::amount::serde::as_sat")] - pub btc: bitcoin::Amount, + pub btc: ::bitcoin::Amount, /// Amount of XMR to swap. - pub xmr: monero::Amount, + #[serde(with = "crate::monero::amount_serde")] + pub xmr: xmr_btc::monero::Amount, } impl Display for SwapParams { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} for {}", self.btc, self.xmr) + write!( + f, + "{} sats for {} piconeros", + self.btc.as_sat(), + self.xmr.as_piconero() + ) } } diff --git a/swap/src/main.rs b/swap/src/main.rs index fc2997ee..f796cb75 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -16,6 +16,7 @@ use anyhow::{bail, Result}; use futures::{channel::mpsc, StreamExt}; use libp2p::Multiaddr; use log::LevelFilter; +use rand::rngs::OsRng; use std::{io, io::Write, process}; use structopt::StructOpt; use tracing::info; @@ -38,7 +39,7 @@ async fn main() -> Result<()> { trace::init_tracing(LevelFilter::Debug)?; let addr = format!("/ip4/{}/tcp/{}", ADDR, PORT); - let alice_addr: Multiaddr = addr.parse().expect("failed to parse Alice's address"); + let alice: Multiaddr = addr.parse().expect("failed to parse Alice's address"); if opt.as_alice { info!("running swap node as Alice ..."); @@ -47,16 +48,23 @@ async fn main() -> Result<()> { bail!("Alice cannot set the amount to swap via the cli"); } - swap_as_alice(alice_addr).await?; + // TODO: Get these addresses from command line + let redeem = bitcoin::Address::default(); + let punish = bitcoin::Address::default(); + + swap_as_alice(alice, redeem, refund).await?; } else { info!("running swap node as Bob ..."); + // TODO: Get refund address from command line + let refund = bitcoin::Address::default(); + 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_addr).await?; + swap_as_bob(sats, alice_addr, refund).await?; } }; } @@ -64,14 +72,18 @@ async fn main() -> Result<()> { Ok(()) } -async fn swap_as_alice(addr: Multiaddr) -> Result<()> { - alice::swap(addr).await +async fn swap_as_alice( + addr: Multiaddr, + redeem: bitcoin::Address::default(), + punish: bitcoin::Address::default(), +) -> Result<()> { + alice::swap(addr, OsRng, redeem, punish).await } -async fn swap_as_bob(sats: u64, addr: Multiaddr) -> Result<()> { +async fn swap_as_bob(sats: u64, addr: Multiaddr, refund: bitcoin::Address) -> Result<()> { let (cmd_tx, mut cmd_rx) = mpsc::channel(1); let (mut rsp_tx, rsp_rx) = mpsc::channel(1); - tokio::spawn(bob::swap(sats, addr, cmd_tx, rsp_rx)); + tokio::spawn(bob::swap(sats, addr, cmd_tx, rsp_rx, OsRng, refund)); loop { let read = cmd_rx.next().await; match read { diff --git a/swap/src/monero.rs b/swap/src/monero.rs index 940a1922..43b571b8 100644 --- a/swap/src/monero.rs +++ b/swap/src/monero.rs @@ -1,30 +1,27 @@ -use serde::{Deserialize, Serialize}; -use std::fmt; +use serde::{de::Error, Deserialize, Deserializer, Serializer}; -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct Amount(u64); +use xmr_btc::monero::Amount; -impl Amount { - /// Create an [Amount] with piconero precision and the given number of - /// piconeros. - /// - /// A piconero (a.k.a atomic unit) is equal to 1e-12 XMR. - pub fn from_piconero(amount: u64) -> Self { - Amount(amount) +pub mod amount_serde { + use super::*; + use std::str::FromStr; + + pub fn serialize(value: &Amount, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&value.as_piconero().to_string()) } - pub fn as_piconero(&self) -> u64 { - self.0 - } -} - -impl From for u64 { - fn from(from: Amount) -> u64 { - from.0 - } -} - -impl fmt::Display for Amount { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} piconeros", self.0) + + pub fn deserialize<'de, D>(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + let value = + u64::from_str(value.as_str()).map_err(>::Error::custom)?; + let amount = Amount::from_piconero(value); + + Ok(amount) } } diff --git a/swap/src/network/request_response.rs b/swap/src/network/request_response.rs index deb19b73..4af3a222 100644 --- a/swap/src/network/request_response.rs +++ b/swap/src/network/request_response.rs @@ -7,7 +7,8 @@ use libp2p::{ use serde::{Deserialize, Serialize}; use std::{fmt::Debug, io}; -use crate::{bitcoin, monero, SwapParams}; +use crate::SwapParams; +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. @@ -16,18 +17,16 @@ pub const TIMEOUT: u64 = 3600; // One hour. #[derive(Clone, Debug, Serialize, Deserialize)] pub enum BobToAlice { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] - AmountsFromBtc(bitcoin::Amount), + AmountsFromBtc(::bitcoin::Amount), AmountsFromXmr(monero::Amount), - /* TODO: How are we going to do this when the messages are not Clone? - * Msg(bob::Message), */ + Message0(bob::Message0), } /// Messages Alice sends to Bob. #[derive(Clone, Debug, Serialize, Deserialize)] pub enum AliceToBob { Amounts(SwapParams), - /* TODO: How are we going to do this when the messages are not Clone? - * Msg(alice::Message) */ + Message0(alice::Message0), } #[derive(Debug, Clone, Copy, Default)]