Consolidate and simplify swap execution. Generators are no longer needed. Consolidate recovery and swap data structures. The recursive calls can be replaced with a loop if returning prior to completion is desired for testing purposes. Fill out alice abort path Move state machine executors into seperate files Not compiling due to recursion/async issues Fix async recursion compilation errors Fix Bob swap execution Remove check for ack message from Alice. Seems like a bad idea to rely on an acknowledgement message instead of looking at the blockchain. Fix Bob abort Fix warnings Xmr lock complete Add TxCancel submit to XmrLocked Bob swap completed Remove alicepull/41/head
parent
e7682a42a4
commit
dd07e2f882
@ -1,461 +0,0 @@
|
||||
//! 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 backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _};
|
||||
use genawaiter::GeneratorState;
|
||||
use libp2p::{
|
||||
core::{identity::Keypair, Multiaddr},
|
||||
request_response::ResponseChannel,
|
||||
NetworkBehaviour, PeerId,
|
||||
};
|
||||
use rand::rngs::OsRng;
|
||||
use std::{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,
|
||||
bitcoin::TX_LOCK_MINE_TIMEOUT,
|
||||
monero,
|
||||
network::{
|
||||
peer_tracker::{self, PeerTracker},
|
||||
request_response::AliceToBob,
|
||||
transport::SwapTransport,
|
||||
TokioExecutor,
|
||||
},
|
||||
state,
|
||||
storage::Database,
|
||||
SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
|
||||
};
|
||||
use xmr_btc::{
|
||||
alice::{self, action_generator, Action, ReceiveBitcoinRedeemEncsig, State0},
|
||||
bitcoin::BroadcastSignedTransaction,
|
||||
bob,
|
||||
monero::{CreateWalletForOutput, Transfer},
|
||||
};
|
||||
|
||||
pub async fn swap(
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
db: Database,
|
||||
listen: Multiaddr,
|
||||
transport: SwapTransport,
|
||||
behaviour: Alice,
|
||||
) -> Result<()> {
|
||||
struct Network {
|
||||
swarm: Arc<Mutex<Swarm>>,
|
||||
channel: Option<ResponseChannel<AliceToBob>>,
|
||||
}
|
||||
|
||||
impl Network {
|
||||
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) => {
|
||||
let mut guard = self.swarm.lock().await;
|
||||
guard.send_message2(channel, alice::Message2 {
|
||||
tx_lock_proof: proof,
|
||||
});
|
||||
info!("Sent transfer proof");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) -> bitcoin::EncryptedSignature {
|
||||
#[derive(Debug)]
|
||||
struct UnexpectedMessage;
|
||||
|
||||
let encsig = (|| 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 Bitcoin redeem encsig, got: {:?}", other);
|
||||
return Err(backoff::Error::Transient(UnexpectedMessage));
|
||||
}
|
||||
};
|
||||
|
||||
Result::<_, backoff::Error<UnexpectedMessage>>::Ok(encsig)
|
||||
})
|
||||
.retry(ConstantBackoff::new(Duration::from_secs(1)))
|
||||
.await
|
||||
.expect("transient errors to be retried");
|
||||
|
||||
info!("Received Bitcoin redeem encsig");
|
||||
|
||||
encsig
|
||||
}
|
||||
}
|
||||
|
||||
let mut swarm = new_swarm(listen, transport, behaviour)?;
|
||||
let message0: bob::Message0;
|
||||
let mut state0: Option<alice::State0> = None;
|
||||
let mut last_amounts: Option<SwapAmounts> = 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(bob) => {
|
||||
info!("Connection established with: {}", bob);
|
||||
}
|
||||
OutEvent::Request(amounts::OutEvent::Btc { btc, channel }) => {
|
||||
let amounts = calculate_amounts(btc);
|
||||
last_amounts = Some(amounts);
|
||||
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();
|
||||
|
||||
// TODO: Pass this in using <R: RngCore + CryptoRng>
|
||||
let rng = &mut OsRng;
|
||||
let state = State0::new(
|
||||
rng,
|
||||
btc,
|
||||
xmr,
|
||||
REFUND_TIMELOCK,
|
||||
PUNISH_TIMELOCK,
|
||||
redeem_address,
|
||||
punish_address,
|
||||
);
|
||||
|
||||
info!("Commencing handshake");
|
||||
swarm.set_state0(state.clone());
|
||||
|
||||
state0 = Some(state)
|
||||
}
|
||||
OutEvent::Message0(msg) => {
|
||||
// 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() {
|
||||
// TODO: We should verify the amounts and notify Bob if they have changed.
|
||||
message0 = msg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
other => panic!("Unexpected event: {:?}", other),
|
||||
};
|
||||
}
|
||||
|
||||
let state1 = state0.expect("to be set").receive(message0)?;
|
||||
|
||||
let (state2, channel) = match swarm.next().await {
|
||||
OutEvent::Message1 { msg, channel } => {
|
||||
let state2 = state1.receive(msg);
|
||||
(state2, channel)
|
||||
}
|
||||
other => panic!("Unexpected event: {:?}", other),
|
||||
};
|
||||
|
||||
let msg = state2.next_message();
|
||||
swarm.send_message1(channel, msg);
|
||||
|
||||
let (state3, channel) = match swarm.next().await {
|
||||
OutEvent::Message2 { msg, channel } => {
|
||||
let state3 = state2.receive(msg)?;
|
||||
(state3, channel)
|
||||
}
|
||||
other => panic!("Unexpected event: {:?}", other),
|
||||
};
|
||||
|
||||
let swap_id = Uuid::new_v4();
|
||||
db.insert_latest_state(swap_id, state::Alice::Handshaken(state3.clone()).into())
|
||||
.await?;
|
||||
|
||||
info!("Handshake complete, we now have State3 for Alice.");
|
||||
|
||||
let network = Arc::new(Mutex::new(Network {
|
||||
swarm: Arc::new(Mutex::new(swarm)),
|
||||
channel: Some(channel),
|
||||
}));
|
||||
|
||||
let mut action_generator = action_generator(
|
||||
network.clone(),
|
||||
bitcoin_wallet.clone(),
|
||||
state3.clone(),
|
||||
TX_LOCK_MINE_TIMEOUT,
|
||||
);
|
||||
|
||||
loop {
|
||||
let state = action_generator.async_resume().await;
|
||||
|
||||
tracing::info!("Resumed execution of generator, got: {:?}", state);
|
||||
|
||||
match state {
|
||||
GeneratorState::Yielded(Action::LockXmr {
|
||||
amount,
|
||||
public_spend_key,
|
||||
public_view_key,
|
||||
}) => {
|
||||
db.insert_latest_state(swap_id, state::Alice::BtcLocked(state3.clone()).into())
|
||||
.await?;
|
||||
|
||||
let (transfer_proof, _) = monero_wallet
|
||||
.transfer(public_spend_key, public_view_key, amount)
|
||||
.await?;
|
||||
|
||||
db.insert_latest_state(swap_id, state::Alice::XmrLocked(state3.clone()).into())
|
||||
.await?;
|
||||
|
||||
let mut guard = network.as_ref().lock().await;
|
||||
guard.send_message2(transfer_proof).await;
|
||||
info!("Sent transfer proof");
|
||||
}
|
||||
|
||||
GeneratorState::Yielded(Action::RedeemBtc(tx)) => {
|
||||
db.insert_latest_state(
|
||||
swap_id,
|
||||
state::Alice::BtcRedeemable {
|
||||
state: state3.clone(),
|
||||
redeem_tx: tx.clone(),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
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)) => {
|
||||
db.insert_latest_state(swap_id, state::Alice::BtcPunishable(state3.clone()).into())
|
||||
.await?;
|
||||
|
||||
let _ = bitcoin_wallet.broadcast_signed_transaction(tx).await?;
|
||||
}
|
||||
GeneratorState::Yielded(Action::CreateMoneroWalletForOutput {
|
||||
spend_key,
|
||||
view_key,
|
||||
}) => {
|
||||
db.insert_latest_state(
|
||||
swap_id,
|
||||
state::Alice::BtcRefunded {
|
||||
state: state3.clone(),
|
||||
spend_key,
|
||||
view_key,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
monero_wallet
|
||||
.create_and_load_wallet_for_output(spend_key, view_key)
|
||||
.await?;
|
||||
}
|
||||
GeneratorState::Complete(()) => {
|
||||
db.insert_latest_state(swap_id, state::Alice::SwapComplete.into())
|
||||
.await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Swarm = libp2p::Swarm<Alice>;
|
||||
|
||||
fn new_swarm(listen: Multiaddr, transport: SwapTransport, behaviour: Alice) -> Result<Swarm> {
|
||||
use anyhow::Context as _;
|
||||
|
||||
let local_peer_id = behaviour.peer_id();
|
||||
|
||||
let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id.clone())
|
||||
.executor(Box::new(TokioExecutor {
|
||||
handle: tokio::runtime::Handle::current(),
|
||||
}))
|
||||
.build();
|
||||
|
||||
Swarm::listen_on(&mut swarm, listen.clone())
|
||||
.with_context(|| format!("Address is not supported: {:#}", listen))?;
|
||||
|
||||
tracing::info!("Initialized swarm: {}", local_peer_id);
|
||||
|
||||
Ok(swarm)
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug)]
|
||||
pub enum OutEvent {
|
||||
ConnectionEstablished(PeerId),
|
||||
Request(amounts::OutEvent), // Not-uniform with Bob on purpose, ready for adding Xmr event.
|
||||
Message0(bob::Message0),
|
||||
Message1 {
|
||||
msg: bob::Message1,
|
||||
channel: ResponseChannel<AliceToBob>,
|
||||
},
|
||||
Message2 {
|
||||
msg: bob::Message2,
|
||||
channel: ResponseChannel<AliceToBob>,
|
||||
},
|
||||
Message3(bob::Message3),
|
||||
}
|
||||
|
||||
impl From<peer_tracker::OutEvent> for OutEvent {
|
||||
fn from(event: peer_tracker::OutEvent) -> Self {
|
||||
match event {
|
||||
peer_tracker::OutEvent::ConnectionEstablished(id) => {
|
||||
OutEvent::ConnectionEstablished(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<amounts::OutEvent> for OutEvent {
|
||||
fn from(event: amounts::OutEvent) -> Self {
|
||||
OutEvent::Request(event)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<message0::OutEvent> for OutEvent {
|
||||
fn from(event: message0::OutEvent) -> Self {
|
||||
match event {
|
||||
message0::OutEvent::Msg(msg) => OutEvent::Message0(msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<message1::OutEvent> for OutEvent {
|
||||
fn from(event: message1::OutEvent) -> Self {
|
||||
match event {
|
||||
message1::OutEvent::Msg { msg, channel } => OutEvent::Message1 { msg, channel },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<message2::OutEvent> for OutEvent {
|
||||
fn from(event: message2::OutEvent) -> Self {
|
||||
match event {
|
||||
message2::OutEvent::Msg { msg, channel } => OutEvent::Message2 { msg, channel },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<message3::OutEvent> 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)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Alice {
|
||||
pt: PeerTracker,
|
||||
amounts: Amounts,
|
||||
message0: Message0,
|
||||
message1: Message1,
|
||||
message2: Message2,
|
||||
message3: Message3,
|
||||
#[behaviour(ignore)]
|
||||
identity: Keypair,
|
||||
}
|
||||
|
||||
impl Alice {
|
||||
pub fn identity(&self) -> Keypair {
|
||||
self.identity.clone()
|
||||
}
|
||||
|
||||
pub fn peer_id(&self) -> PeerId {
|
||||
PeerId::from(self.identity.public())
|
||||
}
|
||||
|
||||
/// Alice always sends her messages as a response to a request from Bob.
|
||||
pub fn send_amounts(&mut self, channel: ResponseChannel<AliceToBob>, 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) {
|
||||
debug!("Set state 0");
|
||||
let _ = self.message0.set_state(state);
|
||||
}
|
||||
|
||||
/// Send Message1 to Bob in response to receiving his Message1.
|
||||
pub fn send_message1(
|
||||
&mut self,
|
||||
channel: ResponseChannel<AliceToBob>,
|
||||
msg: xmr_btc::alice::Message1,
|
||||
) {
|
||||
self.message1.send(channel, msg);
|
||||
debug!("Sent Message1");
|
||||
}
|
||||
|
||||
/// Send Message2 to Bob in response to receiving his Message2.
|
||||
pub fn send_message2(
|
||||
&mut self,
|
||||
channel: ResponseChannel<AliceToBob>,
|
||||
msg: xmr_btc::alice::Message2,
|
||||
) {
|
||||
self.message2.send(channel, msg);
|
||||
debug!("Sent Message2");
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Alice {
|
||||
fn default() -> Self {
|
||||
let identity = Keypair::generate_ed25519();
|
||||
|
||||
Self {
|
||||
pt: PeerTracker::default(),
|
||||
amounts: Amounts::default(),
|
||||
message0: Message0::default(),
|
||||
message1: Message1::default(),
|
||||
message2: Message2::default(),
|
||||
message3: Message3::default(),
|
||||
identity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_amounts(btc: ::bitcoin::Amount) -> SwapAmounts {
|
||||
// TODO: Get this from an exchange.
|
||||
// This value corresponds to 100 XMR per BTC
|
||||
const PICONERO_PER_SAT: u64 = 1_000_000;
|
||||
|
||||
let picos = btc.as_sat() * PICONERO_PER_SAT;
|
||||
let xmr = monero::Amount::from_piconero(picos);
|
||||
|
||||
SwapAmounts { btc, xmr }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const ONE_BTC: u64 = 100_000_000;
|
||||
const HUNDRED_XMR: u64 = 100_000_000_000_000;
|
||||
|
||||
#[test]
|
||||
fn one_bitcoin_equals_a_hundred_moneroj() {
|
||||
let btc = ::bitcoin::Amount::from_sat(ONE_BTC);
|
||||
let want = monero::Amount::from_piconero(HUNDRED_XMR);
|
||||
|
||||
let SwapAmounts { xmr: got, .. } = calculate_amounts(btc);
|
||||
assert_eq!(got, want);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
use anyhow::Result;
|
||||
use structopt::StructOpt;
|
||||
use swap::{
|
||||
bob_simple::{simple_swap, BobState},
|
||||
cli::Options,
|
||||
storage::Database,
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let opt = Options::from_args();
|
||||
|
||||
let db = Database::open(std::path::Path::new("./.swap-db/")).unwrap();
|
||||
let swarm = unimplemented!();
|
||||
let bitcoin_wallet = unimplemented!();
|
||||
let monero_wallet = unimplemented!();
|
||||
let mut rng = unimplemented!();
|
||||
let bob_state = unimplemented!();
|
||||
|
||||
match opt {
|
||||
Options::Alice { .. } => {
|
||||
simple_swap(bob_state, swarm, db, bitcoin_wallet, monero_wallet, rng).await?;
|
||||
}
|
||||
Options::Recover { .. } => {
|
||||
let _stored_state: BobState = unimplemented!("io.get_state(uuid)?");
|
||||
// abort(_stored_state, _io);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
@ -0,0 +1,304 @@
|
||||
use crate::{
|
||||
bitcoin::{self},
|
||||
bob::{OutEvent, Swarm},
|
||||
network::{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::{
|
||||
channel::mpsc::{Receiver, Sender},
|
||||
future::Either,
|
||||
FutureExt, StreamExt,
|
||||
};
|
||||
use genawaiter::GeneratorState;
|
||||
use libp2p::{core::identity::Keypair, Multiaddr, NetworkBehaviour, PeerId};
|
||||
use rand::{rngs::OsRng, CryptoRng, RngCore};
|
||||
use std::{process, sync::Arc, time::Duration};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{debug, info, warn};
|
||||
use uuid::Uuid;
|
||||
use xmr_btc::{
|
||||
alice,
|
||||
bitcoin::{
|
||||
poll_until_block_height_is_gte, BroadcastSignedTransaction, EncryptedSignature, SignTxLock,
|
||||
TransactionBlockHeight,
|
||||
},
|
||||
bob::{self, action_generator, ReceiveTransferProof, State0},
|
||||
monero::CreateWalletForOutput,
|
||||
};
|
||||
|
||||
// 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(Sender<Cmd>, Receiver<Rsp>, u64, PeerId),
|
||||
Negotiated(bob::State2, PeerId),
|
||||
BtcLocked(bob::State3, PeerId),
|
||||
XmrLocked(bob::State4, PeerId),
|
||||
EncSigSent(bob::State4, PeerId),
|
||||
BtcRedeemed(bob::State5),
|
||||
Cancelled(bob::State4),
|
||||
BtcRefunded,
|
||||
XmrRedeemed,
|
||||
Punished,
|
||||
SafelyAborted,
|
||||
}
|
||||
|
||||
// State machine driver for swap execution
|
||||
#[async_recursion]
|
||||
pub async fn simple_swap(
|
||||
state: BobState,
|
||||
mut swarm: Swarm,
|
||||
db: Database,
|
||||
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
||||
monero_wallet: Arc<crate::monero::Wallet>,
|
||||
mut rng: OsRng,
|
||||
) -> Result<BobState> {
|
||||
match state {
|
||||
BobState::Started(mut cmd_tx, mut rsp_rx, btc, alice_peer_id) => {
|
||||
// todo: dial the swarm outside
|
||||
// libp2p::Swarm::dial_addr(&mut swarm, addr)?;
|
||||
let alice = match swarm.next().await {
|
||||
OutEvent::ConnectionEstablished(alice) => alice,
|
||||
other => panic!("unexpected event: {:?}", other),
|
||||
};
|
||||
info!("Connection established with: {}", alice);
|
||||
|
||||
swarm.request_amounts(alice.clone(), btc);
|
||||
|
||||
// todo: remove mspc channel
|
||||
let (btc, xmr) = match swarm.next().await {
|
||||
OutEvent::Amounts(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!("User rejected amounts proposed by Alice, aborting...");
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
info!("User accepted amounts proposed by Alice");
|
||||
(amounts.btc, amounts.xmr)
|
||||
}
|
||||
other => panic!("unexpected event: {:?}", other),
|
||||
};
|
||||
|
||||
let refund_address = bitcoin_wallet.new_address().await?;
|
||||
|
||||
let state0 = State0::new(
|
||||
&mut rng,
|
||||
btc,
|
||||
xmr,
|
||||
REFUND_TIMELOCK,
|
||||
PUNISH_TIMELOCK,
|
||||
refund_address,
|
||||
);
|
||||
|
||||
info!("Commencing handshake");
|
||||
|
||||
swarm.send_message0(alice.clone(), state0.next_message(&mut rng));
|
||||
let state1 = match swarm.next().await {
|
||||
OutEvent::Message0(msg) => state0.receive(bitcoin_wallet.as_ref(), msg).await?,
|
||||
other => panic!("unexpected event: {:?}", other),
|
||||
};
|
||||
|
||||
swarm.send_message1(alice.clone(), state1.next_message());
|
||||
let state2 = match swarm.next().await {
|
||||
OutEvent::Message1(msg) => state1.receive(msg)?,
|
||||
other => panic!("unexpected event: {:?}", other),
|
||||
};
|
||||
|
||||
let swap_id = Uuid::new_v4();
|
||||
db.insert_latest_state(swap_id, state::Bob::Handshaken(state2.clone()).into())
|
||||
.await?;
|
||||
|
||||
swarm.send_message2(alice.clone(), state2.next_message());
|
||||
|
||||
info!("Handshake complete");
|
||||
|
||||
simple_swap(
|
||||
BobState::Negotiated(state2, alice_peer_id),
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
rng,
|
||||
)
|
||||
.await
|
||||
}
|
||||
BobState::Negotiated(state2, alice_peer_id) => {
|
||||
// Alice and Bob have exchanged info
|
||||
let state3 = state2.lock_btc(bitcoin_wallet.as_ref()).await?;
|
||||
simple_swap(
|
||||
BobState::BtcLocked(state3, alice_peer_id),
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
rng,
|
||||
)
|
||||
.await
|
||||
}
|
||||
// Bob has locked Btc
|
||||
// Watch for Alice to Lock Xmr or for t1 to elapse
|
||||
BobState::BtcLocked(state3, alice_peer_id) => {
|
||||
// todo: watch until t1, not indefinetely
|
||||
let state4 = match swarm.next().await {
|
||||
OutEvent::Message2(msg) => {
|
||||
state3
|
||||
.watch_for_lock_xmr(monero_wallet.as_ref(), msg)
|
||||
.await?
|
||||
}
|
||||
other => panic!("unexpected event: {:?}", other),
|
||||
};
|
||||
simple_swap(
|
||||
BobState::XmrLocked(state4, alice_peer_id),
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
rng,
|
||||
)
|
||||
.await
|
||||
}
|
||||
BobState::XmrLocked(state, alice_peer_id) => {
|
||||
// Alice has locked Xmr
|
||||
// Bob sends Alice his key
|
||||
// let cloned = state.clone();
|
||||
let tx_redeem_encsig = state.tx_redeem_encsig();
|
||||
// Do we have to wait for a response?
|
||||
// What if Alice fails to receive this? Should we always resend?
|
||||
// todo: If we cannot dial Alice we should go to EncSigSent. Maybe dialing
|
||||
// should happen in this arm?
|
||||
swarm.send_message3(alice_peer_id.clone(), tx_redeem_encsig);
|
||||
|
||||
simple_swap(
|
||||
BobState::EncSigSent(state, alice_peer_id),
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
rng,
|
||||
)
|
||||
.await
|
||||
}
|
||||
BobState::EncSigSent(state, ..) => {
|
||||
// Watch for redeem
|
||||
let redeem_watcher = state.watch_for_redeem_btc(bitcoin_wallet.as_ref());
|
||||
let t1_timeout = state.wait_for_t1(bitcoin_wallet.as_ref());
|
||||
|
||||
tokio::select! {
|
||||
val = redeem_watcher => {
|
||||
simple_swap(
|
||||
BobState::BtcRedeemed(val?),
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
rng,
|
||||
)
|
||||
.await
|
||||
}
|
||||
val = t1_timeout => {
|
||||
// Check whether TxCancel has been published.
|
||||
// We should not fail if the transaction is already on the blockchain
|
||||
if let Err(_e) = state.check_for_tx_cancel(bitcoin_wallet.as_ref()).await {
|
||||
state.submit_tx_cancel(bitcoin_wallet.as_ref()).await;
|
||||
}
|
||||
|
||||
simple_swap(
|
||||
BobState::Cancelled(state),
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
rng,
|
||||
)
|
||||
.await
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
BobState::BtcRedeemed(state) => {
|
||||
// Bob redeems XMR using revealed s_a
|
||||
state.claim_xmr(monero_wallet.as_ref()).await?;
|
||||
simple_swap(
|
||||
BobState::XmrRedeemed,
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
rng,
|
||||
)
|
||||
.await
|
||||
}
|
||||
BobState::Cancelled(_state) => Ok(BobState::BtcRefunded),
|
||||
BobState::BtcRefunded => Ok(BobState::BtcRefunded),
|
||||
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<BobState> {
|
||||
// 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") {
|
||||
// // Bob publishes TxCancel
|
||||
// abort(BobState::Cancelled, io).await
|
||||
// } else {
|
||||
// // >t2
|
||||
// // submit TxCancel
|
||||
// abort(BobState::Punished, io).await
|
||||
// }
|
||||
// }
|
||||
// BobState::Cancelled => {
|
||||
// // Bob has cancelled the swap
|
||||
// // If <t2 Bob refunds
|
||||
// if unimplemented!("<t2") {
|
||||
// // Submit TxRefund
|
||||
// abort(BobState::BtcRefunded, io).await
|
||||
// } else {
|
||||
// // Bob failed to refund in time and has been punished
|
||||
// abort(BobState::Punished, io).await
|
||||
// }
|
||||
// }
|
||||
// BobState::BtcRedeemed => {
|
||||
// // 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),
|
||||
// }
|
||||
// }
|
Loading…
Reference in new issue