Merge pull request #54 from comit-network/bob-unhappy-paths

Punish Test
This commit is contained in:
rishflab 2020-12-09 15:51:16 +11:00 committed by GitHub
commit f88ed9183b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 882 additions and 683 deletions

View file

@ -5,7 +5,6 @@ use crate::{
SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
};
use anyhow::{bail, Context, Result};
use conquer_once::Lazy;
use ecdsa_fun::{adaptor::Adaptor, nonce::Deterministic};
use futures::{
future::{select, Either},
@ -29,13 +28,6 @@ use xmr_btc::{
monero::Transfer,
};
// The maximum we assume we need to wait from the moment the monero transaction
// is mined to the moment it reaches finality. We set 15 confirmations for now
// (based on Kraken). 1.5 multiplier in case the blockchain is slower than
// usually. Average of 2 minutes block time
static MONERO_MAX_FINALITY_TIME: Lazy<Duration> =
Lazy::new(|| Duration::from_secs_f64(15f64 * 1.5 * 2f64 * 60f64));
pub async fn negotiate(
amounts: SwapAmounts,
a: bitcoin::SecretKey,
@ -180,8 +172,11 @@ where
Ok(())
}
pub async fn wait_for_bitcoin_encrypted_signature(swarm: &mut Swarm) -> Result<EncryptedSignature> {
let event = timeout(*MONERO_MAX_FINALITY_TIME, swarm.next())
pub async fn wait_for_bitcoin_encrypted_signature(
swarm: &mut Swarm,
timeout_duration: Duration,
) -> Result<EncryptedSignature> {
let event = timeout(timeout_duration, swarm.next())
.await
.context("Failed to receive Bitcoin encrypted signature from Bob")?;

View file

@ -24,7 +24,8 @@ use futures::{
};
use libp2p::request_response::ResponseChannel;
use rand::{CryptoRng, RngCore};
use std::sync::Arc;
use std::{fmt, sync::Arc};
use tracing::info;
use xmr_btc::{
alice::State3,
bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction},
@ -86,293 +87,376 @@ pub enum AliceState {
SafelyAborted,
}
// State machine driver for swap execution
#[async_recursion]
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"),
AliceState::EncSignLearned { .. } => write!(f, "encsig_sent"),
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"),
AliceState::WaitingToCancel { .. } => write!(f, "waiting_to_cancel"),
}
}
}
pub async fn swap(
state: AliceState,
swarm: Swarm,
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
monero_wallet: Arc<crate::monero::Wallet>,
config: Config,
) -> Result<(AliceState, Swarm)> {
run_until(
state,
is_complete,
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
pub fn is_complete(state: &AliceState) -> bool {
matches!(
state,
AliceState::XmrRefunded
| AliceState::BtcRedeemed
| AliceState::Punished
| AliceState::SafelyAborted
)
}
pub fn is_xmr_locked(state: &AliceState) -> bool {
matches!(
state,
AliceState::XmrLocked{..}
)
}
// State machine driver for swap execution
#[async_recursion]
pub async fn run_until(
state: AliceState,
is_target_state: fn(&AliceState) -> bool,
mut swarm: Swarm,
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
monero_wallet: Arc<crate::monero::Wallet>,
config: Config,
) -> Result<AliceState> {
match state {
AliceState::Started {
amounts,
a,
s_a,
v_a,
} => {
let (channel, state3) = negotiate(
) -> Result<(AliceState, Swarm)> {
info!("Current state:{}", state);
if is_target_state(&state) {
Ok((state, swarm))
} else {
match state {
AliceState::Started {
amounts,
a,
s_a,
v_a,
&mut swarm,
bitcoin_wallet.clone(),
config,
)
.await?;
swap(
AliceState::Negotiated {
channel,
} => {
let (channel, state3) = negotiate(
amounts,
state3,
},
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
AliceState::Negotiated {
state3,
channel,
amounts,
} => {
let _ = wait_for_locked_bitcoin(state3.tx_lock.txid(), bitcoin_wallet.clone(), config)
a,
s_a,
v_a,
&mut swarm,
bitcoin_wallet.clone(),
config,
)
.await?;
swap(
AliceState::BtcLocked {
channel,
amounts,
state3,
},
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
AliceState::BtcLocked {
channel,
amounts,
state3,
} => {
lock_xmr(
run_until(
AliceState::Negotiated {
channel,
amounts,
state3,
},
is_target_state,
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
AliceState::Negotiated {
state3,
channel,
amounts,
state3.clone(),
&mut swarm,
monero_wallet.clone(),
)
.await?;
} => {
let _ =
wait_for_locked_bitcoin(state3.tx_lock.txid(), bitcoin_wallet.clone(), config)
.await?;
swap(
AliceState::XmrLocked { state3 },
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
AliceState::XmrLocked { state3 } => {
// Our Monero is locked, we need to go through the cancellation process if this
// step fails
match wait_for_bitcoin_encrypted_signature(&mut swarm).await {
Ok(encrypted_signature) => {
swap(
AliceState::EncSignLearned {
state3,
encrypted_signature,
},
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
Err(_) => {
swap(
AliceState::WaitingToCancel { state3 },
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
run_until(
AliceState::BtcLocked {
channel,
amounts,
state3,
},
is_target_state,
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
AliceState::BtcLocked {
channel,
amounts,
state3,
} => {
lock_xmr(
channel,
amounts,
state3.clone(),
&mut swarm,
monero_wallet.clone(),
)
.await?;
run_until(
AliceState::XmrLocked { state3 },
is_target_state,
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
AliceState::XmrLocked { state3 } => {
// Our Monero is locked, we need to go through the cancellation process if this
// step fails
match wait_for_bitcoin_encrypted_signature(
&mut swarm,
config.monero_max_finality_time,
)
.await
{
Ok(encrypted_signature) => {
run_until(
AliceState::EncSignLearned {
state3,
encrypted_signature,
},
is_target_state,
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
Err(_) => {
run_until(
AliceState::WaitingToCancel { state3 },
is_target_state,
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
}
}
}
AliceState::EncSignLearned {
state3,
encrypted_signature,
} => {
let signed_tx_redeem = match build_bitcoin_redeem_transaction(
AliceState::EncSignLearned {
state3,
encrypted_signature,
&state3.tx_lock,
state3.a.clone(),
state3.s_a,
state3.B,
&state3.redeem_address,
) {
Ok(tx) => tx,
Err(_) => {
return swap(
AliceState::WaitingToCancel { state3 },
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
} => {
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(_) => {
return run_until(
AliceState::WaitingToCancel { state3 },
is_target_state,
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await;
}
};
// 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,
)
.await?;
run_until(
AliceState::BtcRedeemed,
is_target_state,
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
AliceState::WaitingToCancel { state3 } => {
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?;
run_until(
AliceState::BtcCancelled { state3, tx_cancel },
is_target_state,
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
AliceState::BtcCancelled { state3, tx_cancel } => {
let tx_cancel_height = bitcoin_wallet
.transaction_block_height(tx_cancel.txid())
.await;
}
};
// 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)
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?;
swap(
AliceState::BtcRedeemed,
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
AliceState::WaitingToCancel { state3 } => {
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?;
swap(
AliceState::BtcCancelled { state3, tx_cancel },
swarm,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
AliceState::BtcCancelled { state3, tx_cancel } => {
let tx_cancel_height = bitcoin_wallet
.transaction_block_height(tx_cancel.txid())
.await;
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?;
// TODO(Franck): Review error handling
match published_refund_tx {
None => {
swap(
AliceState::BtcPunishable { tx_refund, state3 },
swarm,
bitcoin_wallet.clone(),
monero_wallet,
config,
)
.await
}
Some(published_refund_tx) => {
swap(
AliceState::BtcRefunded {
tx_refund,
published_refund_tx,
state3,
},
swarm,
bitcoin_wallet.clone(),
monero_wallet,
config,
)
.await
// TODO(Franck): Review error handling
match published_refund_tx {
None => {
run_until(
AliceState::BtcPunishable { tx_refund, state3 },
is_target_state,
swarm,
bitcoin_wallet.clone(),
monero_wallet,
config,
)
.await
}
Some(published_refund_tx) => {
run_until(
AliceState::BtcRefunded {
tx_refund,
published_refund_tx,
state3,
},
is_target_state,
swarm,
bitcoin_wallet.clone(),
monero_wallet,
config,
)
.await
}
}
}
}
AliceState::BtcRefunded {
tx_refund,
published_refund_tx,
state3,
} => {
let spend_key = extract_monero_private_key(
published_refund_tx,
AliceState::BtcRefunded {
tx_refund,
state3.s_a,
state3.a.clone(),
state3.S_b_bitcoin,
)?;
let view_key = state3.v;
published_refund_tx,
state3,
} => {
let spend_key = extract_monero_private_key(
published_refund_tx,
tx_refund,
state3.s_a,
state3.a.clone(),
state3.S_b_bitcoin,
)?;
let view_key = state3.v;
monero_wallet
.create_and_load_wallet_for_output(spend_key, view_key)
.await?;
monero_wallet
.create_and_load_wallet_for_output(spend_key, view_key)
.await?;
Ok(AliceState::XmrRefunded)
}
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,
)?;
Ok((AliceState::XmrRefunded, swarm))
}
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,
)?;
let punish_tx_finalised = publish_bitcoin_punish_transaction(
signed_tx_punish,
bitcoin_wallet.clone(),
config,
);
let punish_tx_finalised = publish_bitcoin_punish_transaction(
signed_tx_punish,
bitcoin_wallet.clone(),
config,
);
let refund_tx_seen = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid());
let refund_tx_seen = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid());
pin_mut!(punish_tx_finalised);
pin_mut!(refund_tx_seen);
pin_mut!(punish_tx_finalised);
pin_mut!(refund_tx_seen);
match select(punish_tx_finalised, refund_tx_seen).await {
Either::Left(_) => {
swap(
AliceState::Punished,
swarm,
bitcoin_wallet.clone(),
monero_wallet,
config,
)
.await
}
Either::Right((published_refund_tx, _)) => {
swap(
AliceState::BtcRefunded {
tx_refund,
published_refund_tx,
state3,
},
swarm,
bitcoin_wallet.clone(),
monero_wallet,
config,
)
.await
match select(punish_tx_finalised, refund_tx_seen).await {
Either::Left(_) => {
run_until(
AliceState::Punished,
is_target_state,
swarm,
bitcoin_wallet.clone(),
monero_wallet,
config,
)
.await
}
Either::Right((published_refund_tx, _)) => {
run_until(
AliceState::BtcRefunded {
tx_refund,
published_refund_tx,
state3,
},
is_target_state,
swarm,
bitcoin_wallet.clone(),
monero_wallet,
config,
)
.await
}
}
}
AliceState::XmrRefunded => Ok((AliceState::XmrRefunded, swarm)),
AliceState::BtcRedeemed => Ok((AliceState::BtcRedeemed, swarm)),
AliceState::Punished => Ok((AliceState::Punished, swarm)),
AliceState::SafelyAborted => Ok((AliceState::SafelyAborted, swarm)),
}
AliceState::XmrRefunded => Ok(AliceState::XmrRefunded),
AliceState::BtcRedeemed => Ok(AliceState::BtcRedeemed),
AliceState::Punished => Ok(AliceState::Punished),
AliceState::SafelyAborted => Ok(AliceState::SafelyAborted),
}
}

View file

@ -7,8 +7,8 @@ use anyhow::Result;
use async_recursion::async_recursion;
use libp2p::{core::Multiaddr, PeerId};
use rand::{CryptoRng, RngCore};
use std::sync::Arc;
use tracing::debug;
use std::{fmt, sync::Arc};
use tracing::{debug, info};
use uuid::Uuid;
use xmr_btc::bob::{self};
@ -33,10 +33,73 @@ pub enum BobState {
SafelyAborted,
}
// State machine driver for swap execution
#[async_recursion]
impl fmt::Display for BobState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BobState::Started { .. } => write!(f, "started"),
BobState::Negotiated(..) => write!(f, "negotiated"),
BobState::BtcLocked(..) => write!(f, "btc_locked"),
BobState::XmrLocked(..) => write!(f, "xmr_locked"),
BobState::EncSigSent(..) => write!(f, "encsig_sent"),
BobState::BtcRedeemed(_) => write!(f, "btc_redeemed"),
BobState::Cancelled(_) => write!(f, "cancelled"),
BobState::BtcRefunded => write!(f, "btc_refunded"),
BobState::XmrRedeemed => write!(f, "xmr_redeemed"),
BobState::Punished => write!(f, "punished"),
BobState::SafelyAborted => write!(f, "safely_aborted"),
}
}
}
pub async fn swap<R>(
state: BobState,
swarm: Swarm,
db: Database,
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
monero_wallet: Arc<crate::monero::Wallet>,
rng: R,
swap_id: Uuid,
) -> Result<BobState>
where
R: RngCore + CryptoRng + Send,
{
run_until(
state,
is_complete,
swarm,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id,
)
.await
}
pub fn is_complete(state: &BobState) -> bool {
matches!(
state,
BobState::BtcRefunded
| BobState::XmrRedeemed
| BobState::Punished
| BobState::SafelyAborted
)
}
pub fn is_btc_locked(state: &BobState) -> bool {
matches!(state, BobState::BtcLocked(..))
}
pub fn is_xmr_locked(state: &BobState) -> bool {
matches!(state, BobState::XmrLocked(..))
}
// State machine driver for swap execution
#[allow(clippy::too_many_arguments)]
#[async_recursion]
pub async fn run_until<R>(
state: BobState,
is_target_state: fn(&BobState) -> bool,
mut swarm: Swarm,
db: Database,
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
@ -47,219 +110,195 @@ pub async fn swap<R>(
where
R: RngCore + CryptoRng + Send,
{
match state {
BobState::Started {
state0,
amounts,
peer_id,
addr,
} => {
let state2 = negotiate(
info!("Current state: {}", state);
if is_target_state(&state) {
Ok(state)
} else {
match state {
BobState::Started {
state0,
amounts,
&mut swarm,
peer_id,
addr,
&mut rng,
bitcoin_wallet.clone(),
)
.await?;
swap(
BobState::Negotiated(state2, peer_id),
swarm,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id,
)
.await
}
BobState::Negotiated(state2, alice_peer_id) => {
// Alice and Bob have exchanged info
let state3 = state2.lock_btc(bitcoin_wallet.as_ref()).await?;
// db.insert_latest_state(state);
swap(
BobState::BtcLocked(state3, alice_peer_id),
swarm,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id,
)
.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),
};
swap(
BobState::XmrLocked(state4, alice_peer_id),
swarm,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id,
)
.await
}
BobState::XmrLocked(state, alice_peer_id) => {
// Alice has locked Xmr
// Bob sends Alice his key
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);
// Sadly we have to poll the swarm to get make sure the message is sent?
// FIXME: Having to wait for Alice's response here is a big problem, because
// we're stuck if she doesn't send her response back. I believe this is
// currently necessary, so we may have to rework this and/or how we use libp2p
match swarm.next().await {
OutEvent::Message3 => {
debug!("Got Message3 empty response");
}
other => panic!("unexpected event: {:?}", other),
};
swap(
BobState::EncSigSent(state, alice_peer_id),
swarm,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id,
)
.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 => {
swap(
BobState::BtcRedeemed(val?),
swarm,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id,
)
.await
}
_ = t1_timeout => {
// Check whether TxCancel has been published.
// We should not fail if the transaction is already on the blockchain
if state.check_for_tx_cancel(bitcoin_wallet.as_ref()).await.is_err() {
state.submit_tx_cancel(bitcoin_wallet.as_ref()).await?;
} => {
let state2 = negotiate(
state0,
amounts,
&mut swarm,
addr,
&mut rng,
bitcoin_wallet.clone(),
)
.await?;
run_until(
BobState::Negotiated(state2, peer_id),
is_target_state,
swarm,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id,
)
.await
}
BobState::Negotiated(state2, alice_peer_id) => {
// Alice and Bob have exchanged info
let state3 = state2.lock_btc(bitcoin_wallet.as_ref()).await?;
// db.insert_latest_state(state);
run_until(
BobState::BtcLocked(state3, alice_peer_id),
is_target_state,
swarm,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id,
)
.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),
};
run_until(
BobState::XmrLocked(state4, alice_peer_id),
is_target_state,
swarm,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id,
)
.await
}
BobState::XmrLocked(state, alice_peer_id) => {
// Alice has locked Xmr
// Bob sends Alice his key
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);
swap(
BobState::Cancelled(state),
swarm,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id
)
.await
// Sadly we have to poll the swarm to get make sure the message is sent?
// FIXME: Having to wait for Alice's response here is a big problem, because
// we're stuck if she doesn't send her response back. I believe this is
// currently necessary, so we may have to rework this and/or how we use libp2p
match swarm.next().await {
OutEvent::Message3 => {
debug!("Got Message3 empty response");
}
other => panic!("unexpected event: {:?}", other),
};
run_until(
BobState::EncSigSent(state, alice_peer_id),
is_target_state,
swarm,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id,
)
.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 => {
run_until(
BobState::BtcRedeemed(val?),
is_target_state,
swarm,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id,
)
.await
}
_ = t1_timeout => {
// Check whether TxCancel has been published.
// We should not fail if the transaction is already on the blockchain
if state.check_for_tx_cancel(bitcoin_wallet.as_ref()).await.is_err() {
state.submit_tx_cancel(bitcoin_wallet.as_ref()).await?;
}
run_until(
BobState::Cancelled(state),
is_target_state,
swarm,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id
)
.await
}
}
}
BobState::BtcRedeemed(state) => {
// Bob redeems XMR using revealed s_a
state.claim_xmr(monero_wallet.as_ref()).await?;
run_until(
BobState::XmrRedeemed,
is_target_state,
swarm,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id,
)
.await
}
BobState::Cancelled(_state) => {
// 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
// }
Ok(BobState::BtcRefunded)
}
BobState::BtcRefunded => {
info!("btc refunded");
Ok(BobState::BtcRefunded)
}
BobState::Punished => {
info!("punished");
Ok(BobState::Punished)
}
BobState::SafelyAborted => {
info!("safely aborted");
Ok(BobState::SafelyAborted)
}
BobState::XmrRedeemed => {
info!("xmr redeemed");
Ok(BobState::XmrRedeemed)
}
}
BobState::BtcRedeemed(state) => {
// Bob redeems XMR using revealed s_a
state.claim_xmr(monero_wallet.as_ref()).await?;
swap(
BobState::XmrRedeemed,
swarm,
db,
bitcoin_wallet,
monero_wallet,
rng,
swap_id,
)
.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),
// }
// }