2020-11-27 00:30:07 +00:00
|
|
|
//! Run an XMR/BTC swap in the role of Alice.
|
|
|
|
//! Alice holds XMR and wishes receive BTC.
|
|
|
|
use crate::{
|
|
|
|
alice::{
|
2020-12-10 02:19:18 +00:00
|
|
|
event_loop::EventLoopHandle,
|
2020-11-25 05:27:57 +00:00
|
|
|
execution::{
|
|
|
|
build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction,
|
|
|
|
extract_monero_private_key, lock_xmr, negotiate, publish_bitcoin_punish_transaction,
|
|
|
|
publish_bitcoin_redeem_transaction, publish_cancel_transaction,
|
|
|
|
wait_for_bitcoin_encrypted_signature, wait_for_bitcoin_refund, wait_for_locked_bitcoin,
|
|
|
|
},
|
2020-11-27 00:30:07 +00:00
|
|
|
},
|
2020-12-07 01:47:21 +00:00
|
|
|
bitcoin,
|
2020-11-25 05:27:57 +00:00
|
|
|
bitcoin::EncryptedSignature,
|
2020-11-27 00:30:07 +00:00
|
|
|
network::request_response::AliceToBob,
|
2020-12-07 01:47:21 +00:00
|
|
|
state,
|
|
|
|
state::{Alice, Swap},
|
|
|
|
storage::Database,
|
2020-11-27 00:30:07 +00:00
|
|
|
SwapAmounts,
|
|
|
|
};
|
2020-12-07 01:47:21 +00:00
|
|
|
use anyhow::{bail, Result};
|
2020-11-27 00:30:07 +00:00
|
|
|
use async_recursion::async_recursion;
|
|
|
|
use futures::{
|
|
|
|
future::{select, Either},
|
|
|
|
pin_mut,
|
|
|
|
};
|
|
|
|
use libp2p::request_response::ResponseChannel;
|
|
|
|
use rand::{CryptoRng, RngCore};
|
2020-12-08 06:02:58 +00:00
|
|
|
use std::{convert::TryFrom, fmt, sync::Arc};
|
2020-12-02 01:36:47 +00:00
|
|
|
use tracing::info;
|
2020-12-07 01:47:21 +00:00
|
|
|
use uuid::Uuid;
|
2020-11-27 00:30:07 +00:00
|
|
|
use xmr_btc::{
|
2020-12-10 00:18:45 +00:00
|
|
|
alice::{State0, State3},
|
2020-11-25 05:27:57 +00:00
|
|
|
bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction},
|
2020-12-01 23:00:00 +00:00
|
|
|
config::Config,
|
2020-11-27 00:30:07 +00:00
|
|
|
monero::CreateWalletForOutput,
|
2020-12-11 05:49:19 +00:00
|
|
|
Epoch,
|
2020-11-27 00:30:07 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
trait Rng: RngCore + CryptoRng + Send {}
|
|
|
|
|
|
|
|
impl<T> Rng for T where T: RngCore + CryptoRng + Send {}
|
|
|
|
|
|
|
|
// The same data structure is used for swap execution and recovery.
|
|
|
|
// This allows for a seamless transition from a failed swap to recovery.
|
2020-12-01 04:38:24 +00:00
|
|
|
#[allow(clippy::large_enum_variant)]
|
2020-11-27 00:30:07 +00:00
|
|
|
pub enum AliceState {
|
|
|
|
Started {
|
|
|
|
amounts: SwapAmounts,
|
2020-12-10 00:18:45 +00:00
|
|
|
state0: State0,
|
2020-11-27 00:30:07 +00:00
|
|
|
},
|
|
|
|
Negotiated {
|
2020-12-02 03:58:17 +00:00
|
|
|
channel: Option<ResponseChannel<AliceToBob>>,
|
2020-11-27 00:30:07 +00:00
|
|
|
amounts: SwapAmounts,
|
|
|
|
state3: State3,
|
|
|
|
},
|
|
|
|
BtcLocked {
|
2020-12-02 03:58:17 +00:00
|
|
|
channel: Option<ResponseChannel<AliceToBob>>,
|
2020-11-27 00:30:07 +00:00
|
|
|
amounts: SwapAmounts,
|
|
|
|
state3: State3,
|
|
|
|
},
|
|
|
|
XmrLocked {
|
|
|
|
state3: State3,
|
|
|
|
},
|
|
|
|
EncSignLearned {
|
|
|
|
state3: State3,
|
|
|
|
encrypted_signature: EncryptedSignature,
|
|
|
|
},
|
|
|
|
BtcRedeemed,
|
|
|
|
BtcCancelled {
|
|
|
|
state3: State3,
|
|
|
|
tx_cancel: TxCancel,
|
|
|
|
},
|
|
|
|
BtcRefunded {
|
2020-12-02 22:48:18 +00:00
|
|
|
spend_key: monero::PrivateKey,
|
2020-11-27 00:30:07 +00:00
|
|
|
state3: State3,
|
|
|
|
},
|
|
|
|
BtcPunishable {
|
|
|
|
tx_refund: TxRefund,
|
|
|
|
state3: State3,
|
|
|
|
},
|
|
|
|
XmrRefunded,
|
2020-12-11 05:49:19 +00:00
|
|
|
Cancelling {
|
2020-11-27 00:30:07 +00:00
|
|
|
state3: State3,
|
|
|
|
},
|
|
|
|
Punished,
|
|
|
|
SafelyAborted,
|
|
|
|
}
|
|
|
|
|
2020-12-02 01:36:47 +00:00
|
|
|
impl fmt::Display for AliceState {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
AliceState::Started { .. } => write!(f, "started"),
|
|
|
|
AliceState::Negotiated { .. } => write!(f, "negotiated"),
|
|
|
|
AliceState::BtcLocked { .. } => write!(f, "btc_locked"),
|
|
|
|
AliceState::XmrLocked { .. } => write!(f, "xmr_locked"),
|
2020-12-14 00:56:14 +00:00
|
|
|
AliceState::EncSignLearned { .. } => write!(f, "encsig_learned"),
|
2020-12-02 01:36:47 +00:00
|
|
|
AliceState::BtcRedeemed => write!(f, "btc_redeemed"),
|
|
|
|
AliceState::BtcCancelled { .. } => write!(f, "btc_cancelled"),
|
|
|
|
AliceState::BtcRefunded { .. } => write!(f, "btc_refunded"),
|
|
|
|
AliceState::Punished => write!(f, "punished"),
|
|
|
|
AliceState::SafelyAborted => write!(f, "safely_aborted"),
|
|
|
|
AliceState::BtcPunishable { .. } => write!(f, "btc_punishable"),
|
|
|
|
AliceState::XmrRefunded => write!(f, "xmr_refunded"),
|
2020-12-11 05:49:19 +00:00
|
|
|
AliceState::Cancelling { .. } => write!(f, "cancelling"),
|
2020-12-02 01:36:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-08 06:02:58 +00:00
|
|
|
impl From<&AliceState> for state::Alice {
|
|
|
|
fn from(alice_state: &AliceState) -> Self {
|
|
|
|
match alice_state {
|
|
|
|
AliceState::Started {
|
|
|
|
amounts,
|
2020-12-14 06:24:23 +00:00
|
|
|
state0:
|
|
|
|
State0 {
|
|
|
|
a,
|
|
|
|
s_a,
|
|
|
|
v_a,
|
|
|
|
refund_timelock,
|
|
|
|
punish_timelock,
|
|
|
|
redeem_address,
|
|
|
|
punish_address,
|
|
|
|
..
|
|
|
|
},
|
2020-12-08 06:02:58 +00:00
|
|
|
} => Alice::Started {
|
|
|
|
amounts: *amounts,
|
|
|
|
a: a.clone(),
|
|
|
|
s_a: *s_a,
|
|
|
|
v_a: *v_a,
|
2020-12-14 06:24:23 +00:00
|
|
|
refund_timelock: *refund_timelock,
|
|
|
|
punish_timelock: *punish_timelock,
|
|
|
|
redeem_address: redeem_address.clone(),
|
|
|
|
punish_address: punish_address.clone(),
|
2020-12-08 06:02:58 +00:00
|
|
|
},
|
|
|
|
AliceState::Negotiated { state3, .. } => Alice::Negotiated(state3.clone()),
|
|
|
|
AliceState::BtcLocked { state3, .. } => Alice::BtcLocked(state3.clone()),
|
|
|
|
AliceState::XmrLocked { state3 } => Alice::XmrLocked(state3.clone()),
|
2020-12-07 01:47:21 +00:00
|
|
|
AliceState::EncSignLearned {
|
|
|
|
state3,
|
|
|
|
encrypted_signature,
|
2020-12-08 06:02:58 +00:00
|
|
|
} => Alice::EncSignLearned {
|
2020-12-07 01:47:21 +00:00
|
|
|
state: state3.clone(),
|
|
|
|
encrypted_signature: encrypted_signature.clone(),
|
|
|
|
},
|
2020-12-08 06:02:58 +00:00
|
|
|
AliceState::BtcRedeemed => Alice::SwapComplete,
|
|
|
|
AliceState::BtcCancelled { state3, .. } => Alice::BtcCancelled(state3.clone()),
|
|
|
|
AliceState::BtcRefunded { .. } => Alice::SwapComplete,
|
|
|
|
AliceState::BtcPunishable { state3, .. } => Alice::BtcPunishable(state3.clone()),
|
|
|
|
AliceState::XmrRefunded => Alice::SwapComplete,
|
2020-12-07 01:47:21 +00:00
|
|
|
// TODO(Franck): it may be more efficient to store the fact that we already want to
|
|
|
|
// abort
|
2020-12-14 03:43:05 +00:00
|
|
|
AliceState::Cancelling { state3 } => Alice::Cancelling(state3.clone()),
|
2020-12-08 06:02:58 +00:00
|
|
|
AliceState::Punished => Alice::SwapComplete,
|
|
|
|
AliceState::SafelyAborted => Alice::SwapComplete,
|
|
|
|
}
|
2020-12-07 01:47:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<state::Swap> for AliceState {
|
|
|
|
type Error = anyhow::Error;
|
|
|
|
|
|
|
|
fn try_from(db_state: Swap) -> Result<Self, Self::Error> {
|
|
|
|
use AliceState::*;
|
|
|
|
if let Swap::Alice(state) = db_state {
|
|
|
|
let alice_state = match state {
|
2020-12-14 06:24:23 +00:00
|
|
|
Alice::Started {
|
|
|
|
amounts,
|
|
|
|
a,
|
|
|
|
s_a,
|
|
|
|
v_a,
|
|
|
|
refund_timelock,
|
|
|
|
punish_timelock,
|
|
|
|
redeem_address,
|
|
|
|
punish_address,
|
|
|
|
} => Started {
|
|
|
|
amounts,
|
|
|
|
state0: State0 {
|
|
|
|
a,
|
|
|
|
s_a,
|
|
|
|
v_a,
|
|
|
|
btc: amounts.btc,
|
|
|
|
xmr: amounts.xmr,
|
|
|
|
refund_timelock,
|
|
|
|
punish_timelock,
|
|
|
|
redeem_address,
|
|
|
|
punish_address,
|
|
|
|
},
|
|
|
|
},
|
2020-12-07 01:47:21 +00:00
|
|
|
Alice::Negotiated(state3) => Negotiated {
|
|
|
|
channel: None,
|
|
|
|
amounts: SwapAmounts {
|
|
|
|
btc: state3.btc,
|
|
|
|
xmr: state3.xmr,
|
|
|
|
},
|
|
|
|
state3,
|
|
|
|
},
|
|
|
|
Alice::BtcLocked(state3) => BtcLocked {
|
|
|
|
channel: None,
|
|
|
|
amounts: SwapAmounts {
|
|
|
|
btc: state3.btc,
|
|
|
|
xmr: state3.xmr,
|
|
|
|
},
|
|
|
|
state3,
|
|
|
|
},
|
|
|
|
Alice::XmrLocked(state3) => XmrLocked { state3 },
|
|
|
|
Alice::BtcRedeemable { .. } => bail!("BtcRedeemable state is unexpected"),
|
|
|
|
Alice::EncSignLearned {
|
|
|
|
state,
|
|
|
|
encrypted_signature,
|
|
|
|
} => EncSignLearned {
|
|
|
|
state3: state,
|
|
|
|
encrypted_signature,
|
|
|
|
},
|
2020-12-14 03:43:05 +00:00
|
|
|
Alice::Cancelling(state3) => AliceState::Cancelling { state3 },
|
2020-12-07 01:47:21 +00:00
|
|
|
Alice::BtcCancelled(state) => {
|
|
|
|
let tx_cancel = bitcoin::TxCancel::new(
|
|
|
|
&state.tx_lock,
|
|
|
|
state.refund_timelock,
|
|
|
|
state.a.public(),
|
|
|
|
state.B,
|
|
|
|
);
|
|
|
|
|
|
|
|
BtcCancelled {
|
|
|
|
state3: state,
|
|
|
|
tx_cancel,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Alice::BtcPunishable(state) => {
|
|
|
|
let tx_cancel = bitcoin::TxCancel::new(
|
|
|
|
&state.tx_lock,
|
|
|
|
state.refund_timelock,
|
|
|
|
state.a.public(),
|
|
|
|
state.B,
|
|
|
|
);
|
|
|
|
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state.refund_address);
|
|
|
|
BtcPunishable {
|
|
|
|
tx_refund,
|
|
|
|
state3: state,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Alice::BtcRefunded {
|
|
|
|
state, spend_key, ..
|
|
|
|
} => BtcRefunded {
|
|
|
|
spend_key,
|
|
|
|
state3: state,
|
|
|
|
},
|
|
|
|
Alice::SwapComplete => {
|
|
|
|
// TODO(Franck): Better fine grain
|
|
|
|
AliceState::SafelyAborted
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Ok(alice_state)
|
|
|
|
} else {
|
|
|
|
bail!("Alice swap state expected.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-02 01:36:47 +00:00
|
|
|
pub async fn swap(
|
2020-12-02 01:34:47 +00:00
|
|
|
state: AliceState,
|
2020-12-10 02:55:29 +00:00
|
|
|
event_loop_handle: EventLoopHandle,
|
2020-12-02 01:34:47 +00:00
|
|
|
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
|
|
|
monero_wallet: Arc<crate::monero::Wallet>,
|
2020-12-02 01:36:47 +00:00
|
|
|
config: Config,
|
2020-12-07 01:47:21 +00:00
|
|
|
swap_id: Uuid,
|
|
|
|
db: Database,
|
2020-12-10 02:19:18 +00:00
|
|
|
) -> Result<(AliceState, EventLoopHandle)> {
|
2020-12-02 01:36:47 +00:00
|
|
|
run_until(
|
|
|
|
state,
|
|
|
|
is_complete,
|
2020-12-10 02:55:29 +00:00
|
|
|
event_loop_handle,
|
2020-12-02 01:36:47 +00:00
|
|
|
bitcoin_wallet,
|
|
|
|
monero_wallet,
|
|
|
|
config,
|
2020-12-07 01:47:21 +00:00
|
|
|
swap_id,
|
|
|
|
db,
|
2020-12-02 01:36:47 +00:00
|
|
|
)
|
|
|
|
.await
|
2020-12-02 01:34:47 +00:00
|
|
|
}
|
|
|
|
|
2020-12-14 06:24:23 +00:00
|
|
|
pub async fn recover(
|
|
|
|
event_loop_handle: EventLoopHandle,
|
|
|
|
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
|
|
|
monero_wallet: Arc<crate::monero::Wallet>,
|
|
|
|
config: Config,
|
|
|
|
swap_id: Uuid,
|
|
|
|
db: Database,
|
|
|
|
) -> Result<AliceState> {
|
|
|
|
let db_swap = db.get_state(swap_id)?;
|
|
|
|
let start_state = AliceState::try_from(db_swap)?;
|
|
|
|
let (state, _) = swap(
|
|
|
|
start_state,
|
|
|
|
event_loop_handle,
|
|
|
|
bitcoin_wallet,
|
|
|
|
monero_wallet,
|
|
|
|
config,
|
|
|
|
swap_id,
|
|
|
|
db,
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
Ok(state)
|
|
|
|
}
|
|
|
|
|
2020-12-02 01:34:47 +00:00
|
|
|
pub fn is_complete(state: &AliceState) -> bool {
|
2020-12-02 01:36:47 +00:00
|
|
|
matches!(
|
|
|
|
state,
|
|
|
|
AliceState::XmrRefunded
|
|
|
|
| AliceState::BtcRedeemed
|
|
|
|
| AliceState::Punished
|
|
|
|
| AliceState::SafelyAborted
|
|
|
|
)
|
2020-12-02 01:34:47 +00:00
|
|
|
}
|
|
|
|
|
2020-12-02 01:36:47 +00:00
|
|
|
pub fn is_xmr_locked(state: &AliceState) -> bool {
|
|
|
|
matches!(
|
|
|
|
state,
|
|
|
|
AliceState::XmrLocked{..}
|
|
|
|
)
|
|
|
|
}
|
2020-12-02 01:34:47 +00:00
|
|
|
|
2020-12-10 03:59:09 +00:00
|
|
|
pub fn is_encsig_learned(state: &AliceState) -> bool {
|
|
|
|
matches!(
|
|
|
|
state,
|
|
|
|
AliceState::EncSignLearned{..}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-11-27 00:30:07 +00:00
|
|
|
// State machine driver for swap execution
|
|
|
|
#[async_recursion]
|
2020-12-07 01:47:21 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2020-12-02 01:34:47 +00:00
|
|
|
pub async fn run_until(
|
2020-11-27 00:30:07 +00:00
|
|
|
state: AliceState,
|
2020-12-02 01:36:47 +00:00
|
|
|
is_target_state: fn(&AliceState) -> bool,
|
2020-12-10 02:55:29 +00:00
|
|
|
mut event_loop_handle: EventLoopHandle,
|
2020-11-27 00:30:07 +00:00
|
|
|
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
|
|
|
monero_wallet: Arc<crate::monero::Wallet>,
|
2020-12-01 23:00:00 +00:00
|
|
|
config: Config,
|
2020-12-07 01:47:21 +00:00
|
|
|
swap_id: Uuid,
|
|
|
|
db: Database,
|
2020-12-14 06:24:23 +00:00
|
|
|
// TODO: Remove EventLoopHandle!
|
2020-12-10 02:19:18 +00:00
|
|
|
) -> Result<(AliceState, EventLoopHandle)> {
|
2020-12-07 02:55:13 +00:00
|
|
|
info!("Current state:{}", state);
|
2020-12-02 01:36:47 +00:00
|
|
|
if is_target_state(&state) {
|
2020-12-10 02:55:29 +00:00
|
|
|
Ok((state, event_loop_handle))
|
2020-12-02 01:34:47 +00:00
|
|
|
} else {
|
|
|
|
match state {
|
2020-12-10 00:18:45 +00:00
|
|
|
AliceState::Started { amounts, state0 } => {
|
2020-12-10 02:55:29 +00:00
|
|
|
let (channel, state3) =
|
|
|
|
negotiate(state0, amounts, &mut event_loop_handle, config).await?;
|
2020-11-27 00:30:07 +00:00
|
|
|
|
2020-12-07 01:47:21 +00:00
|
|
|
let state = AliceState::Negotiated {
|
|
|
|
channel: Some(channel),
|
|
|
|
amounts,
|
|
|
|
state3,
|
|
|
|
};
|
|
|
|
|
2020-12-08 06:02:58 +00:00
|
|
|
let db_state = (&state).into();
|
|
|
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
|
|
|
.await?;
|
2020-12-02 01:36:47 +00:00
|
|
|
run_until(
|
2020-12-07 01:47:21 +00:00
|
|
|
state,
|
2020-12-02 01:36:47 +00:00
|
|
|
is_target_state,
|
2020-12-10 02:55:29 +00:00
|
|
|
event_loop_handle,
|
2020-12-02 01:36:47 +00:00
|
|
|
bitcoin_wallet,
|
|
|
|
monero_wallet,
|
|
|
|
config,
|
2020-12-07 01:47:21 +00:00
|
|
|
swap_id,
|
|
|
|
db,
|
2020-12-02 01:36:47 +00:00
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
AliceState::Negotiated {
|
|
|
|
state3,
|
2020-11-27 00:30:07 +00:00
|
|
|
channel,
|
|
|
|
amounts,
|
2020-12-02 01:36:47 +00:00
|
|
|
} => {
|
2020-12-07 01:47:21 +00:00
|
|
|
let state = match channel {
|
2020-12-02 03:58:17 +00:00
|
|
|
Some(channel) => {
|
|
|
|
let _ = wait_for_locked_bitcoin(
|
|
|
|
state3.tx_lock.txid(),
|
|
|
|
bitcoin_wallet.clone(),
|
|
|
|
config,
|
|
|
|
)
|
2020-12-02 01:36:47 +00:00
|
|
|
.await?;
|
2020-11-27 00:30:07 +00:00
|
|
|
|
2020-12-07 01:47:21 +00:00
|
|
|
AliceState::BtcLocked {
|
|
|
|
channel: Some(channel),
|
|
|
|
amounts,
|
|
|
|
state3,
|
|
|
|
}
|
2020-12-02 03:58:17 +00:00
|
|
|
}
|
|
|
|
None => {
|
|
|
|
tracing::info!("Cannot resume swap from negotiated state, aborting");
|
|
|
|
|
|
|
|
// Alice did not lock Xmr yet
|
2020-12-07 01:47:21 +00:00
|
|
|
AliceState::SafelyAborted
|
2020-12-02 03:58:17 +00:00
|
|
|
}
|
2020-12-07 01:47:21 +00:00
|
|
|
};
|
|
|
|
|
2020-12-08 06:02:58 +00:00
|
|
|
let db_state = (&state).into();
|
|
|
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
|
|
|
.await?;
|
2020-12-07 01:47:21 +00:00
|
|
|
run_until(
|
|
|
|
state,
|
|
|
|
is_target_state,
|
|
|
|
event_loop_handle,
|
|
|
|
bitcoin_wallet,
|
|
|
|
monero_wallet,
|
|
|
|
config,
|
|
|
|
swap_id,
|
|
|
|
db,
|
|
|
|
)
|
|
|
|
.await
|
2020-12-02 01:36:47 +00:00
|
|
|
}
|
|
|
|
AliceState::BtcLocked {
|
|
|
|
channel,
|
|
|
|
amounts,
|
|
|
|
state3,
|
2020-12-07 01:47:21 +00:00
|
|
|
} => {
|
|
|
|
let state = match channel {
|
|
|
|
Some(channel) => {
|
|
|
|
lock_xmr(
|
|
|
|
channel,
|
|
|
|
amounts,
|
|
|
|
state3.clone(),
|
|
|
|
&mut event_loop_handle,
|
|
|
|
monero_wallet.clone(),
|
|
|
|
)
|
|
|
|
.await?;
|
2020-12-02 01:36:47 +00:00
|
|
|
|
2020-12-07 01:47:21 +00:00
|
|
|
AliceState::XmrLocked { state3 }
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
tracing::info!("Cannot resume swap from BTC locked state, aborting");
|
|
|
|
|
|
|
|
// Alice did not lock Xmr yet
|
|
|
|
AliceState::SafelyAborted
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-12-08 06:02:58 +00:00
|
|
|
let db_state = (&state).into();
|
|
|
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
|
|
|
.await?;
|
2020-12-07 01:47:21 +00:00
|
|
|
run_until(
|
|
|
|
state,
|
|
|
|
is_target_state,
|
|
|
|
event_loop_handle,
|
|
|
|
bitcoin_wallet,
|
|
|
|
monero_wallet,
|
|
|
|
config,
|
|
|
|
swap_id,
|
|
|
|
db,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
2020-12-02 01:36:47 +00:00
|
|
|
AliceState::XmrLocked { state3 } => {
|
2020-12-14 00:56:14 +00:00
|
|
|
// todo: match statement and wait for t1 can probably be expressed more cleanly
|
2020-12-07 01:47:21 +00:00
|
|
|
let state = match state3.current_epoch(bitcoin_wallet.as_ref()).await? {
|
2020-12-11 05:49:19 +00:00
|
|
|
Epoch::T0 => {
|
|
|
|
let wait_for_enc_sig = wait_for_bitcoin_encrypted_signature(
|
|
|
|
&mut event_loop_handle,
|
|
|
|
config.monero_max_finality_time,
|
|
|
|
);
|
2020-12-07 01:47:21 +00:00
|
|
|
let state3_clone = state3.clone();
|
|
|
|
let t1_timeout = state3_clone.wait_for_t1(bitcoin_wallet.as_ref());
|
|
|
|
|
|
|
|
pin_mut!(wait_for_enc_sig);
|
|
|
|
pin_mut!(t1_timeout);
|
|
|
|
|
|
|
|
match select(t1_timeout, wait_for_enc_sig).await {
|
|
|
|
Either::Left(_) => AliceState::Cancelling { state3 },
|
|
|
|
Either::Right((enc_sig, _)) => AliceState::EncSignLearned {
|
|
|
|
state3,
|
|
|
|
encrypted_signature: enc_sig?,
|
|
|
|
},
|
2020-12-11 05:49:19 +00:00
|
|
|
}
|
2020-12-02 01:36:47 +00:00
|
|
|
}
|
2020-12-07 01:47:21 +00:00
|
|
|
_ => AliceState::Cancelling { state3 },
|
|
|
|
};
|
2020-12-11 05:49:19 +00:00
|
|
|
|
2020-12-08 06:02:58 +00:00
|
|
|
let db_state = (&state).into();
|
|
|
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
|
|
|
.await?;
|
2020-12-07 01:47:21 +00:00
|
|
|
run_until(
|
|
|
|
state,
|
|
|
|
is_target_state,
|
|
|
|
event_loop_handle,
|
|
|
|
bitcoin_wallet.clone(),
|
|
|
|
monero_wallet,
|
|
|
|
config,
|
|
|
|
swap_id,
|
|
|
|
db,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
2020-12-02 01:36:47 +00:00
|
|
|
AliceState::EncSignLearned {
|
|
|
|
state3,
|
2020-11-25 05:27:57 +00:00
|
|
|
encrypted_signature,
|
2020-12-02 01:36:47 +00:00
|
|
|
} => {
|
|
|
|
let signed_tx_redeem = match build_bitcoin_redeem_transaction(
|
|
|
|
encrypted_signature,
|
|
|
|
&state3.tx_lock,
|
|
|
|
state3.a.clone(),
|
|
|
|
state3.s_a,
|
|
|
|
state3.B,
|
|
|
|
&state3.redeem_address,
|
|
|
|
) {
|
|
|
|
Ok(tx) => tx,
|
|
|
|
Err(_) => {
|
2020-12-07 01:47:21 +00:00
|
|
|
let state = AliceState::Cancelling { state3 };
|
2020-12-08 06:02:58 +00:00
|
|
|
let db_state = (&state).into();
|
|
|
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
|
|
|
.await?;
|
2020-12-02 01:36:47 +00:00
|
|
|
return run_until(
|
2020-12-07 01:47:21 +00:00
|
|
|
state,
|
2020-12-02 01:36:47 +00:00
|
|
|
is_target_state,
|
2020-12-10 02:55:29 +00:00
|
|
|
event_loop_handle,
|
2020-12-02 01:36:47 +00:00
|
|
|
bitcoin_wallet,
|
|
|
|
monero_wallet,
|
|
|
|
config,
|
2020-12-07 01:47:21 +00:00
|
|
|
swap_id,
|
|
|
|
db,
|
2020-12-02 01:36:47 +00:00
|
|
|
)
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
};
|
2020-11-27 00:30:07 +00:00
|
|
|
|
2020-12-02 01:36:47 +00:00
|
|
|
// TODO(Franck): Error handling is delicate here.
|
|
|
|
// If Bob sees this transaction he can redeem Monero
|
|
|
|
// e.g. If the Bitcoin node is down then the user needs to take action.
|
|
|
|
publish_bitcoin_redeem_transaction(
|
|
|
|
signed_tx_redeem,
|
|
|
|
bitcoin_wallet.clone(),
|
|
|
|
config,
|
|
|
|
)
|
2020-12-01 23:00:00 +00:00
|
|
|
.await?;
|
2020-11-27 00:30:07 +00:00
|
|
|
|
2020-12-07 01:47:21 +00:00
|
|
|
let state = AliceState::BtcRedeemed;
|
2020-12-08 06:02:58 +00:00
|
|
|
let db_state = (&state).into();
|
|
|
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
|
|
|
.await?;
|
2020-12-02 01:36:47 +00:00
|
|
|
run_until(
|
2020-12-07 01:47:21 +00:00
|
|
|
state,
|
2020-12-02 01:36:47 +00:00
|
|
|
is_target_state,
|
2020-12-10 02:55:29 +00:00
|
|
|
event_loop_handle,
|
2020-12-02 01:36:47 +00:00
|
|
|
bitcoin_wallet,
|
|
|
|
monero_wallet,
|
|
|
|
config,
|
2020-12-07 01:47:21 +00:00
|
|
|
swap_id,
|
|
|
|
db,
|
2020-12-02 01:36:47 +00:00
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
2020-12-11 05:49:19 +00:00
|
|
|
AliceState::Cancelling { state3 } => {
|
2020-12-02 01:36:47 +00:00
|
|
|
let tx_cancel = publish_cancel_transaction(
|
|
|
|
state3.tx_lock.clone(),
|
|
|
|
state3.a.clone(),
|
|
|
|
state3.B,
|
|
|
|
state3.refund_timelock,
|
|
|
|
state3.tx_cancel_sig_bob.clone(),
|
|
|
|
bitcoin_wallet.clone(),
|
|
|
|
)
|
|
|
|
.await?;
|
2020-11-27 00:30:07 +00:00
|
|
|
|
2020-12-07 01:47:21 +00:00
|
|
|
let state = AliceState::BtcCancelled { state3, tx_cancel };
|
2020-12-08 06:02:58 +00:00
|
|
|
let db_state = (&state).into();
|
|
|
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
|
|
|
.await?;
|
2020-12-02 01:36:47 +00:00
|
|
|
run_until(
|
2020-12-07 01:47:21 +00:00
|
|
|
state,
|
2020-12-02 01:36:47 +00:00
|
|
|
is_target_state,
|
2020-12-10 02:55:29 +00:00
|
|
|
event_loop_handle,
|
2020-12-02 01:36:47 +00:00
|
|
|
bitcoin_wallet,
|
|
|
|
monero_wallet,
|
|
|
|
config,
|
2020-12-07 01:47:21 +00:00
|
|
|
swap_id,
|
|
|
|
db,
|
2020-12-02 01:36:47 +00:00
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
AliceState::BtcCancelled { state3, tx_cancel } => {
|
|
|
|
let tx_cancel_height = bitcoin_wallet
|
|
|
|
.transaction_block_height(tx_cancel.txid())
|
|
|
|
.await;
|
2020-11-27 00:30:07 +00:00
|
|
|
|
2020-12-02 01:36:47 +00:00
|
|
|
let (tx_refund, published_refund_tx) = wait_for_bitcoin_refund(
|
|
|
|
&tx_cancel,
|
|
|
|
tx_cancel_height,
|
|
|
|
state3.punish_timelock,
|
|
|
|
&state3.refund_address,
|
|
|
|
bitcoin_wallet.clone(),
|
|
|
|
)
|
|
|
|
.await?;
|
2020-11-27 00:30:07 +00:00
|
|
|
|
2020-12-02 01:36:47 +00:00
|
|
|
// TODO(Franck): Review error handling
|
|
|
|
match published_refund_tx {
|
|
|
|
None => {
|
2020-12-07 01:47:21 +00:00
|
|
|
let state = AliceState::BtcPunishable { tx_refund, state3 };
|
2020-12-08 06:02:58 +00:00
|
|
|
let db_state = (&state).into();
|
|
|
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
|
|
|
.await?;
|
2020-12-07 01:47:21 +00:00
|
|
|
swap(
|
|
|
|
state,
|
2020-12-10 02:55:29 +00:00
|
|
|
event_loop_handle,
|
2020-12-02 01:36:47 +00:00
|
|
|
bitcoin_wallet.clone(),
|
|
|
|
monero_wallet,
|
|
|
|
config,
|
2020-12-07 01:47:21 +00:00
|
|
|
swap_id,
|
|
|
|
db,
|
2020-12-02 01:36:47 +00:00
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
Some(published_refund_tx) => {
|
2020-12-02 22:48:18 +00:00
|
|
|
let spend_key = extract_monero_private_key(
|
|
|
|
published_refund_tx,
|
|
|
|
tx_refund,
|
|
|
|
state3.s_a,
|
|
|
|
state3.a.clone(),
|
|
|
|
state3.S_b_bitcoin,
|
|
|
|
)?;
|
|
|
|
|
2020-12-07 01:47:21 +00:00
|
|
|
let state = AliceState::BtcRefunded { spend_key, state3 };
|
2020-12-08 06:02:58 +00:00
|
|
|
let db_state = (&state).into();
|
|
|
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
|
|
|
.await?;
|
2020-12-02 01:36:47 +00:00
|
|
|
run_until(
|
2020-12-07 01:47:21 +00:00
|
|
|
state,
|
2020-12-02 01:36:47 +00:00
|
|
|
is_target_state,
|
2020-12-10 02:55:29 +00:00
|
|
|
event_loop_handle,
|
2020-12-02 01:36:47 +00:00
|
|
|
bitcoin_wallet.clone(),
|
|
|
|
monero_wallet,
|
|
|
|
config,
|
2020-12-07 01:47:21 +00:00
|
|
|
swap_id,
|
|
|
|
db,
|
2020-12-02 01:36:47 +00:00
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
2020-11-27 00:30:07 +00:00
|
|
|
}
|
|
|
|
}
|
2020-12-02 22:48:18 +00:00
|
|
|
AliceState::BtcRefunded { spend_key, state3 } => {
|
2020-12-02 01:36:47 +00:00
|
|
|
let view_key = state3.v;
|
2020-11-27 00:30:07 +00:00
|
|
|
|
2020-12-02 01:36:47 +00:00
|
|
|
monero_wallet
|
|
|
|
.create_and_load_wallet_for_output(spend_key, view_key)
|
|
|
|
.await?;
|
2020-11-27 00:30:07 +00:00
|
|
|
|
2020-12-07 01:47:21 +00:00
|
|
|
let state = AliceState::XmrRefunded;
|
2020-12-08 06:02:58 +00:00
|
|
|
let db_state = (&state).into();
|
|
|
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
|
|
|
.await?;
|
2020-12-07 01:47:21 +00:00
|
|
|
Ok((state, event_loop_handle))
|
2020-12-02 01:36:47 +00:00
|
|
|
}
|
|
|
|
AliceState::BtcPunishable { tx_refund, state3 } => {
|
|
|
|
let signed_tx_punish = build_bitcoin_punish_transaction(
|
|
|
|
&state3.tx_lock,
|
|
|
|
state3.refund_timelock,
|
|
|
|
&state3.punish_address,
|
|
|
|
state3.punish_timelock,
|
|
|
|
state3.tx_punish_sig_bob.clone(),
|
|
|
|
state3.a.clone(),
|
|
|
|
state3.B,
|
|
|
|
)?;
|
2020-11-27 00:30:07 +00:00
|
|
|
|
2020-12-02 01:36:47 +00:00
|
|
|
let punish_tx_finalised = publish_bitcoin_punish_transaction(
|
|
|
|
signed_tx_punish,
|
|
|
|
bitcoin_wallet.clone(),
|
|
|
|
config,
|
|
|
|
);
|
2020-11-27 00:30:07 +00:00
|
|
|
|
2020-12-02 01:36:47 +00:00
|
|
|
let refund_tx_seen = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid());
|
2020-11-27 00:30:07 +00:00
|
|
|
|
2020-12-02 01:36:47 +00:00
|
|
|
pin_mut!(punish_tx_finalised);
|
|
|
|
pin_mut!(refund_tx_seen);
|
2020-11-27 00:30:07 +00:00
|
|
|
|
2020-12-02 01:36:47 +00:00
|
|
|
match select(punish_tx_finalised, refund_tx_seen).await {
|
|
|
|
Either::Left(_) => {
|
2020-12-07 01:47:21 +00:00
|
|
|
let state = AliceState::Punished;
|
2020-12-08 06:02:58 +00:00
|
|
|
let db_state = (&state).into();
|
|
|
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
|
|
|
.await?;
|
2020-12-02 01:36:47 +00:00
|
|
|
run_until(
|
2020-12-07 01:47:21 +00:00
|
|
|
state,
|
2020-12-02 01:36:47 +00:00
|
|
|
is_target_state,
|
2020-12-10 02:55:29 +00:00
|
|
|
event_loop_handle,
|
2020-12-02 01:36:47 +00:00
|
|
|
bitcoin_wallet.clone(),
|
|
|
|
monero_wallet,
|
|
|
|
config,
|
2020-12-07 01:47:21 +00:00
|
|
|
swap_id,
|
|
|
|
db,
|
2020-12-02 01:36:47 +00:00
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
Either::Right((published_refund_tx, _)) => {
|
2020-12-02 22:48:18 +00:00
|
|
|
let spend_key = extract_monero_private_key(
|
|
|
|
published_refund_tx,
|
|
|
|
tx_refund,
|
|
|
|
state3.s_a,
|
|
|
|
state3.a.clone(),
|
|
|
|
state3.S_b_bitcoin,
|
|
|
|
)?;
|
2020-12-07 01:47:21 +00:00
|
|
|
let state = AliceState::BtcRefunded { spend_key, state3 };
|
2020-12-08 06:02:58 +00:00
|
|
|
let db_state = (&state).into();
|
|
|
|
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
|
|
|
.await?;
|
2020-12-02 01:36:47 +00:00
|
|
|
run_until(
|
2020-12-07 01:47:21 +00:00
|
|
|
state,
|
2020-12-02 01:36:47 +00:00
|
|
|
is_target_state,
|
2020-12-10 02:55:29 +00:00
|
|
|
event_loop_handle,
|
2020-12-02 01:36:47 +00:00
|
|
|
bitcoin_wallet.clone(),
|
|
|
|
monero_wallet,
|
|
|
|
config,
|
2020-12-07 01:47:21 +00:00
|
|
|
swap_id,
|
|
|
|
db,
|
2020-12-02 01:36:47 +00:00
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
2020-11-27 00:30:07 +00:00
|
|
|
}
|
|
|
|
}
|
2020-12-10 02:55:29 +00:00
|
|
|
AliceState::XmrRefunded => Ok((AliceState::XmrRefunded, event_loop_handle)),
|
|
|
|
AliceState::BtcRedeemed => Ok((AliceState::BtcRedeemed, event_loop_handle)),
|
|
|
|
AliceState::Punished => Ok((AliceState::Punished, event_loop_handle)),
|
|
|
|
AliceState::SafelyAborted => Ok((AliceState::SafelyAborted, event_loop_handle)),
|
2020-11-27 00:30:07 +00:00
|
|
|
}
|
2020-12-02 01:34:47 +00:00
|
|
|
}
|
2020-11-27 00:30:07 +00:00
|
|
|
}
|