diff --git a/swap/src/alice.rs b/swap/src/alice.rs index 4803534c..6c44cdac 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -4,7 +4,6 @@ use self::{amounts::*, message0::*, message1::*, message2::*, message3::*}; use crate::{ bitcoin, bitcoin::TX_LOCK_MINE_TIMEOUT, - io::Io, monero, network::{ peer_tracker::{self, PeerTracker}, @@ -16,7 +15,7 @@ use crate::{ storage::Database, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; -use anyhow::Result; +use anyhow::{bail, Result}; use async_recursion::async_recursion; use async_trait::async_trait; use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _}; @@ -60,23 +59,99 @@ pub enum AliceState { // State machine driver for swap execution #[async_recursion] -pub async fn simple_swap(state: AliceState, io: Io) -> Result { +pub async fn simple_swap( + state: AliceState, + &mut rng: R, + mut swarm: Swarm, + bitcoin_wallet: Arc, +) -> Result { match state { AliceState::Started => { - // Alice and Bob exchange swap info - // Todo: Poll the swarm here until Alice and Bob have exchanged info - simple_swap(AliceState::Negotiated, io).await + // Bob dials us + let bob_peer_id = match swarm.next().await { + OutEvent::ConnectionEstablished(bob_peer_id) => bob_peer_id, + other => bail!("Unexpected event received: {:?}", other), + }; + + // Bob sends us a request + let (btc, channel) = match swarm.next().await { + OutEvent::Request(amounts::OutEvent::Btc { btc, channel }) => (btc, channel), + other => bail!("Unexpected event received: {:?}", other), + }; + + let amounts = calculate_amounts(btc); + swarm.send_amounts(channel, amounts); + + let SwapAmounts { btc, xmr } = amounts; + + let redeem_address = bitcoin_wallet.as_ref().new_address().await?; + let punish_address = redeem_address.clone(); + + let state0 = State0::new( + rng, + btc, + xmr, + REFUND_TIMELOCK, + PUNISH_TIMELOCK, + redeem_address, + punish_address, + ); + + // TODO(Franck) This is not needed + // Review if the swarm really needs to store states + swarm.set_state0(state0.clone()); + + // Bob sends us message0 + let message0 = match swarm.next().await { + OutEvent::Message0(msg) => msg, + other => bail!("Unexpected event received: {:?}", other), + }; + + let state1 = state0.receive(message0)?; + + // TODO(Franck) We should use the same channel everytime, + // Can we remove this response channel? + let (state2, channel) = match swarm.next().await { + OutEvent::Message1 { msg, channel } => { + let state2 = state1.receive(msg); + (state2, channel) + } + other => bail!("Unexpected event: {:?}", other), + }; + + let message1 = state2.next_message(); + swarm.send_message1(channel, message1); + + let (state3, channel) = match swarm.next().await { + OutEvent::Message2 { msg, channel } => { + let state3 = state2.receive(msg)?; + (state3, channel) + } + other => bail!("Unexpected event: {:?}", other), + }; + + let swap_id = Uuid::new_v4(); + // TODO(Franck): Use the same terminology (negotiated) to describe this state. + db.insert_latest_state(swap_id, state::Alice::Handshaken(state3.clone()).into()) + .await?; + + info!( + "State transitioned from Started to Negotiated, Bob peer id is {}", + bob_peer_id + ); + + simple_swap(AliceState::Negotiated, swarm, rng, bitcoin_wallet).await } AliceState::Negotiated => { // Alice and Bob have exchanged info // Todo: Alice watches for BTC to be locked on chain // Todo: Timeout at t1? - simple_swap(AliceState::BtcLocked, io).await + simple_swap(AliceState::BtcLocked, swarm, rng, bitcoin_wallet).await } AliceState::BtcLocked => { // Alice has seen that Bob has locked BTC // Todo: Alice locks XMR - simple_swap(AliceState::XmrLocked, io).await + simple_swap(AliceState::XmrLocked, swarm, bitcoin_wallet).await } AliceState::XmrLocked => { // Alice has locked Xmr @@ -84,10 +159,10 @@ pub async fn simple_swap(state: AliceState, io: Io) -> Result { // Todo: Poll the swarm here until msg from Bob arrives or t1 if unimplemented!("key_received") { // Alice redeems BTC - simple_swap(AliceState::BtcRedeemed, io).await + simple_swap(AliceState::BtcRedeemed, swarm, bitcoin_wallet).await } else { // submit TxCancel - simple_swap(AliceState::Cancelled, io).await + simple_swap(AliceState::Cancelled, swarm, bitcoin_wallet).await } } AliceState::Cancelled => { @@ -95,9 +170,9 @@ pub async fn simple_swap(state: AliceState, io: Io) -> Result { // If Bob has refunded the Alice should extract Bob's monero secret key and move // the TxLockXmr output to her wallet. if unimplemented!("refunded") { - simple_swap(AliceState::XmrRefunded, io).await + simple_swap(AliceState::XmrRefunded, swarm, bitcoin_wallet).await } else { - simple_swap(AliceState::Punished, io).await + simple_swap(AliceState::Punished, swarm, bitcoin_wallet).await } } AliceState::XmrRefunded => Ok(AliceState::XmrRefunded), @@ -109,30 +184,30 @@ pub async fn simple_swap(state: AliceState, io: Io) -> Result { // State machine driver for recovery execution #[async_recursion] -pub async fn abort(state: AliceState, io: Io) -> Result { +pub async fn abort(state: AliceState) -> Result { match state { AliceState::Started => { // Nothing has been commited by either party, abort swap. - abort(AliceState::SafelyAborted, io).await + abort(AliceState::SafelyAborted).await } AliceState::Negotiated => { // Nothing has been commited by either party, abort swap. - abort(AliceState::SafelyAborted, io).await + abort(AliceState::SafelyAborted).await } AliceState::BtcLocked => { // Alice has seen that Bob has locked BTC // Alice does not need to do anything to recover - abort(AliceState::SafelyAborted, io).await + abort(AliceState::SafelyAborted).await } AliceState::XmrLocked => { // Alice has locked XMR // Alice watches for TxRedeem until t1 if unimplemented!("TxRedeemSeen") { // Alice has successfully redeemed, protocol was a success - abort(AliceState::BtcRedeemed, io).await + abort(AliceState::BtcRedeemed).await } else if unimplemented!("T1Elapsed") { // publish TxCancel or see if it has been published - abort(AliceState::Cancelled, io).await + abort(AliceState::Cancelled).await } else { Err(unimplemented!()) } @@ -142,11 +217,11 @@ pub async fn abort(state: AliceState, io: Io) -> Result { // Alice waits watches for t2 or TxRefund if unimplemented!("TxRefundSeen") { // Bob has refunded and leaked s_b - abort(AliceState::XmrRefunded, io).await + abort(AliceState::XmrRefunded).await } else if unimplemented!("T1Elapsed") { // publish TxCancel or see if it has been published // Wait until t2 and publish TxPunish - abort(AliceState::Punished, io).await + abort(AliceState::Punished).await } else { Err(unimplemented!()) } @@ -164,7 +239,7 @@ pub async fn swap( db: Database, listen: Multiaddr, transport: SwapTransport, - behaviour: Alice, + behaviour: Behaviour, ) -> Result<()> { struct Network(Arc>); @@ -359,9 +434,9 @@ pub async fn swap( } } -pub type Swarm = libp2p::Swarm; +pub type Swarm = libp2p::Swarm; -fn new_swarm(listen: Multiaddr, transport: SwapTransport, behaviour: Alice) -> Result { +fn new_swarm(listen: Multiaddr, transport: SwapTransport, behaviour: Behaviour) -> Result { use anyhow::Context as _; let local_peer_id = behaviour.peer_id(); @@ -384,6 +459,8 @@ fn new_swarm(listen: Multiaddr, transport: SwapTransport, behaviour: Alice) -> R #[derive(Debug)] pub enum OutEvent { ConnectionEstablished(PeerId), + // TODO (Franck): Change this to get both amounts so parties can verify the amounts are + // expected early on. Request(amounts::OutEvent), // Not-uniform with Bob on purpose, ready for adding Xmr event. Message0(bob::Message0), Message1 { @@ -446,7 +523,7 @@ impl From for OutEvent { #[derive(NetworkBehaviour)] #[behaviour(out_event = "OutEvent", event_process = false)] #[allow(missing_debug_implementations)] -pub struct Alice { +pub struct Behaviour { pt: PeerTracker, amounts: Amounts, message0: Message0, @@ -457,7 +534,7 @@ pub struct Alice { identity: Keypair, } -impl Alice { +impl Behaviour { pub fn identity(&self) -> Keypair { self.identity.clone() } @@ -490,7 +567,7 @@ impl Alice { } } -impl Default for Alice { +impl Default for Behaviour { fn default() -> Self { let identity = Keypair::generate_ed25519(); @@ -507,8 +584,8 @@ impl Default for Alice { } fn calculate_amounts(btc: ::bitcoin::Amount) -> SwapAmounts { - // TODO: Get this from an exchange. - // This value corresponds to 100 XMR per BTC + // TODO (Franck): This should instead verify that the received amounts matches + // the command line arguments This value corresponds to 100 XMR per BTC const PICONERO_PER_SAT: u64 = 1_000_000; let picos = btc.as_sat() * PICONERO_PER_SAT; diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index b9a9e45f..2e24c418 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -19,7 +19,7 @@ use prettytable::{row, Table}; use std::{io, io::Write, process, sync::Arc}; use structopt::StructOpt; use swap::{ - alice::{self, Alice}, + alice::{self, Behaviour}, bitcoin, bob::{self, Bob}, cli::Options, @@ -52,7 +52,7 @@ async fn main() -> Result<()> { } => { info!("running swap node as Alice ..."); - let behaviour = Alice::default(); + let behaviour = Behaviour::default(); let local_key_pair = behaviour.identity(); let (listen_addr, _ac, transport) = match tor_port { @@ -180,7 +180,7 @@ async fn swap_as_alice( db: Database, addr: Multiaddr, transport: SwapTransport, - behaviour: Alice, + behaviour: Behaviour, ) -> Result<()> { alice::swap( bitcoin_wallet, diff --git a/swap/src/bob.rs b/swap/src/bob.rs index f7bf16af..31a2819b 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -36,7 +36,6 @@ use crate::{ storage::Database, Cmd, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; - use xmr_btc::{ alice, bitcoin::{BroadcastSignedTransaction, EncryptedSignature, SignTxLock}, diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 7323262f..5abbe191 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -74,7 +74,7 @@ async fn swap() { )); let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet("bob").unwrap().client())); - let alice_behaviour = alice::Alice::default(); + let alice_behaviour = alice::Behaviour::default(); let alice_transport = build(alice_behaviour.identity()).unwrap(); let db = Database::open(std::path::Path::new("../.swap-db/")).unwrap();