Allow Bob to exit execution at a specified state

This commit is contained in:
rishflab 2020-12-02 12:09:43 +11:00
parent 150e5f2aba
commit 584cc22024
2 changed files with 157 additions and 120 deletions

1
Cargo.lock generated
View File

@ -3369,6 +3369,7 @@ dependencies = [
"bitcoin", "bitcoin",
"bitcoin-harness", "bitcoin-harness",
"conquer-once", "conquer-once",
"conquer-once",
"derivative", "derivative",
"ecdsa_fun", "ecdsa_fun",
"futures", "futures",

View File

@ -33,10 +33,31 @@ pub enum BobState {
SafelyAborted, SafelyAborted,
} }
// State machine driver for swap execution
#[async_recursion]
pub async fn swap<R>( pub async fn swap<R>(
state: BobState, 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
}
// TODO: use macro or generics
pub fn is_complete(state: &BobState) -> bool {
matches!(state, BobState::BtcRefunded| BobState::XmrRedeemed | BobState::Punished | BobState::SafelyAborted)
}
// State machine driver for swap execution
#[async_recursion]
pub async fn run_until<R>(
state: BobState,
is_state: fn(&BobState) -> bool,
mut swarm: Swarm, mut swarm: Swarm,
db: Database, db: Database,
bitcoin_wallet: Arc<crate::bitcoin::Wallet>, bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
@ -47,112 +68,120 @@ pub async fn swap<R>(
where where
R: RngCore + CryptoRng + Send, R: RngCore + CryptoRng + Send,
{ {
match state { if is_state(&state) {
BobState::Started { Ok(state)
state0, } else {
amounts, match state {
peer_id, BobState::Started {
addr,
} => {
let state2 = negotiate(
state0, state0,
amounts, amounts,
&mut swarm, peer_id,
addr, addr,
&mut rng, } => {
bitcoin_wallet.clone(), let state2 = negotiate(
) state0,
.await?; amounts,
swap( &mut swarm,
BobState::Negotiated(state2, peer_id), addr,
swarm, &mut rng,
db, bitcoin_wallet.clone(),
bitcoin_wallet, )
monero_wallet, .await?;
rng, run_until(
swap_id, BobState::Negotiated(state2, peer_id),
) is_state,
.await swarm,
} db,
BobState::Negotiated(state2, alice_peer_id) => { bitcoin_wallet,
// Alice and Bob have exchanged info monero_wallet,
let state3 = state2.lock_btc(bitcoin_wallet.as_ref()).await?; rng,
// db.insert_latest_state(state); swap_id,
swap( )
BobState::BtcLocked(state3, alice_peer_id), .await
swarm, }
db, BobState::Negotiated(state2, alice_peer_id) => {
bitcoin_wallet, // Alice and Bob have exchanged info
monero_wallet, let state3 = state2.lock_btc(bitcoin_wallet.as_ref()).await?;
rng, // db.insert_latest_state(state);
swap_id, run_until(
) BobState::BtcLocked(state3, alice_peer_id),
.await is_state,
} swarm,
// Bob has locked Btc db,
// Watch for Alice to Lock Xmr or for t1 to elapse bitcoin_wallet,
BobState::BtcLocked(state3, alice_peer_id) => { monero_wallet,
// todo: watch until t1, not indefinetely rng,
let state4 = match swarm.next().await { swap_id,
OutEvent::Message2(msg) => { )
state3 .await
.watch_for_lock_xmr(monero_wallet.as_ref(), msg) }
.await? // Bob has locked Btc
} // Watch for Alice to Lock Xmr or for t1 to elapse
other => panic!("unexpected event: {:?}", other), BobState::BtcLocked(state3, alice_peer_id) => {
}; // todo: watch until t1, not indefinetely
swap( let state4 = match swarm.next().await {
BobState::XmrLocked(state4, alice_peer_id), OutEvent::Message2(msg) => {
swarm, state3
db, .watch_for_lock_xmr(monero_wallet.as_ref(), msg)
bitcoin_wallet, .await?
monero_wallet, }
rng, other => panic!("unexpected event: {:?}", other),
swap_id, };
) run_until(
.await BobState::XmrLocked(state4, alice_peer_id),
} is_state,
BobState::XmrLocked(state, alice_peer_id) => { swarm,
// Alice has locked Xmr db,
// Bob sends Alice his key bitcoin_wallet,
let tx_redeem_encsig = state.tx_redeem_encsig(); monero_wallet,
// Do we have to wait for a response? rng,
// What if Alice fails to receive this? Should we always resend? swap_id,
// todo: If we cannot dial Alice we should go to EncSigSent. Maybe dialing )
// should happen in this arm? .await
swarm.send_message3(alice_peer_id.clone(), tx_redeem_encsig); }
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? // 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 // 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 // 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 // currently necessary, so we may have to rework this and/or how we use libp2p
match swarm.next().await { match swarm.next().await {
OutEvent::Message3 => { OutEvent::Message3 => {
debug!("Got Message3 empty response"); debug!("Got Message3 empty response");
} }
other => panic!("unexpected event: {:?}", other), other => panic!("unexpected event: {:?}", other),
}; };
swap( run_until(
BobState::EncSigSent(state, alice_peer_id), BobState::EncSigSent(state, alice_peer_id),
swarm, is_state,
db, swarm,
bitcoin_wallet, db,
monero_wallet, bitcoin_wallet,
rng, monero_wallet,
swap_id, rng,
) swap_id,
.await )
} .await
BobState::EncSigSent(state, ..) => { }
// Watch for redeem BobState::EncSigSent(state, ..) => {
let redeem_watcher = state.watch_for_redeem_btc(bitcoin_wallet.as_ref()); // Watch for redeem
let t1_timeout = state.wait_for_t1(bitcoin_wallet.as_ref()); 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! { tokio::select! {
val = redeem_watcher => { val = redeem_watcher => {
swap( run_until(
BobState::BtcRedeemed(val?), BobState::BtcRedeemed(val?),
is_state,
swarm, swarm,
db, db,
bitcoin_wallet, bitcoin_wallet,
@ -169,8 +198,9 @@ where
state.submit_tx_cancel(bitcoin_wallet.as_ref()).await?; state.submit_tx_cancel(bitcoin_wallet.as_ref()).await?;
} }
swap( run_until(
BobState::Cancelled(state), BobState::Cancelled(state),
is_state,
swarm, swarm,
db, db,
bitcoin_wallet, bitcoin_wallet,
@ -182,29 +212,35 @@ where
} }
} }
}
BobState::BtcRedeemed(state) => {
// Bob redeems XMR using revealed s_a
state.claim_xmr(monero_wallet.as_ref()).await?;
run_until(
BobState::XmrRedeemed,
is_state,
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),
} }
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 // // State machine driver for recovery execution
// #[async_recursion] // #[async_recursion]
// pub async fn abort(state: BobState, io: Io) -> Result<BobState> { // pub async fn abort(state: BobState, io: Io) -> Result<BobState> {