Save Alice states in db

This commit is contained in:
Franck Royer 2020-12-07 12:47:21 +11:00
parent 238f6a989d
commit 64b021daf4
No known key found for this signature in database
GPG Key ID: A82ED75A8DFC50A4
5 changed files with 301 additions and 119 deletions

View File

@ -10,11 +10,15 @@ use crate::{
wait_for_bitcoin_encrypted_signature, wait_for_bitcoin_refund, wait_for_locked_bitcoin, wait_for_bitcoin_encrypted_signature, wait_for_bitcoin_refund, wait_for_locked_bitcoin,
}, },
}, },
bitcoin,
bitcoin::EncryptedSignature, bitcoin::EncryptedSignature,
network::request_response::AliceToBob, network::request_response::AliceToBob,
state,
state::{Alice, Swap},
storage::Database,
SwapAmounts, SwapAmounts,
}; };
use anyhow::Result; use anyhow::{bail, Result};
use async_recursion::async_recursion; use async_recursion::async_recursion;
use futures::{ use futures::{
future::{select, Either}, future::{select, Either},
@ -22,8 +26,13 @@ use futures::{
}; };
use libp2p::request_response::ResponseChannel; use libp2p::request_response::ResponseChannel;
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
use std::{fmt, sync::Arc}; use std::{
convert::{TryFrom, TryInto},
fmt,
sync::Arc,
};
use tracing::info; use tracing::info;
use uuid::Uuid;
use xmr_btc::{ use xmr_btc::{
alice::{State0, State3}, alice::{State0, State3},
bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction}, bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction},
@ -102,12 +111,124 @@ impl fmt::Display for AliceState {
} }
} }
impl TryFrom<&AliceState> for state::Swap {
type Error = anyhow::Error;
fn try_from(alice_state: &AliceState) -> Result<Self> {
use state::{Alice::*, Swap::Alice};
let state = match alice_state {
AliceState::Started { .. } => bail!("Does not support storing `Started state."),
AliceState::Negotiated { state3, .. } => Negotiated(state3.clone()),
AliceState::BtcLocked { state3, .. } => BtcLocked(state3.clone()),
AliceState::XmrLocked { state3 } => XmrLocked(state3.clone()),
AliceState::EncSignLearned {
state3,
encrypted_signature,
} => EncSignLearned {
state: state3.clone(),
encrypted_signature: encrypted_signature.clone(),
},
AliceState::BtcRedeemed => SwapComplete,
AliceState::BtcCancelled { state3, .. } => BtcCancelled(state3.clone()),
AliceState::BtcRefunded { .. } => SwapComplete,
AliceState::BtcPunishable { state3, .. } => BtcPunishable(state3.clone()),
AliceState::XmrRefunded => SwapComplete,
// TODO(Franck): it may be more efficient to store the fact that we already want to
// abort
AliceState::Cancelling { state3 } => XmrLocked(state3.clone()),
AliceState::Punished => SwapComplete,
AliceState::SafelyAborted => SwapComplete,
};
Ok(Alice(state))
}
}
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 {
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,
},
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.")
}
}
}
pub async fn swap( pub async fn swap(
state: AliceState, state: AliceState,
event_loop_handle: EventLoopHandle, event_loop_handle: EventLoopHandle,
bitcoin_wallet: Arc<crate::bitcoin::Wallet>, bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
monero_wallet: Arc<crate::monero::Wallet>, monero_wallet: Arc<crate::monero::Wallet>,
config: Config, config: Config,
swap_id: Uuid,
db: Database,
) -> Result<(AliceState, EventLoopHandle)> { ) -> Result<(AliceState, EventLoopHandle)> {
run_until( run_until(
state, state,
@ -116,6 +237,8 @@ pub async fn swap(
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
config, config,
swap_id,
db,
) )
.await .await
} }
@ -139,6 +262,7 @@ pub fn is_xmr_locked(state: &AliceState) -> bool {
// State machine driver for swap execution // State machine driver for swap execution
#[async_recursion] #[async_recursion]
#[allow(clippy::too_many_arguments)]
pub async fn run_until( pub async fn run_until(
state: AliceState, state: AliceState,
is_target_state: fn(&AliceState) -> bool, is_target_state: fn(&AliceState) -> bool,
@ -146,6 +270,8 @@ pub async fn run_until(
bitcoin_wallet: Arc<crate::bitcoin::Wallet>, bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
monero_wallet: Arc<crate::monero::Wallet>, monero_wallet: Arc<crate::monero::Wallet>,
config: Config, config: Config,
swap_id: Uuid,
db: Database,
) -> Result<(AliceState, EventLoopHandle)> { ) -> Result<(AliceState, EventLoopHandle)> {
info!("Current state:{}", state); info!("Current state:{}", state);
if is_target_state(&state) { if is_target_state(&state) {
@ -156,17 +282,23 @@ pub async fn run_until(
let (channel, state3) = let (channel, state3) =
negotiate(state0, amounts, &mut event_loop_handle, config).await?; negotiate(state0, amounts, &mut event_loop_handle, config).await?;
run_until( let state = AliceState::Negotiated {
AliceState::Negotiated {
channel: Some(channel), channel: Some(channel),
amounts, amounts,
state3, state3,
}, };
let db_state = (&state).try_into()?;
db.insert_latest_state(swap_id, db_state).await?;
run_until(
state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
config, config,
swap_id,
db,
) )
.await .await
} }
@ -175,7 +307,7 @@ pub async fn run_until(
channel, channel,
amounts, amounts,
} => { } => {
match channel { let state = match channel {
Some(channel) => { Some(channel) => {
let _ = wait_for_locked_bitcoin( let _ = wait_for_locked_bitcoin(
state3.tx_lock.txid(), state3.tx_lock.txid(),
@ -184,41 +316,40 @@ pub async fn run_until(
) )
.await?; .await?;
run_until(
AliceState::BtcLocked { AliceState::BtcLocked {
channel: Some(channel), channel: Some(channel),
amounts, amounts,
state3, state3,
}, }
is_target_state,
event_loop_handle,
bitcoin_wallet,
monero_wallet,
config,
)
.await
} }
None => { None => {
tracing::info!("Cannot resume swap from negotiated state, aborting"); tracing::info!("Cannot resume swap from negotiated state, aborting");
// Alice did not lock Xmr yet // Alice did not lock Xmr yet
AliceState::SafelyAborted
}
};
let db_state = (&state).try_into()?;
db.insert_latest_state(swap_id, db_state).await?;
run_until( run_until(
AliceState::SafelyAborted, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
config, config,
swap_id,
db,
) )
.await .await
} }
}
}
AliceState::BtcLocked { AliceState::BtcLocked {
channel, channel,
amounts, amounts,
state3, state3,
} => match channel { } => {
let state = match channel {
Some(channel) => { Some(channel) => {
lock_xmr( lock_xmr(
channel, channel,
@ -229,83 +360,69 @@ pub async fn run_until(
) )
.await?; .await?;
run_until( AliceState::XmrLocked { state3 }
AliceState::XmrLocked { state3 },
is_target_state,
event_loop_handle,
bitcoin_wallet,
monero_wallet,
config,
)
.await
} }
None => { None => {
tracing::info!("Cannot resume swap from BTC locked state, aborting"); tracing::info!("Cannot resume swap from BTC locked state, aborting");
// Alice did not lock Xmr yet // Alice did not lock Xmr yet
AliceState::SafelyAborted
}
};
let db_state = (&state).try_into()?;
db.insert_latest_state(swap_id, db_state).await?;
run_until( run_until(
AliceState::SafelyAborted, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
config, config,
swap_id,
db,
) )
.await .await
} }
},
AliceState::XmrLocked { state3 } => { AliceState::XmrLocked { state3 } => {
// todo: match statement and wait for t1 can probably be expressed more cleanly // todo: match statement and wait for t1 can probably be expressed more cleanly
match state3.current_epoch(bitcoin_wallet.as_ref()).await? { let state = match state3.current_epoch(bitcoin_wallet.as_ref()).await? {
Epoch::T0 => { Epoch::T0 => {
let wait_for_enc_sig = wait_for_bitcoin_encrypted_signature( let wait_for_enc_sig = wait_for_bitcoin_encrypted_signature(
&mut event_loop_handle, &mut event_loop_handle,
config.monero_max_finality_time, config.monero_max_finality_time,
); );
let t1_timeout = state3.wait_for_t1(bitcoin_wallet.as_ref()); let state3_clone = state3.clone();
let t1_timeout = state3_clone.wait_for_t1(bitcoin_wallet.as_ref());
tokio::select! { pin_mut!(wait_for_enc_sig);
_ = t1_timeout => { pin_mut!(t1_timeout);
run_until(
AliceState::Cancelling { state3 }, match select(t1_timeout, wait_for_enc_sig).await {
is_target_state, Either::Left(_) => AliceState::Cancelling { state3 },
event_loop_handle, Either::Right((enc_sig, _)) => AliceState::EncSignLearned {
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
enc_sig = wait_for_enc_sig => {
run_until(
AliceState::EncSignLearned {
state3, state3,
encrypted_signature: enc_sig?, encrypted_signature: enc_sig?,
}, },
is_target_state,
event_loop_handle,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
}
}
_ => {
run_until(
AliceState::Cancelling { state3 },
is_target_state,
event_loop_handle,
bitcoin_wallet,
monero_wallet,
config,
)
.await
}
} }
} }
_ => AliceState::Cancelling { state3 },
};
let db_state = (&state).try_into()?;
db.insert_latest_state(swap_id, db_state).await?;
run_until(
state,
is_target_state,
event_loop_handle,
bitcoin_wallet.clone(),
monero_wallet,
config,
swap_id,
db,
)
.await
}
AliceState::EncSignLearned { AliceState::EncSignLearned {
state3, state3,
encrypted_signature, encrypted_signature,
@ -320,13 +437,18 @@ pub async fn run_until(
) { ) {
Ok(tx) => tx, Ok(tx) => tx,
Err(_) => { Err(_) => {
let state = AliceState::Cancelling { state3 };
let db_state = (&state).try_into()?;
db.insert_latest_state(swap_id, db_state).await?;
return run_until( return run_until(
AliceState::Cancelling { state3 }, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
config, config,
swap_id,
db,
) )
.await; .await;
} }
@ -342,13 +464,18 @@ pub async fn run_until(
) )
.await?; .await?;
let state = AliceState::BtcRedeemed;
let db_state = (&state).try_into()?;
db.insert_latest_state(swap_id, db_state).await?;
run_until( run_until(
AliceState::BtcRedeemed, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
config, config,
swap_id,
db,
) )
.await .await
} }
@ -363,13 +490,18 @@ pub async fn run_until(
) )
.await?; .await?;
let state = AliceState::BtcCancelled { state3, tx_cancel };
let db_state = (&state).try_into()?;
db.insert_latest_state(swap_id, db_state).await?;
run_until( run_until(
AliceState::BtcCancelled { state3, tx_cancel }, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
config, config,
swap_id,
db,
) )
.await .await
} }
@ -390,13 +522,17 @@ pub async fn run_until(
// TODO(Franck): Review error handling // TODO(Franck): Review error handling
match published_refund_tx { match published_refund_tx {
None => { None => {
run_until( let state = AliceState::BtcPunishable { tx_refund, state3 };
AliceState::BtcPunishable { tx_refund, state3 }, let db_state = (&state).try_into()?;
is_target_state, db.insert_latest_state(swap_id, db_state).await?;
swap(
state,
event_loop_handle, event_loop_handle,
bitcoin_wallet.clone(), bitcoin_wallet.clone(),
monero_wallet, monero_wallet,
config, config,
swap_id,
db,
) )
.await .await
} }
@ -409,13 +545,18 @@ pub async fn run_until(
state3.S_b_bitcoin, state3.S_b_bitcoin,
)?; )?;
let state = AliceState::BtcRefunded { spend_key, state3 };
let db_state = (&state).try_into()?;
db.insert_latest_state(swap_id, db_state).await?;
run_until( run_until(
AliceState::BtcRefunded { spend_key, state3 }, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
bitcoin_wallet.clone(), bitcoin_wallet.clone(),
monero_wallet, monero_wallet,
config, config,
swap_id,
db,
) )
.await .await
} }
@ -428,7 +569,10 @@ pub async fn run_until(
.create_and_load_wallet_for_output(spend_key, view_key) .create_and_load_wallet_for_output(spend_key, view_key)
.await?; .await?;
Ok((AliceState::XmrRefunded, event_loop_handle)) let state = AliceState::XmrRefunded;
let db_state = (&state).try_into()?;
db.insert_latest_state(swap_id, db_state).await?;
Ok((state, event_loop_handle))
} }
AliceState::BtcPunishable { tx_refund, state3 } => { AliceState::BtcPunishable { tx_refund, state3 } => {
let signed_tx_punish = build_bitcoin_punish_transaction( let signed_tx_punish = build_bitcoin_punish_transaction(
@ -454,13 +598,18 @@ pub async fn run_until(
match select(punish_tx_finalised, refund_tx_seen).await { match select(punish_tx_finalised, refund_tx_seen).await {
Either::Left(_) => { Either::Left(_) => {
let state = AliceState::Punished;
let db_state = (&state).try_into()?;
db.insert_latest_state(swap_id, db_state).await?;
run_until( run_until(
AliceState::Punished, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
bitcoin_wallet.clone(), bitcoin_wallet.clone(),
monero_wallet, monero_wallet,
config, config,
swap_id,
db,
) )
.await .await
} }
@ -472,14 +621,18 @@ pub async fn run_until(
state3.a.clone(), state3.a.clone(),
state3.S_b_bitcoin, state3.S_b_bitcoin,
)?; )?;
let state = AliceState::BtcRefunded { spend_key, state3 };
let db_state = (&state).try_into()?;
db.insert_latest_state(swap_id, db_state).await?;
run_until( run_until(
AliceState::BtcRefunded { spend_key, state3 }, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
bitcoin_wallet.clone(), bitcoin_wallet.clone(),
monero_wallet, monero_wallet,
config, config,
swap_id,
db,
) )
.await .await
} }

View File

@ -117,12 +117,17 @@ async fn main() -> Result<()> {
let (mut event_loop, handle) = let (mut event_loop, handle) =
alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen_addr)?; alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen_addr)?;
let swap_id = Uuid::new_v4();
info!("Swap id: {}", swap_id);
let swap = alice::swap::swap( let swap = alice::swap::swap(
alice_state, alice_state,
handle, handle,
bitcoin_wallet.clone(), bitcoin_wallet.clone(),
monero_wallet.clone(), monero_wallet.clone(),
config, config,
swap_id,
db,
); );
let _event_loop = tokio::spawn(async move { event_loop.run().await }); let _event_loop = tokio::spawn(async move { event_loop.run().await });

View File

@ -157,6 +157,8 @@ pub async fn alice_recover(
} }
}; };
} }
Alice::EncSignLearned { .. } => unimplemented!("recover method is deprecated"),
Alice::BtcCancelled { .. } => unimplemented!("recover method is deprecated"),
Alice::BtcRedeemable { redeem_tx, state } => { Alice::BtcRedeemable { redeem_tx, state } => {
info!("Have the means to redeem the Bitcoin"); info!("Have the means to redeem the Bitcoin");

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Display; use std::fmt::Display;
use xmr_btc::{alice, bob, monero, serde::monero_private_key}; use xmr_btc::{alice, bitcoin::EncryptedSignature, bob, monero, serde::monero_private_key};
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
@ -15,10 +15,16 @@ pub enum Alice {
Negotiated(alice::State3), Negotiated(alice::State3),
BtcLocked(alice::State3), BtcLocked(alice::State3),
XmrLocked(alice::State3), XmrLocked(alice::State3),
// TODO(Franck): Delete this state as it is not used in alice::swap
BtcRedeemable { BtcRedeemable {
state: alice::State3, state: alice::State3,
redeem_tx: bitcoin::Transaction, redeem_tx: bitcoin::Transaction,
}, },
EncSignLearned {
state: alice::State3,
encrypted_signature: EncryptedSignature,
},
BtcCancelled(alice::State3),
BtcPunishable(alice::State3), BtcPunishable(alice::State3),
BtcRefunded { BtcRefunded {
state: alice::State3, state: alice::State3,
@ -67,9 +73,11 @@ impl Display for Alice {
Alice::BtcLocked(_) => f.write_str("Bitcoin locked"), Alice::BtcLocked(_) => f.write_str("Bitcoin locked"),
Alice::XmrLocked(_) => f.write_str("Monero locked"), Alice::XmrLocked(_) => f.write_str("Monero locked"),
Alice::BtcRedeemable { .. } => f.write_str("Bitcoin redeemable"), Alice::BtcRedeemable { .. } => f.write_str("Bitcoin redeemable"),
Alice::BtcCancelled(_) => f.write_str("Bitcoin cancel transaction published"),
Alice::BtcPunishable(_) => f.write_str("Bitcoin punishable"), Alice::BtcPunishable(_) => f.write_str("Bitcoin punishable"),
Alice::BtcRefunded { .. } => f.write_str("Monero refundable"), Alice::BtcRefunded { .. } => f.write_str("Monero refundable"),
Alice::SwapComplete => f.write_str("Swap complete"), Alice::SwapComplete => f.write_str("Swap complete"),
Alice::EncSignLearned { .. } => f.write_str("Encrypted signature learned"),
} }
} }
} }

View File

@ -77,12 +77,17 @@ async fn happy_path() {
) )
.await; .await;
let alice_db_datadir = tempdir().unwrap();
let alice_db = Database::open(alice_db_datadir.path()).unwrap();
let alice_swap_fut = alice::swap::swap( let alice_swap_fut = alice::swap::swap(
alice_state, alice_state,
alice_swarm_handle, alice_swarm_handle,
alice_btc_wallet.clone(), alice_btc_wallet.clone(),
alice_xmr_wallet.clone(), alice_xmr_wallet.clone(),
config, config,
Uuid::new_v4(),
alice_db,
); );
let _alice_swarm_fut = tokio::spawn(async move { alice_swarm_driver.run().await }); let _alice_swarm_fut = tokio::spawn(async move { alice_swarm_driver.run().await });
@ -188,12 +193,17 @@ async fn alice_punishes_if_bob_never_acts_after_fund() {
let _bob_swarm_fut = tokio::spawn(async move { bob_swarm_driver.run().await }); let _bob_swarm_fut = tokio::spawn(async move { bob_swarm_driver.run().await });
let alice_db_datadir = tempdir().unwrap();
let alice_db = Database::open(alice_db_datadir.path()).unwrap();
let alice_fut = alice::swap::swap( let alice_fut = alice::swap::swap(
alice_state, alice_state,
alice_swarm_handle, alice_swarm_handle,
alice_btc_wallet.clone(), alice_btc_wallet.clone(),
alice_xmr_wallet.clone(), alice_xmr_wallet.clone(),
Config::regtest(), Config::regtest(),
Uuid::new_v4(),
alice_db,
); );
let _alice_swarm_fut = tokio::spawn(async move { alice_swarm.run().await }); let _alice_swarm_fut = tokio::spawn(async move { alice_swarm.run().await });
@ -311,6 +321,8 @@ async fn both_refund() {
alice_btc_wallet.clone(), alice_btc_wallet.clone(),
alice_xmr_wallet.clone(), alice_xmr_wallet.clone(),
Config::regtest(), Config::regtest(),
todo!(),
todo!(),
); );
tokio::spawn(async move { alice_swarm_driver.run().await }); tokio::spawn(async move { alice_swarm_driver.run().await });
@ -331,6 +343,8 @@ async fn both_refund() {
alice_btc_wallet.clone(), alice_btc_wallet.clone(),
alice_xmr_wallet.clone(), alice_xmr_wallet.clone(),
Config::regtest(), Config::regtest(),
todo!(),
todo!(),
) )
.await .await
.unwrap(); .unwrap();
@ -408,8 +422,8 @@ async fn init_alice(
xmr: xmr_to_swap, xmr: xmr_to_swap,
}; };
let (alice_state, alice_behaviour) = {
let rng = &mut OsRng; let rng = &mut OsRng;
let (alice_state, alice_behaviour) = {
let a = bitcoin::SecretKey::new_random(rng); let a = bitcoin::SecretKey::new_random(rng);
let s_a = cross_curve_dleq::Scalar::random(rng); let s_a = cross_curve_dleq::Scalar::random(rng);
let v_a = xmr_btc::monero::PrivateViewKey::new_random(rng); let v_a = xmr_btc::monero::PrivateViewKey::new_random(rng);