diff --git a/swap/src/alice.rs b/swap/src/alice.rs index 9bf8e51c..c5aaa159 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -1,6 +1,7 @@ //! Run an XMR/BTC swap in the role of Alice. //! Alice holds XMR and wishes receive BTC. use anyhow::Result; +use async_recursion::async_recursion; use async_trait::async_trait; use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _}; use genawaiter::GeneratorState; @@ -25,6 +26,7 @@ use self::{amounts::*, message0::*, message1::*, message2::*, message3::*}; use crate::{ bitcoin, bitcoin::TX_LOCK_MINE_TIMEOUT, + io::Io, monero, network::{ peer_tracker::{self, PeerTracker}, @@ -36,6 +38,7 @@ use crate::{ storage::Database, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; + use xmr_btc::{ alice::{self, action_generator, Action, ReceiveBitcoinRedeemEncsig, State0}, bitcoin::BroadcastSignedTransaction, @@ -43,6 +46,120 @@ use xmr_btc::{ monero::{CreateWalletForOutput, Transfer}, }; +// The same data structure is used for swap execution and recovery. +// This allows for a seamless transition from a failed swap to recovery. +pub enum AliceState { + Started, + Negotiated, + BtcLocked, + XmrLocked, + BtcRedeemed, + XmrRefunded, + Cancelled, + Punished, + SafelyAborted, +} + +// State machine driver for swap execution +#[async_recursion] +pub async fn simple_swap(state: AliceState, io: Io) -> 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 + } + 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 + } + AliceState::BtcLocked => { + // Alice has seen that Bob has locked BTC + // Todo: Alice locks XMR + simple_swap(AliceState::XmrLocked, io).await + } + AliceState::XmrLocked => { + // Alice has locked Xmr + // Alice waits until Bob sends her key to redeem BTC + // 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 + } else { + // submit TxCancel + simple_swap(AliceState::Cancelled, io).await + } + } + AliceState::Cancelled => { + // Wait until t2 or if TxRefund is seen + // 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 + } else { + simple_swap(AliceState::Punished, io).await + } + } + AliceState::XmrRefunded => Ok(AliceState::XmrRefunded), + AliceState::BtcRedeemed => Ok(AliceState::BtcRedeemed), + AliceState::Punished => Ok(AliceState::Punished), + AliceState::SafelyAborted => Ok(AliceState::SafelyAborted), + } +} + +// State machine driver for recovery execution +#[async_recursion] +pub async fn abort(state: AliceState, io: Io) -> Result { + match state { + AliceState::Started => { + // Nothing has been commited by either party, abort swap. + abort(AliceState::SafelyAborted, io).await + } + AliceState::Negotiated => { + // Nothing has been commited by either party, abort swap. + abort(AliceState::SafelyAborted, io).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 + } + 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 + } else if unimplemented!("T1Elapsed") { + // publish TxCancel or see if it has been published + abort(AliceState::Cancelled, io).await + } else { + Err(unimplemented!()) + } + } + AliceState::Cancelled => { + // Alice has cancelled the swap + // Alice waits watches for t2 or TxRefund + if unimplemented!("TxRefundSeen") { + // Bob has refunded and leaked s_b + abort(AliceState::XmrRefunded, io).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 + } else { + Err(unimplemented!()) + } + } + AliceState::BtcRedeemed => Ok(AliceState::BtcRedeemed), + AliceState::XmrRefunded => Ok(AliceState::XmrRefunded), + AliceState::Punished => Ok(AliceState::Punished), + AliceState::SafelyAborted => Ok(AliceState::SafelyAborted), + } +} + pub async fn swap( bitcoin_wallet: Arc, monero_wallet: Arc, diff --git a/swap/src/bob.rs b/swap/src/bob.rs index ea424e18..f7bf16af 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -1,19 +1,7 @@ //! Run an XMR/BTC swap in the role of Bob. //! Bob holds BTC and wishes receive XMR. -use self::{amounts::*, message0::*, message1::*, message2::*, message3::*}; -use crate::{ - bitcoin::{self, TX_LOCK_MINE_TIMEOUT}, - monero, - network::{ - peer_tracker::{self, PeerTracker}, - transport::SwapTransport, - TokioExecutor, - }, - state, - storage::Database, - Cmd, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, -}; use anyhow::Result; +use async_recursion::async_recursion; use async_trait::async_trait; use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _}; use futures::{ @@ -27,6 +15,28 @@ use std::{process, sync::Arc, time::Duration}; use tokio::sync::Mutex; use tracing::{debug, info, warn}; use uuid::Uuid; + +mod amounts; +mod message0; +mod message1; +mod message2; +mod message3; + +use self::{amounts::*, message0::*, message1::*, message2::*, message3::*}; +use crate::{ + bitcoin::{self, TX_LOCK_MINE_TIMEOUT}, + io::Io, + monero, + network::{ + peer_tracker::{self, PeerTracker}, + transport::SwapTransport, + TokioExecutor, + }, + state, + storage::Database, + Cmd, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, +}; + use xmr_btc::{ alice, bitcoin::{BroadcastSignedTransaction, EncryptedSignature, SignTxLock}, @@ -34,11 +44,128 @@ use xmr_btc::{ monero::CreateWalletForOutput, }; -mod amounts; -mod message0; -mod message1; -mod message2; -mod message3; +// The same data structure is used for swap execution and recovery. +// This allows for a seamless transition from a failed swap to recovery. +pub enum BobState { + Started, + Negotiated, + BtcLocked, + XmrLocked, + BtcRedeemed, + BtcRefunded, + XmrRedeemed, + Cancelled, + Punished, + SafelyAborted, +} + +// State machine driver for swap execution +#[async_recursion] +pub async fn simple_swap(state: BobState, io: Io) -> Result { + match state { + BobState::Started => { + // Alice and Bob exchange swap info + // Todo: Poll the swarm here until Alice and Bob have exchanged info + simple_swap(BobState::Negotiated, io).await + } + BobState::Negotiated => { + // Alice and Bob have exchanged info + // Bob Locks Btc + simple_swap(BobState::BtcLocked, io).await + } + BobState::BtcLocked => { + // Bob has locked Btc + // Watch for Alice to Lock Xmr + simple_swap(BobState::XmrLocked, io).await + } + BobState::XmrLocked => { + // Alice has locked Xmr + // Bob sends Alice his key + // Todo: This should be a oneshot + if unimplemented!("Redeemed before t1") { + simple_swap(BobState::BtcRedeemed, io).await + } else { + // submit TxCancel + simple_swap(BobState::Cancelled, io).await + } + } + BobState::Cancelled => { + if unimplemented!(" Ok(BobState::BtcRefunded), + BobState::BtcRedeemed => { + // Bob redeems XMR using revealed s_a + simple_swap(BobState::XmrRedeemed, io).await + } + BobState::Punished => Ok(BobState::Punished), + BobState::SafelyAborted => Ok(BobState::SafelyAborted), + BobState::XmrRedeemed => Ok(BobState::XmrRedeemed), + } +} + +// State machine driver for recovery execution +#[async_recursion] +pub async fn abort(state: BobState, io: Io) -> Result { + match state { + BobState::Started => { + // Nothing has been commited by either party, abort swap. + abort(BobState::SafelyAborted, io).await + } + BobState::Negotiated => { + // Nothing has been commited by either party, abort swap. + abort(BobState::SafelyAborted, io).await + } + BobState::BtcLocked => { + // Bob has locked BTC and must refund it + // Bob waits for alice to publish TxRedeem or t1 + if unimplemented!("TxRedeemSeen") { + // Alice has redeemed revealing s_a + abort(BobState::BtcRedeemed, io).await + } else if unimplemented!("T1Elapsed") { + // publish TxCancel or see if it has been published + abort(BobState::Cancelled, io).await + } else { + Err(unimplemented!()) + } + } + BobState::XmrLocked => { + // Alice has locked Xmr + // Wait until t1 + if unimplemented!(">t1 and t2 + // submit TxCancel + abort(BobState::Punished, io).await + } + } + BobState::Cancelled => { + // Bob has cancelled the swap + // If { + // Bob uses revealed s_a to redeem XMR + abort(BobState::XmrRedeemed, io).await + } + BobState::BtcRefunded => Ok(BobState::BtcRefunded), + BobState::Punished => Ok(BobState::Punished), + BobState::SafelyAborted => Ok(BobState::SafelyAborted), + BobState::XmrRedeemed => Ok(BobState::XmrRedeemed), + } +} #[allow(clippy::too_many_arguments)] pub async fn swap( @@ -97,9 +224,6 @@ pub async fn swap( swarm.request_amounts(alice.clone(), btc); - // What is going on here, shouldn't this be a simple req/resp?? - // Why do we need mspc channels? - // Todo: simplify this code let (btc, xmr) = match swarm.next().await { OutEvent::Amounts(amounts) => { info!("Got amounts from Alice: {:?}", amounts); @@ -110,6 +234,7 @@ pub async fn swap( info!("User rejected amounts proposed by Alice, aborting..."); process::exit(0); } + info!("User accepted amounts proposed by Alice"); (amounts.btc, amounts.xmr) } @@ -237,7 +362,7 @@ pub async fn swap( pub type Swarm = libp2p::Swarm; -pub fn new_swarm(transport: SwapTransport, behaviour: Bob) -> Result { +fn new_swarm(transport: SwapTransport, behaviour: Bob) -> Result { let local_peer_id = behaviour.peer_id(); let swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id.clone()) diff --git a/swap/src/io.rs b/swap/src/io.rs new file mode 100644 index 00000000..5b26d09a --- /dev/null +++ b/swap/src/io.rs @@ -0,0 +1,8 @@ +// This struct contains all the I/O required to execute a swap +pub struct Io { + // swarm: libp2p::Swarm<>, +// bitcoind_rpc: _, +// monerod_rpc: _, +// monero_wallet_rpc: _, +// db: _, +} diff --git a/swap/src/lib.rs b/swap/src/lib.rs index 5ec6d6be..ea801e64 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -6,6 +6,7 @@ pub mod bitcoin; pub mod bob; pub mod bob_simple; pub mod cli; +pub mod io; pub mod monero; pub mod network; pub mod recover;