112: Align database types with swap states r=D4nte a=D4nte

- Removing naming ambiguities and errors
- Clean up the DB state now that they are only use in the new recursive function.
- Remove few todos and panics.

Co-authored-by: Franck Royer <franck@coblox.tech>
This commit is contained in:
bors[bot] 2020-12-23 03:20:42 +00:00 committed by GitHub
commit 4e91ac467b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 433 additions and 349 deletions

22
Cargo.lock generated
View File

@ -3289,6 +3289,27 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "strum"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "1.0.0" version = "1.0.0"
@ -3337,6 +3358,7 @@ dependencies = [
"sled", "sled",
"spectral", "spectral",
"structopt", "structopt",
"strum",
"tempfile", "tempfile",
"testcontainers", "testcontainers",
"time", "time",

View File

@ -34,6 +34,7 @@ serde_json = "1"
sha2 = "0.9" sha2 = "0.9"
sled = "0.34" sled = "0.34"
structopt = "0.3" structopt = "0.3"
strum = { version = "0.20", features = ["derive"] }
tempfile = "3" tempfile = "3"
time = "0.2" time = "0.2"
tokio = { version = "0.2", features = ["rt-threaded", "time", "macros", "sync"] } tokio = { version = "0.2", features = ["rt-threaded", "time", "macros", "sync"] }

View File

@ -207,20 +207,20 @@ pub async fn publish_cancel_transaction<W>(
tx_lock: TxLock, tx_lock: TxLock,
a: bitcoin::SecretKey, a: bitcoin::SecretKey,
B: bitcoin::PublicKey, B: bitcoin::PublicKey,
refund_timelock: u32, cancel_timelock: u32,
tx_cancel_sig_bob: bitcoin::Signature, tx_cancel_sig_bob: bitcoin::Signature,
bitcoin_wallet: Arc<W>, bitcoin_wallet: Arc<W>,
) -> Result<bitcoin::TxCancel> ) -> Result<bitcoin::TxCancel>
where where
W: GetRawTransaction + TransactionBlockHeight + BlockHeight + BroadcastSignedTransaction, W: GetRawTransaction + TransactionBlockHeight + BlockHeight + BroadcastSignedTransaction,
{ {
// First wait for t1 to expire // First wait for cancel timelock to expire
let tx_lock_height = bitcoin_wallet let tx_lock_height = bitcoin_wallet
.transaction_block_height(tx_lock.txid()) .transaction_block_height(tx_lock.txid())
.await; .await;
poll_until_block_height_is_gte(bitcoin_wallet.as_ref(), tx_lock_height + refund_timelock).await; poll_until_block_height_is_gte(bitcoin_wallet.as_ref(), tx_lock_height + cancel_timelock).await;
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, refund_timelock, a.public(), B); let tx_cancel = bitcoin::TxCancel::new(&tx_lock, cancel_timelock, a.public(), B);
// If Bob hasn't yet broadcasted the tx cancel, we do it // If Bob hasn't yet broadcasted the tx cancel, we do it
if bitcoin_wallet if bitcoin_wallet
@ -306,14 +306,14 @@ pub fn extract_monero_private_key(
pub fn build_bitcoin_punish_transaction( pub fn build_bitcoin_punish_transaction(
tx_lock: &TxLock, tx_lock: &TxLock,
refund_timelock: u32, cancel_timelock: u32,
punish_address: &bitcoin::Address, punish_address: &bitcoin::Address,
punish_timelock: u32, punish_timelock: u32,
tx_punish_sig_bob: bitcoin::Signature, tx_punish_sig_bob: bitcoin::Signature,
a: bitcoin::SecretKey, a: bitcoin::SecretKey,
B: bitcoin::PublicKey, B: bitcoin::PublicKey,
) -> Result<bitcoin::Transaction> { ) -> Result<bitcoin::Transaction> {
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, refund_timelock, a.public(), B); let tx_cancel = bitcoin::TxCancel::new(&tx_lock, cancel_timelock, a.public(), B);
let tx_punish = bitcoin::TxPunish::new(&tx_cancel, &punish_address, punish_timelock); let tx_punish = bitcoin::TxPunish::new(&tx_cancel, &punish_address, punish_timelock);
let sig_a = a.sign(tx_punish.digest()); let sig_a = a.sign(tx_punish.digest());

View File

@ -14,11 +14,11 @@ use crate::{
bitcoin::EncryptedSignature, bitcoin::EncryptedSignature,
network::request_response::AliceToBob, network::request_response::AliceToBob,
state, state,
state::{Alice, Swap}, state::{Alice, AliceEndState, Swap},
storage::Database, storage::Database,
SwapAmounts, SwapAmounts,
}; };
use anyhow::{bail, Result}; use anyhow::Result;
use async_recursion::async_recursion; use async_recursion::async_recursion;
use futures::{ use futures::{
future::{select, Either}, future::{select, Either},
@ -26,7 +26,7 @@ use futures::{
}; };
use libp2p::request_response::ResponseChannel; use libp2p::request_response::ResponseChannel;
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
use std::{convert::TryFrom, fmt, sync::Arc}; use std::{fmt, sync::Arc};
use tracing::info; use tracing::info;
use uuid::Uuid; use uuid::Uuid;
use xmr_btc::{ use xmr_btc::{
@ -34,7 +34,7 @@ use xmr_btc::{
bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction}, bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction},
config::Config, config::Config,
monero::CreateWalletForOutput, monero::CreateWalletForOutput,
Epoch, ExpiredTimelocks,
}; };
trait Rng: RngCore + CryptoRng + Send {} trait Rng: RngCore + CryptoRng + Send {}
@ -60,7 +60,7 @@ pub enum AliceState {
XmrLocked { XmrLocked {
state3: State3, state3: State3,
}, },
EncSignLearned { EncSigLearned {
state3: State3, state3: State3,
encrypted_signature: EncryptedSignature, encrypted_signature: EncryptedSignature,
}, },
@ -78,10 +78,10 @@ pub enum AliceState {
state3: State3, state3: State3,
}, },
XmrRefunded, XmrRefunded,
T1Expired { CancelTimelockExpired {
state3: State3, state3: State3,
}, },
Punished, BtcPunished,
SafelyAborted, SafelyAborted,
} }
@ -90,17 +90,17 @@ impl fmt::Display for AliceState {
match self { match self {
AliceState::Started { .. } => write!(f, "started"), AliceState::Started { .. } => write!(f, "started"),
AliceState::Negotiated { .. } => write!(f, "negotiated"), AliceState::Negotiated { .. } => write!(f, "negotiated"),
AliceState::BtcLocked { .. } => write!(f, "btc_locked"), AliceState::BtcLocked { .. } => write!(f, "btc is locked"),
AliceState::XmrLocked { .. } => write!(f, "xmr_locked"), AliceState::XmrLocked { .. } => write!(f, "xmr is locked"),
AliceState::EncSignLearned { .. } => write!(f, "encsig_learned"), AliceState::EncSigLearned { .. } => write!(f, "encrypted signature is learned"),
AliceState::BtcRedeemed => write!(f, "btc_redeemed"), AliceState::BtcRedeemed => write!(f, "btc is redeemed"),
AliceState::BtcCancelled { .. } => write!(f, "btc_cancelled"), AliceState::BtcCancelled { .. } => write!(f, "btc is cancelled"),
AliceState::BtcRefunded { .. } => write!(f, "btc_refunded"), AliceState::BtcRefunded { .. } => write!(f, "btc is refunded"),
AliceState::Punished => write!(f, "punished"), AliceState::BtcPunished => write!(f, "btc is punished"),
AliceState::SafelyAborted => write!(f, "safely_aborted"), AliceState::SafelyAborted => write!(f, "safely aborted"),
AliceState::BtcPunishable { .. } => write!(f, "btc_punishable"), AliceState::BtcPunishable { .. } => write!(f, "btc is punishable"),
AliceState::XmrRefunded => write!(f, "xmr_refunded"), AliceState::XmrRefunded => write!(f, "xmr is refunded"),
AliceState::T1Expired { .. } => write!(f, "t1 is expired"), AliceState::CancelTimelockExpired { .. } => write!(f, "cancel timelock is expired"),
} }
} }
} }
@ -111,36 +111,39 @@ impl From<&AliceState> for state::Alice {
AliceState::Negotiated { state3, .. } => Alice::Negotiated(state3.clone()), AliceState::Negotiated { state3, .. } => Alice::Negotiated(state3.clone()),
AliceState::BtcLocked { state3, .. } => Alice::BtcLocked(state3.clone()), AliceState::BtcLocked { state3, .. } => Alice::BtcLocked(state3.clone()),
AliceState::XmrLocked { state3 } => Alice::XmrLocked(state3.clone()), AliceState::XmrLocked { state3 } => Alice::XmrLocked(state3.clone()),
AliceState::EncSignLearned { AliceState::EncSigLearned {
state3, state3,
encrypted_signature, encrypted_signature,
} => Alice::EncSignLearned { } => Alice::EncSigLearned {
state: state3.clone(), state: state3.clone(),
encrypted_signature: encrypted_signature.clone(), encrypted_signature: encrypted_signature.clone(),
}, },
AliceState::BtcRedeemed => Alice::SwapComplete, AliceState::BtcRedeemed => Alice::Done(AliceEndState::BtcRedeemed),
AliceState::BtcCancelled { state3, .. } => Alice::BtcCancelled(state3.clone()), AliceState::BtcCancelled { state3, .. } => Alice::BtcCancelled(state3.clone()),
AliceState::BtcRefunded { .. } => Alice::SwapComplete, AliceState::BtcRefunded { spend_key, state3 } => Alice::BtcRefunded {
spend_key: *spend_key,
state3: state3.clone(),
},
AliceState::BtcPunishable { state3, .. } => Alice::BtcPunishable(state3.clone()), AliceState::BtcPunishable { state3, .. } => Alice::BtcPunishable(state3.clone()),
AliceState::XmrRefunded => Alice::SwapComplete, AliceState::XmrRefunded => Alice::Done(AliceEndState::XmrRefunded),
AliceState::T1Expired { state3 } => Alice::T1Expired(state3.clone()), AliceState::CancelTimelockExpired { state3 } => {
AliceState::Punished => Alice::SwapComplete, Alice::CancelTimelockExpired(state3.clone())
AliceState::SafelyAborted => Alice::SwapComplete,
// TODO: Potentially add support to resume swaps that are not Negotiated
AliceState::Started { .. } => {
panic!("Alice attempted to save swap before being negotiated")
} }
AliceState::BtcPunished => Alice::Done(AliceEndState::BtcPunished),
AliceState::SafelyAborted => Alice::Done(AliceEndState::SafelyAborted),
AliceState::Started { amounts, state0 } => Alice::Started {
amounts: *amounts,
state0: state0.clone(),
},
} }
} }
} }
impl TryFrom<state::Swap> for AliceState { impl From<state::Alice> for AliceState {
type Error = anyhow::Error; fn from(db_state: state::Alice) -> Self {
fn try_from(db_state: Swap) -> Result<Self, Self::Error> {
use AliceState::*; use AliceState::*;
if let Swap::Alice(state) = db_state { match db_state {
let alice_state = match state { Alice::Started { amounts, state0 } => Started { amounts, state0 },
Alice::Negotiated(state3) => Negotiated { Alice::Negotiated(state3) => Negotiated {
channel: None, channel: None,
amounts: SwapAmounts { amounts: SwapAmounts {
@ -158,19 +161,18 @@ impl TryFrom<state::Swap> for AliceState {
state3, state3,
}, },
Alice::XmrLocked(state3) => XmrLocked { state3 }, Alice::XmrLocked(state3) => XmrLocked { state3 },
Alice::BtcRedeemable { .. } => bail!("BtcRedeemable state is unexpected"), Alice::EncSigLearned {
Alice::EncSignLearned {
state, state,
encrypted_signature, encrypted_signature,
} => EncSignLearned { } => EncSigLearned {
state3: state, state3: state,
encrypted_signature, encrypted_signature,
}, },
Alice::T1Expired(state3) => AliceState::T1Expired { state3 }, Alice::CancelTimelockExpired(state3) => AliceState::CancelTimelockExpired { state3 },
Alice::BtcCancelled(state) => { Alice::BtcCancelled(state) => {
let tx_cancel = bitcoin::TxCancel::new( let tx_cancel = bitcoin::TxCancel::new(
&state.tx_lock, &state.tx_lock,
state.refund_timelock, state.cancel_timelock,
state.a.public(), state.a.public(),
state.B, state.B,
); );
@ -183,7 +185,7 @@ impl TryFrom<state::Swap> for AliceState {
Alice::BtcPunishable(state) => { Alice::BtcPunishable(state) => {
let tx_cancel = bitcoin::TxCancel::new( let tx_cancel = bitcoin::TxCancel::new(
&state.tx_lock, &state.tx_lock,
state.refund_timelock, state.cancel_timelock,
state.a.public(), state.a.public(),
state.B, state.B,
); );
@ -194,19 +196,19 @@ impl TryFrom<state::Swap> for AliceState {
} }
} }
Alice::BtcRefunded { Alice::BtcRefunded {
state, spend_key, .. state3: state,
spend_key,
..
} => BtcRefunded { } => BtcRefunded {
spend_key, spend_key,
state3: state, state3: state,
}, },
Alice::SwapComplete => { Alice::Done(end_state) => match end_state {
// TODO(Franck): Better fine grain AliceEndState::SafelyAborted => SafelyAborted,
AliceState::SafelyAborted AliceEndState::BtcRedeemed => BtcRedeemed,
} AliceEndState::XmrRefunded => XmrRefunded,
}; AliceEndState::BtcPunished => BtcPunished,
Ok(alice_state) },
} else {
bail!("Alice swap state expected.")
} }
} }
} }
@ -238,7 +240,7 @@ pub fn is_complete(state: &AliceState) -> bool {
state, state,
AliceState::XmrRefunded AliceState::XmrRefunded
| AliceState::BtcRedeemed | AliceState::BtcRedeemed
| AliceState::Punished | AliceState::BtcPunished
| AliceState::SafelyAborted | AliceState::SafelyAborted
) )
} }
@ -253,7 +255,7 @@ pub fn is_xmr_locked(state: &AliceState) -> bool {
pub fn is_encsig_learned(state: &AliceState) -> bool { pub fn is_encsig_learned(state: &AliceState) -> bool {
matches!( matches!(
state, state,
AliceState::EncSignLearned{..} AliceState::EncSigLearned{..}
) )
} }
@ -386,28 +388,30 @@ pub async fn run_until(
.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 cancel timelock to expire can probably be
let state = match state3.current_epoch(bitcoin_wallet.as_ref()).await? { // expressed more cleanly
Epoch::T0 => { let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? {
ExpiredTimelocks::None => {
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 state3_clone = state3.clone(); let state3_clone = state3.clone();
let t1_timeout = state3_clone.wait_for_t1(bitcoin_wallet.as_ref()); let cancel_timelock_expires = state3_clone
.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref());
pin_mut!(wait_for_enc_sig); pin_mut!(wait_for_enc_sig);
pin_mut!(t1_timeout); pin_mut!(cancel_timelock_expires);
match select(t1_timeout, wait_for_enc_sig).await { match select(cancel_timelock_expires, wait_for_enc_sig).await {
Either::Left(_) => AliceState::T1Expired { state3 }, Either::Left(_) => AliceState::CancelTimelockExpired { state3 },
Either::Right((enc_sig, _)) => AliceState::EncSignLearned { Either::Right((enc_sig, _)) => AliceState::EncSigLearned {
state3, state3,
encrypted_signature: enc_sig?, encrypted_signature: enc_sig?,
}, },
} }
} }
_ => AliceState::T1Expired { state3 }, _ => AliceState::CancelTimelockExpired { state3 },
}; };
let db_state = (&state).into(); let db_state = (&state).into();
@ -425,13 +429,13 @@ pub async fn run_until(
) )
.await .await
} }
AliceState::EncSignLearned { AliceState::EncSigLearned {
state3, state3,
encrypted_signature, encrypted_signature,
} => { } => {
// TODO: Evaluate if it is correct for Alice to Redeem no matter what. // TODO: Evaluate if it is correct for Alice to Redeem no matter what.
// If T1 expired she should potentially not try redeem. (The implementation // If cancel timelock expired she should potentially not try redeem. (The
// gives her an advantage.) // implementation gives her an advantage.)
let signed_tx_redeem = match build_bitcoin_redeem_transaction( let signed_tx_redeem = match build_bitcoin_redeem_transaction(
encrypted_signature, encrypted_signature,
@ -443,9 +447,11 @@ pub async fn run_until(
) { ) {
Ok(tx) => tx, Ok(tx) => tx,
Err(_) => { Err(_) => {
state3.wait_for_t1(bitcoin_wallet.as_ref()).await?; state3
.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref())
.await?;
let state = AliceState::T1Expired { state3 }; let state = AliceState::CancelTimelockExpired { state3 };
let db_state = (&state).into(); let db_state = (&state).into();
db.insert_latest_state(swap_id, Swap::Alice(db_state)) db.insert_latest_state(swap_id, Swap::Alice(db_state))
.await?; .await?;
@ -489,12 +495,12 @@ pub async fn run_until(
) )
.await .await
} }
AliceState::T1Expired { state3 } => { AliceState::CancelTimelockExpired { state3 } => {
let tx_cancel = publish_cancel_transaction( let tx_cancel = publish_cancel_transaction(
state3.tx_lock.clone(), state3.tx_lock.clone(),
state3.a.clone(), state3.a.clone(),
state3.B, state3.B,
state3.refund_timelock, state3.cancel_timelock,
state3.tx_cancel_sig_bob.clone(), state3.tx_cancel_sig_bob.clone(),
bitcoin_wallet.clone(), bitcoin_wallet.clone(),
) )
@ -591,7 +597,7 @@ pub async fn run_until(
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(
&state3.tx_lock, &state3.tx_lock,
state3.refund_timelock, state3.cancel_timelock,
&state3.punish_address, &state3.punish_address,
state3.punish_timelock, state3.punish_timelock,
state3.tx_punish_sig_bob.clone(), state3.tx_punish_sig_bob.clone(),
@ -612,7 +618,7 @@ 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 state = AliceState::BtcPunished;
let db_state = (&state).into(); let db_state = (&state).into();
db.insert_latest_state(swap_id, Swap::Alice(db_state)) db.insert_latest_state(swap_id, Swap::Alice(db_state))
.await?; .await?;
@ -656,7 +662,7 @@ pub async fn run_until(
} }
AliceState::XmrRefunded => Ok(AliceState::XmrRefunded), AliceState::XmrRefunded => Ok(AliceState::XmrRefunded),
AliceState::BtcRedeemed => Ok(AliceState::BtcRedeemed), AliceState::BtcRedeemed => Ok(AliceState::BtcRedeemed),
AliceState::Punished => Ok(AliceState::Punished), AliceState::BtcPunished => Ok(AliceState::BtcPunished),
AliceState::SafelyAborted => Ok(AliceState::SafelyAborted), AliceState::SafelyAborted => Ok(AliceState::SafelyAborted),
} }
} }

View File

@ -12,11 +12,11 @@
)] )]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
use anyhow::{Context, Result}; use anyhow::{bail, Context, Result};
use libp2p::{core::Multiaddr, PeerId}; use libp2p::{core::Multiaddr, PeerId};
use prettytable::{row, Table}; use prettytable::{row, Table};
use rand::rngs::OsRng; use rand::rngs::OsRng;
use std::{convert::TryFrom, sync::Arc}; use std::sync::Arc;
use structopt::StructOpt; use structopt::StructOpt;
use swap::{ use swap::{
alice, alice,
@ -26,6 +26,7 @@ use swap::{
cli::{Command, Options, Resume}, cli::{Command, Options, Resume},
monero, monero,
network::transport::build, network::transport::build,
state::Swap,
storage::Database, storage::Database,
trace::init_tracing, trace::init_tracing,
SwapAmounts, SwapAmounts,
@ -84,7 +85,7 @@ async fn main() -> Result<()> {
v_a, v_a,
amounts.btc, amounts.btc,
amounts.xmr, amounts.xmr,
config.bitcoin_refund_timelock, config.bitcoin_cancel_timelock,
config.bitcoin_punish_timelock, config.bitcoin_punish_timelock,
redeem_address, redeem_address,
punish_address, punish_address,
@ -132,7 +133,7 @@ async fn main() -> Result<()> {
&mut OsRng, &mut OsRng,
send_bitcoin, send_bitcoin,
receive_monero, receive_monero,
config.bitcoin_refund_timelock, config.bitcoin_cancel_timelock,
config.bitcoin_punish_timelock, config.bitcoin_punish_timelock,
refund_address, refund_address,
); );
@ -180,9 +181,12 @@ async fn main() -> Result<()> {
monero_wallet_rpc_url, monero_wallet_rpc_url,
listen_addr, listen_addr,
}) => { }) => {
let db_swap = db.get_state(swap_id)?; let db_state = if let Swap::Alice(db_state) = db.get_state(swap_id)? {
db_state
} else {
bail!("Swap {} is not sell xmr.", swap_id)
};
let alice_state = AliceState::try_from(db_swap.clone())?;
let (bitcoin_wallet, monero_wallet) = setup_wallets( let (bitcoin_wallet, monero_wallet) = setup_wallets(
bitcoind_url, bitcoind_url,
bitcoin_wallet_name.as_str(), bitcoin_wallet_name.as_str(),
@ -192,7 +196,7 @@ async fn main() -> Result<()> {
.await?; .await?;
alice_swap( alice_swap(
swap_id, swap_id,
alice_state, db_state.into(),
listen_addr, listen_addr,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
@ -209,9 +213,11 @@ async fn main() -> Result<()> {
alice_peer_id, alice_peer_id,
alice_addr, alice_addr,
}) => { }) => {
let db_swap = db.get_state(swap_id)?; let db_state = if let Swap::Bob(db_state) = db.get_state(swap_id)? {
db_state
let bob_state = BobState::try_from(db_swap)?; } else {
bail!("Swap {} is not buy xmr.", swap_id)
};
let (bitcoin_wallet, monero_wallet) = setup_wallets( let (bitcoin_wallet, monero_wallet) = setup_wallets(
bitcoind_url, bitcoind_url,
@ -222,7 +228,7 @@ async fn main() -> Result<()> {
.await?; .await?;
bob_swap( bob_swap(
swap_id, swap_id,
bob_state, db_state.into(),
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
db, db,

View File

@ -1,20 +1,20 @@
use crate::{ use crate::{
bob::event_loop::EventLoopHandle, bob::event_loop::EventLoopHandle,
state, state,
state::{Bob, Swap}, state::{Bob, BobEndState},
storage::Database, storage::Database,
SwapAmounts, SwapAmounts,
}; };
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use async_recursion::async_recursion; use async_recursion::async_recursion;
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
use std::{convert::TryFrom, fmt, sync::Arc}; use std::{fmt, sync::Arc};
use tokio::select; use tokio::select;
use tracing::info; use tracing::info;
use uuid::Uuid; use uuid::Uuid;
use xmr_btc::{ use xmr_btc::{
bob::{self, State2}, bob::{self, State2},
Epoch, ExpiredTimelocks,
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -28,11 +28,11 @@ pub enum BobState {
XmrLocked(bob::State4), XmrLocked(bob::State4),
EncSigSent(bob::State4), EncSigSent(bob::State4),
BtcRedeemed(bob::State5), BtcRedeemed(bob::State5),
T1Expired(bob::State4), CancelTimelockExpired(bob::State4),
Cancelled(bob::State4), BtcCancelled(bob::State4),
BtcRefunded(bob::State4), BtcRefunded(bob::State4),
XmrRedeemed, XmrRedeemed,
Punished, BtcPunished,
SafelyAborted, SafelyAborted,
} }
@ -41,16 +41,16 @@ impl fmt::Display for BobState {
match self { match self {
BobState::Started { .. } => write!(f, "started"), BobState::Started { .. } => write!(f, "started"),
BobState::Negotiated(..) => write!(f, "negotiated"), BobState::Negotiated(..) => write!(f, "negotiated"),
BobState::BtcLocked(..) => write!(f, "btc_locked"), BobState::BtcLocked(..) => write!(f, "btc is locked"),
BobState::XmrLocked(..) => write!(f, "xmr_locked"), BobState::XmrLocked(..) => write!(f, "xmr is locked"),
BobState::EncSigSent(..) => write!(f, "encsig_sent"), BobState::EncSigSent(..) => write!(f, "encrypted signature is sent"),
BobState::BtcRedeemed(..) => write!(f, "btc_redeemed"), BobState::BtcRedeemed(..) => write!(f, "btc is redeemed"),
BobState::T1Expired(..) => write!(f, "t1_expired"), BobState::CancelTimelockExpired(..) => write!(f, "cancel timelock is expired"),
BobState::Cancelled(..) => write!(f, "cancelled"), BobState::BtcCancelled(..) => write!(f, "btc is cancelled"),
BobState::BtcRefunded(..) => write!(f, "btc_refunded"), BobState::BtcRefunded(..) => write!(f, "btc is refunded"),
BobState::XmrRedeemed => write!(f, "xmr_redeemed"), BobState::XmrRedeemed => write!(f, "xmr is redeemed"),
BobState::Punished => write!(f, "punished"), BobState::BtcPunished => write!(f, "btc is punished"),
BobState::SafelyAborted => write!(f, "safely_aborted"), BobState::SafelyAborted => write!(f, "safely aborted"),
} }
} }
} }
@ -58,44 +58,39 @@ impl fmt::Display for BobState {
impl From<BobState> for state::Bob { impl From<BobState> for state::Bob {
fn from(bob_state: BobState) -> Self { fn from(bob_state: BobState) -> Self {
match bob_state { match bob_state {
BobState::Started { .. } => { BobState::Started { state0, amounts } => Bob::Started { state0, amounts },
// TODO: Do we want to resume just started swaps
unimplemented!("Cannot save a swap that has just started")
}
BobState::Negotiated(state2) => Bob::Negotiated { state2 }, BobState::Negotiated(state2) => Bob::Negotiated { state2 },
BobState::BtcLocked(state3) => Bob::BtcLocked { state3 }, BobState::BtcLocked(state3) => Bob::BtcLocked { state3 },
BobState::XmrLocked(state4) => Bob::XmrLocked { state4 }, BobState::XmrLocked(state4) => Bob::XmrLocked { state4 },
BobState::EncSigSent(state4) => Bob::EncSigSent { state4 }, BobState::EncSigSent(state4) => Bob::EncSigSent { state4 },
BobState::BtcRedeemed(state5) => Bob::BtcRedeemed(state5), BobState::BtcRedeemed(state5) => Bob::BtcRedeemed(state5),
BobState::T1Expired(state4) => Bob::T1Expired(state4), BobState::CancelTimelockExpired(state4) => Bob::CancelTimelockExpired(state4),
BobState::Cancelled(state4) => Bob::BtcCancelled(state4), BobState::BtcCancelled(state4) => Bob::BtcCancelled(state4),
BobState::BtcRefunded(_) BobState::BtcRefunded(state4) => Bob::Done(BobEndState::BtcRefunded(Box::new(state4))),
| BobState::XmrRedeemed BobState::XmrRedeemed => Bob::Done(BobEndState::XmrRedeemed),
| BobState::Punished BobState::BtcPunished => Bob::Done(BobEndState::BtcPunished),
| BobState::SafelyAborted => Bob::SwapComplete, BobState::SafelyAborted => Bob::Done(BobEndState::SafelyAborted),
} }
} }
} }
impl TryFrom<state::Swap> for BobState { impl From<state::Bob> for BobState {
type Error = anyhow::Error; fn from(db_state: state::Bob) -> Self {
match db_state {
fn try_from(db_state: state::Swap) -> Result<Self, Self::Error> { Bob::Started { state0, amounts } => BobState::Started { state0, amounts },
if let Swap::Bob(state) = db_state {
let bob_State = match state {
Bob::Negotiated { state2 } => BobState::Negotiated(state2), Bob::Negotiated { state2 } => BobState::Negotiated(state2),
Bob::BtcLocked { state3 } => BobState::BtcLocked(state3), Bob::BtcLocked { state3 } => BobState::BtcLocked(state3),
Bob::XmrLocked { state4 } => BobState::XmrLocked(state4), Bob::XmrLocked { state4 } => BobState::XmrLocked(state4),
Bob::EncSigSent { state4 } => BobState::EncSigSent(state4), Bob::EncSigSent { state4 } => BobState::EncSigSent(state4),
Bob::BtcRedeemed(state5) => BobState::BtcRedeemed(state5), Bob::BtcRedeemed(state5) => BobState::BtcRedeemed(state5),
Bob::T1Expired(state4) => BobState::T1Expired(state4), Bob::CancelTimelockExpired(state4) => BobState::CancelTimelockExpired(state4),
Bob::BtcCancelled(state4) => BobState::Cancelled(state4), Bob::BtcCancelled(state4) => BobState::BtcCancelled(state4),
Bob::SwapComplete => BobState::SafelyAborted, Bob::Done(end_state) => match end_state {
}; BobEndState::SafelyAborted => BobState::SafelyAborted,
BobEndState::XmrRedeemed => BobState::XmrRedeemed,
Ok(bob_State) BobEndState::BtcRefunded(state4) => BobState::BtcRefunded(*state4),
} else { BobEndState::BtcPunished => BobState::BtcPunished,
bail!("Bob swap state expected.") },
} }
} }
} }
@ -132,7 +127,7 @@ pub fn is_complete(state: &BobState) -> bool {
state, state,
BobState::BtcRefunded(..) BobState::BtcRefunded(..)
| BobState::XmrRedeemed | BobState::XmrRedeemed
| BobState::Punished | BobState::BtcPunished
| BobState::SafelyAborted | BobState::SafelyAborted
) )
} }
@ -221,41 +216,43 @@ where
.await .await
} }
// Bob has locked Btc // Bob has locked Btc
// Watch for Alice to Lock Xmr or for t1 to elapse // Watch for Alice to Lock Xmr or for cancel timelock to elapse
BobState::BtcLocked(state3) => { BobState::BtcLocked(state3) => {
let state = if let Epoch::T0 = state3.current_epoch(bitcoin_wallet.as_ref()).await? let state = if let ExpiredTimelocks::None =
state3.current_epoch(bitcoin_wallet.as_ref()).await?
{ {
event_loop_handle.dial().await?; event_loop_handle.dial().await?;
let msg2_watcher = event_loop_handle.recv_message2(); let msg2_watcher = event_loop_handle.recv_message2();
let t1_timeout = state3.wait_for_t1(bitcoin_wallet.as_ref()); let cancel_timelock_expires =
state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref());
select! { select! {
msg2 = msg2_watcher => { msg2 = msg2_watcher => {
let xmr_lock_watcher = state3.clone() let xmr_lock_watcher = state3.clone()
.watch_for_lock_xmr(monero_wallet.as_ref(), msg2?); .watch_for_lock_xmr(monero_wallet.as_ref(), msg2?);
let t1_timeout = state3.wait_for_t1(bitcoin_wallet.as_ref()); let cancel_timelock_expires = state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref());
select! { select! {
state4 = xmr_lock_watcher => { state4 = xmr_lock_watcher => {
BobState::XmrLocked(state4?) BobState::XmrLocked(state4?)
}, },
_ = t1_timeout => { _ = cancel_timelock_expires => {
let state4 = state3.t1_expired(); let state4 = state3.state4();
BobState::T1Expired(state4) BobState::CancelTimelockExpired(state4)
} }
} }
}, },
_ = t1_timeout => { _ = cancel_timelock_expires => {
let state4 = state3.t1_expired(); let state4 = state3.state4();
BobState::T1Expired(state4) BobState::CancelTimelockExpired(state4)
} }
} }
} else { } else {
let state4 = state3.t1_expired(); let state4 = state3.state4();
BobState::T1Expired(state4) BobState::CancelTimelockExpired(state4)
}; };
let db_state = state.clone().into(); let db_state = state.clone().into();
db.insert_latest_state(swap_id, state::Swap::Bob(db_state)) db.insert_latest_state(swap_id, state::Swap::Bob(db_state))
@ -273,7 +270,9 @@ where
.await .await
} }
BobState::XmrLocked(state) => { BobState::XmrLocked(state) => {
let state = if let Epoch::T0 = state.current_epoch(bitcoin_wallet.as_ref()).await? { let state = if let ExpiredTimelocks::None =
state.expired_timelock(bitcoin_wallet.as_ref()).await?
{
event_loop_handle.dial().await?; event_loop_handle.dial().await?;
// Alice has locked Xmr // Alice has locked Xmr
// Bob sends Alice his key // Bob sends Alice his key
@ -283,18 +282,19 @@ where
// TODO(Franck): Refund if message cannot be sent. // TODO(Franck): Refund if message cannot be sent.
let enc_sig_sent_watcher = event_loop_handle.send_message3(tx_redeem_encsig); let enc_sig_sent_watcher = event_loop_handle.send_message3(tx_redeem_encsig);
let bitcoin_wallet = bitcoin_wallet.clone(); let bitcoin_wallet = bitcoin_wallet.clone();
let t1_timeout = state4_clone.wait_for_t1(bitcoin_wallet.as_ref()); let cancel_timelock_expires =
state4_clone.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref());
select! { select! {
_ = enc_sig_sent_watcher => { _ = enc_sig_sent_watcher => {
BobState::EncSigSent(state) BobState::EncSigSent(state)
}, },
_ = t1_timeout => { _ = cancel_timelock_expires => {
BobState::T1Expired(state) BobState::CancelTimelockExpired(state)
} }
} }
} else { } else {
BobState::T1Expired(state) BobState::CancelTimelockExpired(state)
}; };
let db_state = state.clone().into(); let db_state = state.clone().into();
db.insert_latest_state(swap_id, state::Swap::Bob(db_state)) db.insert_latest_state(swap_id, state::Swap::Bob(db_state))
@ -312,21 +312,24 @@ where
.await .await
} }
BobState::EncSigSent(state) => { BobState::EncSigSent(state) => {
let state = if let Epoch::T0 = state.current_epoch(bitcoin_wallet.as_ref()).await? { let state = if let ExpiredTimelocks::None =
state.expired_timelock(bitcoin_wallet.as_ref()).await?
{
let state_clone = state.clone(); let state_clone = state.clone();
let redeem_watcher = state_clone.watch_for_redeem_btc(bitcoin_wallet.as_ref()); let redeem_watcher = state_clone.watch_for_redeem_btc(bitcoin_wallet.as_ref());
let t1_timeout = state_clone.wait_for_t1(bitcoin_wallet.as_ref()); let cancel_timelock_expires =
state_clone.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref());
select! { select! {
state5 = redeem_watcher => { state5 = redeem_watcher => {
BobState::BtcRedeemed(state5?) BobState::BtcRedeemed(state5?)
}, },
_ = t1_timeout => { _ = cancel_timelock_expires => {
BobState::T1Expired(state) BobState::CancelTimelockExpired(state)
} }
} }
} else { } else {
BobState::T1Expired(state) BobState::CancelTimelockExpired(state)
}; };
let db_state = state.clone().into(); let db_state = state.clone().into();
@ -364,7 +367,7 @@ where
) )
.await .await
} }
BobState::T1Expired(state4) => { BobState::CancelTimelockExpired(state4) => {
if state4 if state4
.check_for_tx_cancel(bitcoin_wallet.as_ref()) .check_for_tx_cancel(bitcoin_wallet.as_ref())
.await .await
@ -373,7 +376,7 @@ where
state4.submit_tx_cancel(bitcoin_wallet.as_ref()).await?; state4.submit_tx_cancel(bitcoin_wallet.as_ref()).await?;
} }
let state = BobState::Cancelled(state4); let state = BobState::BtcCancelled(state4);
db.insert_latest_state(swap_id, state::Swap::Bob(state.clone().into())) db.insert_latest_state(swap_id, state::Swap::Bob(state.clone().into()))
.await?; .await?;
@ -389,16 +392,17 @@ where
) )
.await .await
} }
BobState::Cancelled(state) => { BobState::BtcCancelled(state) => {
// TODO
// Bob has cancelled the swap // Bob has cancelled the swap
let state = match state.current_epoch(bitcoin_wallet.as_ref()).await? { let state = match state.expired_timelock(bitcoin_wallet.as_ref()).await? {
Epoch::T0 => panic!("Cancelled before t1??? Something is really wrong"), ExpiredTimelocks::None => {
Epoch::T1 => { bail!("Internal error: canceled state reached before cancel timelock was expired");
}
ExpiredTimelocks::Cancel => {
state.refund_btc(bitcoin_wallet.as_ref()).await?; state.refund_btc(bitcoin_wallet.as_ref()).await?;
BobState::BtcRefunded(state) BobState::BtcRefunded(state)
} }
Epoch::T2 => BobState::Punished, ExpiredTimelocks::Punish => BobState::BtcPunished,
}; };
let db_state = state.clone().into(); let db_state = state.clone().into();
@ -417,7 +421,7 @@ where
.await .await
} }
BobState::BtcRefunded(state4) => Ok(BobState::BtcRefunded(state4)), BobState::BtcRefunded(state4) => Ok(BobState::BtcRefunded(state4)),
BobState::Punished => Ok(BobState::Punished), BobState::BtcPunished => Ok(BobState::BtcPunished),
BobState::SafelyAborted => Ok(BobState::SafelyAborted), BobState::SafelyAborted => Ok(BobState::SafelyAborted),
BobState::XmrRedeemed => Ok(BobState::XmrRedeemed), BobState::XmrRedeemed => Ok(BobState::XmrRedeemed),
} }

View File

@ -1,3 +1,4 @@
use crate::SwapAmounts;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Display; use std::fmt::Display;
use xmr_btc::{alice, bitcoin::EncryptedSignature, bob, monero, serde::monero_private_key}; use xmr_btc::{alice, bitcoin::EncryptedSignature, bob, monero, serde::monero_private_key};
@ -12,40 +13,66 @@ pub enum Swap {
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum Alice { pub enum Alice {
Started {
amounts: SwapAmounts,
state0: alice::State0,
},
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 EncSigLearned {
BtcRedeemable {
state: alice::State3,
redeem_tx: bitcoin::Transaction,
},
EncSignLearned {
state: alice::State3, state: alice::State3,
encrypted_signature: EncryptedSignature, encrypted_signature: EncryptedSignature,
}, },
T1Expired(alice::State3), CancelTimelockExpired(alice::State3),
BtcCancelled(alice::State3), BtcCancelled(alice::State3),
BtcPunishable(alice::State3), BtcPunishable(alice::State3),
BtcRefunded { BtcRefunded {
state: alice::State3, state3: alice::State3,
#[serde(with = "monero_private_key")] #[serde(with = "monero_private_key")]
spend_key: monero::PrivateKey, spend_key: monero::PrivateKey,
view_key: monero::PrivateViewKey,
}, },
SwapComplete, Done(AliceEndState),
}
#[derive(Clone, strum::Display, Debug, Deserialize, Serialize, PartialEq)]
pub enum AliceEndState {
SafelyAborted,
BtcRedeemed,
XmrRefunded,
BtcPunished,
} }
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum Bob { pub enum Bob {
Negotiated { state2: bob::State2 }, Started {
BtcLocked { state3: bob::State3 }, state0: bob::State0,
XmrLocked { state4: bob::State4 }, amounts: SwapAmounts,
EncSigSent { state4: bob::State4 }, },
Negotiated {
state2: bob::State2,
},
BtcLocked {
state3: bob::State3,
},
XmrLocked {
state4: bob::State4,
},
EncSigSent {
state4: bob::State4,
},
BtcRedeemed(bob::State5), BtcRedeemed(bob::State5),
T1Expired(bob::State4), CancelTimelockExpired(bob::State4),
BtcCancelled(bob::State4), BtcCancelled(bob::State4),
SwapComplete, Done(BobEndState),
}
#[derive(Clone, strum::Display, Debug, Deserialize, Serialize, PartialEq)]
pub enum BobEndState {
SafelyAborted,
XmrRedeemed,
BtcRefunded(Box<bob::State4>),
BtcPunished,
} }
impl From<Alice> for Swap { impl From<Alice> for Swap {
@ -72,16 +99,16 @@ impl Display for Swap {
impl Display for Alice { impl Display for Alice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Alice::Negotiated(_) => f.write_str("Handshake complete"), Alice::Started { .. } => write!(f, "Started"),
Alice::Negotiated(_) => f.write_str("Negotiated"),
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::CancelTimelockExpired(_) => f.write_str("Cancel timelock is expired"),
Alice::T1Expired(_) => f.write_str("Timelock T1 expired"),
Alice::BtcCancelled(_) => f.write_str("Bitcoin cancel transaction published"), 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::Done(end_state) => write!(f, "Done: {}", end_state),
Alice::EncSignLearned { .. } => f.write_str("Encrypted signature learned"), Alice::EncSigLearned { .. } => f.write_str("Encrypted signature learned"),
} }
} }
} }
@ -89,13 +116,14 @@ impl Display for Alice {
impl Display for Bob { impl Display for Bob {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Bob::Negotiated { .. } => f.write_str("Handshake complete"), Bob::Started { .. } => write!(f, "Started"),
Bob::Negotiated { .. } => f.write_str("Negotiated"),
Bob::BtcLocked { .. } => f.write_str("Bitcoin locked"), Bob::BtcLocked { .. } => f.write_str("Bitcoin locked"),
Bob::XmrLocked { .. } => f.write_str("Monero locked"), Bob::XmrLocked { .. } => f.write_str("Monero locked"),
Bob::T1Expired(_) => f.write_str("Timelock T1 expired"), Bob::CancelTimelockExpired(_) => f.write_str("Cancel timelock is expired"),
Bob::BtcCancelled(_) => f.write_str("Bitcoin refundable"), Bob::BtcCancelled(_) => f.write_str("Bitcoin refundable"),
Bob::BtcRedeemed(_) => f.write_str("Monero redeemable"), Bob::BtcRedeemed(_) => f.write_str("Monero redeemable"),
Bob::SwapComplete => f.write_str("Swap complete"), Bob::Done(end_state) => write!(f, "Done: {}", end_state),
Bob::EncSigSent { .. } => f.write_str("Encrypted signature sent"), Bob::EncSigSent { .. } => f.write_str("Encrypted signature sent"),
} }
} }

View File

@ -81,7 +81,7 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::state::{Alice, Bob}; use crate::state::{Alice, AliceEndState, Bob, BobEndState};
use super::*; use super::*;
@ -90,13 +90,13 @@ mod tests {
let db_dir = tempfile::tempdir().unwrap(); let db_dir = tempfile::tempdir().unwrap();
let db = Database::open(db_dir.path()).unwrap(); let db = Database::open(db_dir.path()).unwrap();
let state_1 = Swap::Alice(Alice::SwapComplete); let state_1 = Swap::Alice(Alice::Done(AliceEndState::BtcRedeemed));
let swap_id_1 = Uuid::new_v4(); let swap_id_1 = Uuid::new_v4();
db.insert_latest_state(swap_id_1, state_1.clone()) db.insert_latest_state(swap_id_1, state_1.clone())
.await .await
.expect("Failed to save second state"); .expect("Failed to save second state");
let state_2 = Swap::Bob(Bob::SwapComplete); let state_2 = Swap::Bob(Bob::Done(BobEndState::XmrRedeemed));
let swap_id_2 = Uuid::new_v4(); let swap_id_2 = Uuid::new_v4();
db.insert_latest_state(swap_id_2, state_2.clone()) db.insert_latest_state(swap_id_2, state_2.clone())
.await .await
@ -119,7 +119,7 @@ mod tests {
let db_dir = tempfile::tempdir().unwrap(); let db_dir = tempfile::tempdir().unwrap();
let db = Database::open(db_dir.path()).unwrap(); let db = Database::open(db_dir.path()).unwrap();
let state = Swap::Alice(Alice::SwapComplete); let state = Swap::Alice(Alice::Done(AliceEndState::SafelyAborted));
let swap_id = Uuid::new_v4(); let swap_id = Uuid::new_v4();
db.insert_latest_state(swap_id, state.clone()) db.insert_latest_state(swap_id, state.clone())
@ -146,13 +146,13 @@ mod tests {
let db_dir = tempfile::tempdir().unwrap(); let db_dir = tempfile::tempdir().unwrap();
let db = Database::open(db_dir.path()).unwrap(); let db = Database::open(db_dir.path()).unwrap();
let state_1 = Swap::Alice(Alice::SwapComplete); let state_1 = Swap::Alice(Alice::Done(AliceEndState::BtcPunished));
let swap_id_1 = Uuid::new_v4(); let swap_id_1 = Uuid::new_v4();
db.insert_latest_state(swap_id_1, state_1.clone()) db.insert_latest_state(swap_id_1, state_1.clone())
.await .await
.expect("Failed to save second state"); .expect("Failed to save second state");
let state_2 = Swap::Bob(Bob::SwapComplete); let state_2 = Swap::Bob(Bob::Done(BobEndState::BtcPunished));
let swap_id_2 = Uuid::new_v4(); let swap_id_2 = Uuid::new_v4();
db.insert_latest_state(swap_id_2, state_2.clone()) db.insert_latest_state(swap_id_2, state_2.clone())
.await .await

View File

@ -2,7 +2,6 @@ use crate::testutils::{init_alice, init_bob};
use get_port::get_port; use get_port::get_port;
use libp2p::Multiaddr; use libp2p::Multiaddr;
use rand::rngs::OsRng; use rand::rngs::OsRng;
use std::convert::TryFrom;
use swap::{alice, alice::swap::AliceState, bitcoin, bob, storage::Database}; use swap::{alice, alice::swap::AliceState, bitcoin, bob, storage::Database};
use tempfile::tempdir; use tempfile::tempdir;
use testcontainers::clients::Cli; use testcontainers::clients::Cli;
@ -108,22 +107,22 @@ async fn given_alice_restarts_after_encsig_is_learned_resume_swap() {
.await .await
.unwrap(); .unwrap();
assert!(matches!(alice_state, AliceState::EncSignLearned {..})); assert!(matches!(alice_state, AliceState::EncSigLearned {..}));
let alice_db = Database::open(alice_db_datadir.path()).unwrap(); let alice_db = Database::open(alice_db_datadir.path()).unwrap();
let state_before_restart = alice_db.get_state(alice_swap_id).unwrap();
if let swap::state::Swap::Alice(state) = state_before_restart.clone() { let resume_state =
assert!(matches!(state, swap::state::Alice::EncSignLearned {..})); if let swap::state::Swap::Alice(state) = alice_db.get_state(alice_swap_id).unwrap() {
} assert!(matches!(state, swap::state::Alice::EncSigLearned {..}));
state.into()
} else {
unreachable!()
};
let (mut event_loop_after_restart, event_loop_handle_after_restart) = let (mut event_loop_after_restart, event_loop_handle_after_restart) =
testutils::init_alice_event_loop(alice_multiaddr); testutils::init_alice_event_loop(alice_multiaddr);
tokio::spawn(async move { event_loop_after_restart.run().await }); tokio::spawn(async move { event_loop_after_restart.run().await });
let db_swap = alice_db.get_state(alice_swap_id).unwrap();
let resume_state = AliceState::try_from(db_swap).unwrap();
let alice_state = alice::swap::swap( let alice_state = alice::swap::swap(
resume_state, resume_state,
event_loop_handle_after_restart, event_loop_handle_after_restart,

View File

@ -2,7 +2,6 @@ use crate::testutils::{init_alice, init_bob};
use get_port::get_port; use get_port::get_port;
use libp2p::Multiaddr; use libp2p::Multiaddr;
use rand::rngs::OsRng; use rand::rngs::OsRng;
use std::convert::TryFrom;
use swap::{alice, bitcoin, bob, bob::swap::BobState, storage::Database}; use swap::{alice, bitcoin, bob, bob::swap::BobState, storage::Database};
use tempfile::tempdir; use tempfile::tempdir;
use testcontainers::clients::Cli; use testcontainers::clients::Cli;
@ -114,19 +113,19 @@ async fn given_bob_restarts_after_encsig_is_sent_resume_swap() {
assert!(matches!(bob_state, BobState::EncSigSent {..})); assert!(matches!(bob_state, BobState::EncSigSent {..}));
let bob_db = Database::open(bob_db_datadir.path()).unwrap(); let bob_db = Database::open(bob_db_datadir.path()).unwrap();
let state_before_restart = bob_db.get_state(bob_swap_id).unwrap();
if let swap::state::Swap::Bob(state) = state_before_restart.clone() { let resume_state = if let swap::state::Swap::Bob(state) = bob_db.get_state(bob_swap_id).unwrap()
{
assert!(matches!(state, swap::state::Bob::EncSigSent {..})); assert!(matches!(state, swap::state::Bob::EncSigSent {..}));
} state.into()
} else {
unreachable!()
};
let (event_loop_after_restart, event_loop_handle_after_restart) = let (event_loop_after_restart, event_loop_handle_after_restart) =
testutils::init_bob_event_loop(alice_peer_id, alice_multiaddr); testutils::init_bob_event_loop(alice_peer_id, alice_multiaddr);
tokio::spawn(event_loop_after_restart.run()); tokio::spawn(event_loop_after_restart.run());
let db_swap = bob_db.get_state(bob_swap_id).unwrap();
let resume_state = BobState::try_from(db_swap).unwrap();
let bob_state = bob::swap::swap( let bob_state = bob::swap::swap(
resume_state, resume_state,
event_loop_handle_after_restart, event_loop_handle_after_restart,

View File

@ -115,7 +115,7 @@ async fn alice_punishes_if_bob_never_acts_after_fund() {
Either::Right(_) => panic!("Bob event loop should not terminate."), Either::Right(_) => panic!("Bob event loop should not terminate."),
}; };
assert!(matches!(alice_state, AliceState::Punished)); assert!(matches!(alice_state, AliceState::BtcPunished));
let bob_state3 = if let BobState::BtcLocked(state3, ..) = bob_state { let bob_state3 = if let BobState::BtcLocked(state3, ..) = bob_state {
state3 state3
} else { } else {

View File

@ -88,7 +88,7 @@ pub async fn init_alice_state(
v_a, v_a,
amounts.btc, amounts.btc,
amounts.xmr, amounts.xmr,
config.bitcoin_refund_timelock, config.bitcoin_cancel_timelock,
config.bitcoin_punish_timelock, config.bitcoin_punish_timelock,
redeem_address, redeem_address,
punish_address, punish_address,
@ -170,7 +170,7 @@ pub async fn init_bob_state(
&mut OsRng, &mut OsRng,
btc_to_swap, btc_to_swap,
xmr_to_swap, xmr_to_swap,
config.bitcoin_refund_timelock, config.bitcoin_cancel_timelock,
config.bitcoin_punish_timelock, config.bitcoin_punish_timelock,
refund_address, refund_address,
); );

View File

@ -4,7 +4,7 @@ use crate::{
bob, monero, bob, monero,
monero::{CreateWalletForOutput, Transfer}, monero::{CreateWalletForOutput, Transfer},
transport::{ReceiveMessage, SendMessage}, transport::{ReceiveMessage, SendMessage},
Epoch, ExpiredTimelocks,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use async_trait::async_trait; use async_trait::async_trait;
@ -28,7 +28,9 @@ use std::{
use tokio::{sync::Mutex, time::timeout}; use tokio::{sync::Mutex, time::timeout};
use tracing::{error, info}; use tracing::{error, info};
pub mod message; pub mod message;
use crate::bitcoin::{current_epoch, wait_for_t1, BlockHeight, TransactionBlockHeight}; use crate::bitcoin::{
current_epoch, wait_for_cancel_timelock_to_expire, BlockHeight, TransactionBlockHeight,
};
pub use message::{Message, Message0, Message1, Message2}; pub use message::{Message, Message0, Message1, Message2};
#[derive(Debug)] #[derive(Debug)]
@ -74,7 +76,7 @@ pub fn action_generator<N, B>(
S_b_bitcoin, S_b_bitcoin,
v, v,
xmr, xmr,
refund_timelock, cancel_timelock,
punish_timelock, punish_timelock,
refund_address, refund_address,
redeem_address, redeem_address,
@ -138,7 +140,7 @@ where
.await; .await;
let poll_until_btc_has_expired = poll_until_block_height_is_gte( let poll_until_btc_has_expired = poll_until_block_height_is_gte(
bitcoin_client.as_ref(), bitcoin_client.as_ref(),
tx_lock_height + refund_timelock, tx_lock_height + cancel_timelock,
) )
.shared(); .shared();
pin_mut!(poll_until_btc_has_expired); pin_mut!(poll_until_btc_has_expired);
@ -221,7 +223,7 @@ where
if let Err(SwapFailed::AfterXmrLock(Reason::BtcExpired)) = swap_result { if let Err(SwapFailed::AfterXmrLock(Reason::BtcExpired)) = swap_result {
let refund_result: Result<(), RefundFailed> = async { let refund_result: Result<(), RefundFailed> = async {
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, refund_timelock, a.public(), B); let tx_cancel = bitcoin::TxCancel::new(&tx_lock, cancel_timelock, a.public(), B);
let signed_tx_cancel = { let signed_tx_cancel = {
let sig_a = a.sign(tx_cancel.digest()); let sig_a = a.sign(tx_cancel.digest());
let sig_b = tx_cancel_sig_bob.clone(); let sig_b = tx_cancel_sig_bob.clone();
@ -292,7 +294,7 @@ where
// with the refund on Monero. Doing so may be too verbose with the current, // with the refund on Monero. Doing so may be too verbose with the current,
// linear approach. A different design may be required // linear approach. A different design may be required
if let Err(RefundFailed::BtcPunishable) = refund_result { if let Err(RefundFailed::BtcPunishable) = refund_result {
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, refund_timelock, a.public(), B); let tx_cancel = bitcoin::TxCancel::new(&tx_lock, cancel_timelock, a.public(), B);
let tx_punish = let tx_punish =
bitcoin::TxPunish::new(&tx_cancel, &punish_address, punish_timelock); bitcoin::TxPunish::new(&tx_cancel, &punish_address, punish_timelock);
let tx_punish_txid = tx_punish.txid(); let tx_punish_txid = tx_punish.txid();
@ -410,7 +412,7 @@ impl State {
rng: &mut R, rng: &mut R,
btc: bitcoin::Amount, btc: bitcoin::Amount,
xmr: monero::Amount, xmr: monero::Amount,
refund_timelock: u32, cancel_timelock: u32,
punish_timelock: u32, punish_timelock: u32,
redeem_address: bitcoin::Address, redeem_address: bitcoin::Address,
punish_address: bitcoin::Address, punish_address: bitcoin::Address,
@ -425,7 +427,7 @@ impl State {
v_a, v_a,
btc, btc,
xmr, xmr,
refund_timelock, cancel_timelock,
punish_timelock, punish_timelock,
redeem_address, redeem_address,
punish_address, punish_address,
@ -433,7 +435,7 @@ impl State {
} }
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct State0 { pub struct State0 {
pub a: bitcoin::SecretKey, pub a: bitcoin::SecretKey,
pub s_a: cross_curve_dleq::Scalar, pub s_a: cross_curve_dleq::Scalar,
@ -441,7 +443,7 @@ pub struct State0 {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
pub btc: bitcoin::Amount, pub btc: bitcoin::Amount,
pub xmr: monero::Amount, pub xmr: monero::Amount,
pub refund_timelock: u32, pub cancel_timelock: u32,
pub punish_timelock: u32, pub punish_timelock: u32,
pub redeem_address: bitcoin::Address, pub redeem_address: bitcoin::Address,
pub punish_address: bitcoin::Address, pub punish_address: bitcoin::Address,
@ -455,7 +457,7 @@ impl State0 {
v_a: monero::PrivateViewKey, v_a: monero::PrivateViewKey,
btc: bitcoin::Amount, btc: bitcoin::Amount,
xmr: monero::Amount, xmr: monero::Amount,
refund_timelock: u32, cancel_timelock: u32,
punish_timelock: u32, punish_timelock: u32,
redeem_address: bitcoin::Address, redeem_address: bitcoin::Address,
punish_address: bitcoin::Address, punish_address: bitcoin::Address,
@ -468,7 +470,7 @@ impl State0 {
punish_address, punish_address,
btc, btc,
xmr, xmr,
refund_timelock, cancel_timelock,
punish_timelock, punish_timelock,
} }
} }
@ -510,7 +512,7 @@ impl State0 {
v, v,
btc: self.btc, btc: self.btc,
xmr: self.xmr, xmr: self.xmr,
refund_timelock: self.refund_timelock, cancel_timelock: self.cancel_timelock,
punish_timelock: self.punish_timelock, punish_timelock: self.punish_timelock,
refund_address: msg.refund_address, refund_address: msg.refund_address,
redeem_address: self.redeem_address, redeem_address: self.redeem_address,
@ -530,7 +532,7 @@ pub struct State1 {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
btc: bitcoin::Amount, btc: bitcoin::Amount,
xmr: monero::Amount, xmr: monero::Amount,
refund_timelock: u32, cancel_timelock: u32,
punish_timelock: u32, punish_timelock: u32,
refund_address: bitcoin::Address, refund_address: bitcoin::Address,
redeem_address: bitcoin::Address, redeem_address: bitcoin::Address,
@ -548,7 +550,7 @@ impl State1 {
v: self.v, v: self.v,
btc: self.btc, btc: self.btc,
xmr: self.xmr, xmr: self.xmr,
refund_timelock: self.refund_timelock, cancel_timelock: self.cancel_timelock,
punish_timelock: self.punish_timelock, punish_timelock: self.punish_timelock,
refund_address: self.refund_address, refund_address: self.refund_address,
redeem_address: self.redeem_address, redeem_address: self.redeem_address,
@ -569,7 +571,7 @@ pub struct State2 {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
btc: bitcoin::Amount, btc: bitcoin::Amount,
xmr: monero::Amount, xmr: monero::Amount,
refund_timelock: u32, cancel_timelock: u32,
punish_timelock: u32, punish_timelock: u32,
refund_address: bitcoin::Address, refund_address: bitcoin::Address,
redeem_address: bitcoin::Address, redeem_address: bitcoin::Address,
@ -580,7 +582,7 @@ pub struct State2 {
impl State2 { impl State2 {
pub fn next_message(&self) -> Message1 { pub fn next_message(&self) -> Message1 {
let tx_cancel = let tx_cancel =
bitcoin::TxCancel::new(&self.tx_lock, self.refund_timelock, self.a.public(), self.B); bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.a.public(), self.B);
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address); let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address);
// Alice encsigns the refund transaction(bitcoin) digest with Bob's monero // Alice encsigns the refund transaction(bitcoin) digest with Bob's monero
@ -599,7 +601,7 @@ impl State2 {
pub fn receive(self, msg: bob::Message2) -> Result<State3> { pub fn receive(self, msg: bob::Message2) -> Result<State3> {
let tx_cancel = let tx_cancel =
bitcoin::TxCancel::new(&self.tx_lock, self.refund_timelock, self.a.public(), self.B); bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.a.public(), self.B);
bitcoin::verify_sig(&self.B, &tx_cancel.digest(), &msg.tx_cancel_sig)?; bitcoin::verify_sig(&self.B, &tx_cancel.digest(), &msg.tx_cancel_sig)?;
let tx_punish = let tx_punish =
bitcoin::TxPunish::new(&tx_cancel, &self.punish_address, self.punish_timelock); bitcoin::TxPunish::new(&tx_cancel, &self.punish_address, self.punish_timelock);
@ -615,7 +617,7 @@ impl State2 {
// TODO(Franck): Review if these amounts are actually needed // TODO(Franck): Review if these amounts are actually needed
btc: self.btc, btc: self.btc,
xmr: self.xmr, xmr: self.xmr,
refund_timelock: self.refund_timelock, cancel_timelock: self.cancel_timelock,
punish_timelock: self.punish_timelock, punish_timelock: self.punish_timelock,
refund_address: self.refund_address, refund_address: self.refund_address,
redeem_address: self.redeem_address, redeem_address: self.redeem_address,
@ -638,7 +640,7 @@ pub struct State3 {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
pub btc: bitcoin::Amount, pub btc: bitcoin::Amount,
pub xmr: monero::Amount, pub xmr: monero::Amount,
pub refund_timelock: u32, pub cancel_timelock: u32,
pub punish_timelock: u32, pub punish_timelock: u32,
pub refund_address: bitcoin::Address, pub refund_address: bitcoin::Address,
pub redeem_address: bitcoin::Address, pub redeem_address: bitcoin::Address,
@ -669,7 +671,7 @@ impl State3 {
v: self.v, v: self.v,
btc: self.btc, btc: self.btc,
xmr: self.xmr, xmr: self.xmr,
refund_timelock: self.refund_timelock, cancel_timelock: self.cancel_timelock,
punish_timelock: self.punish_timelock, punish_timelock: self.punish_timelock,
refund_address: self.refund_address, refund_address: self.refund_address,
redeem_address: self.redeem_address, redeem_address: self.redeem_address,
@ -680,20 +682,25 @@ impl State3 {
}) })
} }
pub async fn wait_for_t1<W>(&self, bitcoin_wallet: &W) -> Result<()> pub async fn wait_for_cancel_timelock_to_expire<W>(&self, bitcoin_wallet: &W) -> Result<()>
where where
W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight,
{ {
wait_for_t1(bitcoin_wallet, self.refund_timelock, self.tx_lock.txid()).await wait_for_cancel_timelock_to_expire(
bitcoin_wallet,
self.cancel_timelock,
self.tx_lock.txid(),
)
.await
} }
pub async fn current_epoch<W>(&self, bitcoin_wallet: &W) -> Result<Epoch> pub async fn expired_timelocks<W>(&self, bitcoin_wallet: &W) -> Result<ExpiredTimelocks>
where where
W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight,
{ {
current_epoch( current_epoch(
bitcoin_wallet, bitcoin_wallet,
self.refund_timelock, self.cancel_timelock,
self.punish_timelock, self.punish_timelock,
self.tx_lock.txid(), self.tx_lock.txid(),
) )
@ -712,7 +719,7 @@ pub struct State4 {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
btc: bitcoin::Amount, btc: bitcoin::Amount,
xmr: monero::Amount, xmr: monero::Amount,
refund_timelock: u32, cancel_timelock: u32,
punish_timelock: u32, punish_timelock: u32,
refund_address: bitcoin::Address, refund_address: bitcoin::Address,
redeem_address: bitcoin::Address, redeem_address: bitcoin::Address,
@ -745,7 +752,7 @@ impl State4 {
v: self.v, v: self.v,
btc: self.btc, btc: self.btc,
xmr: self.xmr, xmr: self.xmr,
refund_timelock: self.refund_timelock, cancel_timelock: self.cancel_timelock,
punish_timelock: self.punish_timelock, punish_timelock: self.punish_timelock,
refund_address: self.refund_address, refund_address: self.refund_address,
redeem_address: self.redeem_address, redeem_address: self.redeem_address,
@ -763,7 +770,7 @@ impl State4 {
bitcoin_wallet: &W, bitcoin_wallet: &W,
) -> Result<()> { ) -> Result<()> {
let tx_cancel = let tx_cancel =
bitcoin::TxCancel::new(&self.tx_lock, self.refund_timelock, self.a.public(), self.B); bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.a.public(), self.B);
let tx_punish = let tx_punish =
bitcoin::TxPunish::new(&tx_cancel, &self.punish_address, self.punish_timelock); bitcoin::TxPunish::new(&tx_cancel, &self.punish_address, self.punish_timelock);
@ -809,7 +816,7 @@ pub struct State5 {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
btc: bitcoin::Amount, btc: bitcoin::Amount,
xmr: monero::Amount, xmr: monero::Amount,
refund_timelock: u32, cancel_timelock: u32,
punish_timelock: u32, punish_timelock: u32,
refund_address: bitcoin::Address, refund_address: bitcoin::Address,
redeem_address: bitcoin::Address, redeem_address: bitcoin::Address,
@ -840,7 +847,7 @@ impl State5 {
v: self.v, v: self.v,
btc: self.btc, btc: self.btc,
xmr: self.xmr, xmr: self.xmr,
refund_timelock: self.refund_timelock, cancel_timelock: self.cancel_timelock,
punish_timelock: self.punish_timelock, punish_timelock: self.punish_timelock,
refund_address: self.refund_address, refund_address: self.refund_address,
redeem_address: self.redeem_address, redeem_address: self.redeem_address,
@ -859,7 +866,7 @@ impl State5 {
M: CreateWalletForOutput, M: CreateWalletForOutput,
{ {
let tx_cancel = let tx_cancel =
bitcoin::TxCancel::new(&self.tx_lock, self.refund_timelock, self.a.public(), self.B); bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.a.public(), self.B);
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address); let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address);
@ -898,7 +905,7 @@ pub struct State6 {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
btc: bitcoin::Amount, btc: bitcoin::Amount,
xmr: monero::Amount, xmr: monero::Amount,
refund_timelock: u32, cancel_timelock: u32,
punish_timelock: u32, punish_timelock: u32,
refund_address: bitcoin::Address, refund_address: bitcoin::Address,
redeem_address: bitcoin::Address, redeem_address: bitcoin::Address,

View File

@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
use sha2::Sha256; use sha2::Sha256;
use std::str::FromStr; use std::str::FromStr;
use crate::Epoch; use crate::ExpiredTimelocks;
pub use bitcoin::{util::psbt::PartiallySignedTransaction, *}; pub use bitcoin::{util::psbt::PartiallySignedTransaction, *};
pub use ecdsa_fun::{adaptor::EncryptedSignature, fun::Scalar, Signature}; pub use ecdsa_fun::{adaptor::EncryptedSignature, fun::Scalar, Signature};
pub use transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxRefund}; pub use transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxRefund};
@ -247,28 +247,31 @@ where
pub async fn current_epoch<W>( pub async fn current_epoch<W>(
bitcoin_wallet: &W, bitcoin_wallet: &W,
refund_timelock: u32, cancel_timelock: u32,
punish_timelock: u32, punish_timelock: u32,
lock_tx_id: ::bitcoin::Txid, lock_tx_id: ::bitcoin::Txid,
) -> anyhow::Result<Epoch> ) -> anyhow::Result<ExpiredTimelocks>
where where
W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight,
{ {
let current_block_height = bitcoin_wallet.block_height().await; let current_block_height = bitcoin_wallet.block_height().await;
let t0 = bitcoin_wallet.transaction_block_height(lock_tx_id).await; let lock_tx_height = bitcoin_wallet.transaction_block_height(lock_tx_id).await;
let t1 = t0 + refund_timelock; let cancel_timelock_height = lock_tx_height + cancel_timelock;
let t2 = t1 + punish_timelock; let punish_timelock_height = cancel_timelock_height + punish_timelock;
match (current_block_height < t1, current_block_height < t2) { match (
(true, _) => Ok(Epoch::T0), current_block_height < cancel_timelock_height,
(false, true) => Ok(Epoch::T1), current_block_height < punish_timelock_height,
(false, false) => Ok(Epoch::T2), ) {
(true, _) => Ok(ExpiredTimelocks::None),
(false, true) => Ok(ExpiredTimelocks::Cancel),
(false, false) => Ok(ExpiredTimelocks::Punish),
} }
} }
pub async fn wait_for_t1<W>( pub async fn wait_for_cancel_timelock_to_expire<W>(
bitcoin_wallet: &W, bitcoin_wallet: &W,
refund_timelock: u32, cancel_timelock: u32,
lock_tx_id: ::bitcoin::Txid, lock_tx_id: ::bitcoin::Txid,
) -> Result<()> ) -> Result<()>
where where
@ -276,8 +279,6 @@ where
{ {
let tx_lock_height = bitcoin_wallet.transaction_block_height(lock_tx_id).await; let tx_lock_height = bitcoin_wallet.transaction_block_height(lock_tx_id).await;
let t1_timeout = poll_until_block_height_is_gte(bitcoin_wallet, tx_lock_height + cancel_timelock).await;
poll_until_block_height_is_gte(bitcoin_wallet, tx_lock_height + refund_timelock);
t1_timeout.await;
Ok(()) Ok(())
} }

View File

@ -7,7 +7,7 @@ use crate::{
monero, monero,
serde::monero_private_key, serde::monero_private_key,
transport::{ReceiveMessage, SendMessage}, transport::{ReceiveMessage, SendMessage},
Epoch, ExpiredTimelocks,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use async_trait::async_trait; use async_trait::async_trait;
@ -35,7 +35,8 @@ use tracing::error;
pub mod message; pub mod message;
use crate::{ use crate::{
bitcoin::{ bitcoin::{
current_epoch, wait_for_t1, BlockHeight, GetRawTransaction, Network, TransactionBlockHeight, current_epoch, wait_for_cancel_timelock_to_expire, BlockHeight, GetRawTransaction, Network,
TransactionBlockHeight,
}, },
monero::{CreateWalletForOutput, WatchForTransfer}, monero::{CreateWalletForOutput, WatchForTransfer},
}; };
@ -81,7 +82,7 @@ pub fn action_generator<N, M, B>(
S_a_bitcoin, S_a_bitcoin,
v, v,
xmr, xmr,
refund_timelock, cancel_timelock,
redeem_address, redeem_address,
refund_address, refund_address,
tx_lock, tx_lock,
@ -142,7 +143,7 @@ where
.await; .await;
let poll_until_btc_has_expired = poll_until_block_height_is_gte( let poll_until_btc_has_expired = poll_until_block_height_is_gte(
bitcoin_client.as_ref(), bitcoin_client.as_ref(),
tx_lock_height + refund_timelock, tx_lock_height + cancel_timelock,
) )
.shared(); .shared();
pin_mut!(poll_until_btc_has_expired); pin_mut!(poll_until_btc_has_expired);
@ -224,7 +225,7 @@ where
} }
if let Err(SwapFailed::AfterBtcLock(_)) = swap_result { if let Err(SwapFailed::AfterBtcLock(_)) = swap_result {
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, refund_timelock, A, b.public()); let tx_cancel = bitcoin::TxCancel::new(&tx_lock, cancel_timelock, A, b.public());
let tx_cancel_txid = tx_cancel.txid(); let tx_cancel_txid = tx_cancel.txid();
let signed_tx_cancel = { let signed_tx_cancel = {
let sig_a = tx_cancel_sig_a.clone(); let sig_a = tx_cancel_sig_a.clone();
@ -356,7 +357,7 @@ pub struct State0 {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
btc: bitcoin::Amount, btc: bitcoin::Amount,
xmr: monero::Amount, xmr: monero::Amount,
refund_timelock: u32, cancel_timelock: u32,
punish_timelock: u32, punish_timelock: u32,
refund_address: bitcoin::Address, refund_address: bitcoin::Address,
} }
@ -366,7 +367,7 @@ impl State0 {
rng: &mut R, rng: &mut R,
btc: bitcoin::Amount, btc: bitcoin::Amount,
xmr: monero::Amount, xmr: monero::Amount,
refund_timelock: u32, cancel_timelock: u32,
punish_timelock: u32, punish_timelock: u32,
refund_address: bitcoin::Address, refund_address: bitcoin::Address,
) -> Self { ) -> Self {
@ -381,7 +382,7 @@ impl State0 {
v_b, v_b,
btc, btc,
xmr, xmr,
refund_timelock, cancel_timelock,
punish_timelock, punish_timelock,
refund_address, refund_address,
} }
@ -426,7 +427,7 @@ impl State0 {
v, v,
btc: self.btc, btc: self.btc,
xmr: self.xmr, xmr: self.xmr,
refund_timelock: self.refund_timelock, cancel_timelock: self.cancel_timelock,
punish_timelock: self.punish_timelock, punish_timelock: self.punish_timelock,
refund_address: self.refund_address, refund_address: self.refund_address,
redeem_address: msg.redeem_address, redeem_address: msg.redeem_address,
@ -447,7 +448,7 @@ pub struct State1 {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
btc: bitcoin::Amount, btc: bitcoin::Amount,
xmr: monero::Amount, xmr: monero::Amount,
refund_timelock: u32, cancel_timelock: u32,
punish_timelock: u32, punish_timelock: u32,
refund_address: bitcoin::Address, refund_address: bitcoin::Address,
redeem_address: bitcoin::Address, redeem_address: bitcoin::Address,
@ -463,7 +464,7 @@ impl State1 {
} }
pub fn receive(self, msg: alice::Message1) -> Result<State2> { pub fn receive(self, msg: alice::Message1) -> Result<State2> {
let tx_cancel = TxCancel::new(&self.tx_lock, self.refund_timelock, self.A, self.b.public()); let tx_cancel = TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address); let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address);
bitcoin::verify_sig(&self.A, &tx_cancel.digest(), &msg.tx_cancel_sig)?; bitcoin::verify_sig(&self.A, &tx_cancel.digest(), &msg.tx_cancel_sig)?;
@ -483,7 +484,7 @@ impl State1 {
v: self.v, v: self.v,
btc: self.btc, btc: self.btc,
xmr: self.xmr, xmr: self.xmr,
refund_timelock: self.refund_timelock, cancel_timelock: self.cancel_timelock,
punish_timelock: self.punish_timelock, punish_timelock: self.punish_timelock,
refund_address: self.refund_address, refund_address: self.refund_address,
redeem_address: self.redeem_address, redeem_address: self.redeem_address,
@ -506,7 +507,7 @@ pub struct State2 {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
btc: bitcoin::Amount, btc: bitcoin::Amount,
pub xmr: monero::Amount, pub xmr: monero::Amount,
pub refund_timelock: u32, pub cancel_timelock: u32,
pub punish_timelock: u32, pub punish_timelock: u32,
pub refund_address: bitcoin::Address, pub refund_address: bitcoin::Address,
pub redeem_address: bitcoin::Address, pub redeem_address: bitcoin::Address,
@ -518,7 +519,7 @@ pub struct State2 {
impl State2 { impl State2 {
pub fn next_message(&self) -> Message2 { pub fn next_message(&self) -> Message2 {
let tx_cancel = TxCancel::new(&self.tx_lock, self.refund_timelock, self.A, self.b.public()); let tx_cancel = TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
let tx_cancel_sig = self.b.sign(tx_cancel.digest()); let tx_cancel_sig = self.b.sign(tx_cancel.digest());
let tx_punish = let tx_punish =
bitcoin::TxPunish::new(&tx_cancel, &self.punish_address, self.punish_timelock); bitcoin::TxPunish::new(&tx_cancel, &self.punish_address, self.punish_timelock);
@ -550,7 +551,7 @@ impl State2 {
v: self.v, v: self.v,
btc: self.btc, btc: self.btc,
xmr: self.xmr, xmr: self.xmr,
refund_timelock: self.refund_timelock, cancel_timelock: self.cancel_timelock,
punish_timelock: self.punish_timelock, punish_timelock: self.punish_timelock,
refund_address: self.refund_address, refund_address: self.refund_address,
redeem_address: self.redeem_address, redeem_address: self.redeem_address,
@ -573,7 +574,7 @@ pub struct State3 {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
btc: bitcoin::Amount, btc: bitcoin::Amount,
xmr: monero::Amount, xmr: monero::Amount,
pub refund_timelock: u32, pub cancel_timelock: u32,
punish_timelock: u32, punish_timelock: u32,
pub refund_address: bitcoin::Address, pub refund_address: bitcoin::Address,
redeem_address: bitcoin::Address, redeem_address: bitcoin::Address,
@ -612,7 +613,7 @@ impl State3 {
v: self.v, v: self.v,
btc: self.btc, btc: self.btc,
xmr: self.xmr, xmr: self.xmr,
refund_timelock: self.refund_timelock, cancel_timelock: self.cancel_timelock,
punish_timelock: self.punish_timelock, punish_timelock: self.punish_timelock,
refund_address: self.refund_address, refund_address: self.refund_address,
redeem_address: self.redeem_address, redeem_address: self.redeem_address,
@ -623,14 +624,19 @@ impl State3 {
}) })
} }
pub async fn wait_for_t1<W>(&self, bitcoin_wallet: &W) -> Result<()> pub async fn wait_for_cancel_timelock_to_expire<W>(&self, bitcoin_wallet: &W) -> Result<()>
where where
W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight,
{ {
wait_for_t1(bitcoin_wallet, self.refund_timelock, self.tx_lock.txid()).await wait_for_cancel_timelock_to_expire(
bitcoin_wallet,
self.cancel_timelock,
self.tx_lock.txid(),
)
.await
} }
pub fn t1_expired(&self) -> State4 { pub fn state4(&self) -> State4 {
State4 { State4 {
A: self.A, A: self.A,
b: self.b.clone(), b: self.b.clone(),
@ -640,7 +646,7 @@ impl State3 {
v: self.v, v: self.v,
btc: self.btc, btc: self.btc,
xmr: self.xmr, xmr: self.xmr,
refund_timelock: self.refund_timelock, cancel_timelock: self.cancel_timelock,
punish_timelock: self.punish_timelock, punish_timelock: self.punish_timelock,
refund_address: self.refund_address.clone(), refund_address: self.refund_address.clone(),
redeem_address: self.redeem_address.clone(), redeem_address: self.redeem_address.clone(),
@ -655,13 +661,13 @@ impl State3 {
self.tx_lock.txid() self.tx_lock.txid()
} }
pub async fn current_epoch<W>(&self, bitcoin_wallet: &W) -> Result<Epoch> pub async fn current_epoch<W>(&self, bitcoin_wallet: &W) -> Result<ExpiredTimelocks>
where where
W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight,
{ {
current_epoch( current_epoch(
bitcoin_wallet, bitcoin_wallet,
self.refund_timelock, self.cancel_timelock,
self.punish_timelock, self.punish_timelock,
self.tx_lock.txid(), self.tx_lock.txid(),
) )
@ -680,7 +686,7 @@ pub struct State4 {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
btc: bitcoin::Amount, btc: bitcoin::Amount,
xmr: monero::Amount, xmr: monero::Amount,
pub refund_timelock: u32, pub cancel_timelock: u32,
punish_timelock: u32, punish_timelock: u32,
pub refund_address: bitcoin::Address, pub refund_address: bitcoin::Address,
pub redeem_address: bitcoin::Address, pub redeem_address: bitcoin::Address,
@ -708,7 +714,7 @@ impl State4 {
W: GetRawTransaction, W: GetRawTransaction,
{ {
let tx_cancel = let tx_cancel =
bitcoin::TxCancel::new(&self.tx_lock, self.refund_timelock, self.A, self.b.public()); bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
let sig_a = self.tx_cancel_sig_a.clone(); let sig_a = self.tx_cancel_sig_a.clone();
let sig_b = self.b.sign(tx_cancel.digest()); let sig_b = self.b.sign(tx_cancel.digest());
@ -731,7 +737,7 @@ impl State4 {
W: BroadcastSignedTransaction, W: BroadcastSignedTransaction,
{ {
let tx_cancel = let tx_cancel =
bitcoin::TxCancel::new(&self.tx_lock, self.refund_timelock, self.A, self.b.public()); bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
let sig_a = self.tx_cancel_sig_a.clone(); let sig_a = self.tx_cancel_sig_a.clone();
let sig_b = self.b.sign(tx_cancel.digest()); let sig_b = self.b.sign(tx_cancel.digest());
@ -776,7 +782,7 @@ impl State4 {
v: self.v, v: self.v,
btc: self.btc, btc: self.btc,
xmr: self.xmr, xmr: self.xmr,
refund_timelock: self.refund_timelock, cancel_timelock: self.cancel_timelock,
punish_timelock: self.punish_timelock, punish_timelock: self.punish_timelock,
refund_address: self.refund_address.clone(), refund_address: self.refund_address.clone(),
redeem_address: self.redeem_address.clone(), redeem_address: self.redeem_address.clone(),
@ -787,20 +793,25 @@ impl State4 {
}) })
} }
pub async fn wait_for_t1<W>(&self, bitcoin_wallet: &W) -> Result<()> pub async fn wait_for_cancel_timelock_to_expire<W>(&self, bitcoin_wallet: &W) -> Result<()>
where where
W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight,
{ {
wait_for_t1(bitcoin_wallet, self.refund_timelock, self.tx_lock.txid()).await wait_for_cancel_timelock_to_expire(
bitcoin_wallet,
self.cancel_timelock,
self.tx_lock.txid(),
)
.await
} }
pub async fn current_epoch<W>(&self, bitcoin_wallet: &W) -> Result<Epoch> pub async fn expired_timelock<W>(&self, bitcoin_wallet: &W) -> Result<ExpiredTimelocks>
where where
W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight,
{ {
current_epoch( current_epoch(
bitcoin_wallet, bitcoin_wallet,
self.refund_timelock, self.cancel_timelock,
self.punish_timelock, self.punish_timelock,
self.tx_lock.txid(), self.tx_lock.txid(),
) )
@ -812,7 +823,7 @@ impl State4 {
bitcoin_wallet: &W, bitcoin_wallet: &W,
) -> Result<()> { ) -> Result<()> {
let tx_cancel = let tx_cancel =
bitcoin::TxCancel::new(&self.tx_lock, self.refund_timelock, self.A, self.b.public()); bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address); let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address);
{ {
@ -868,7 +879,7 @@ pub struct State5 {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
btc: bitcoin::Amount, btc: bitcoin::Amount,
xmr: monero::Amount, xmr: monero::Amount,
refund_timelock: u32, cancel_timelock: u32,
punish_timelock: u32, punish_timelock: u32,
refund_address: bitcoin::Address, refund_address: bitcoin::Address,
pub redeem_address: bitcoin::Address, pub redeem_address: bitcoin::Address,

View File

@ -7,7 +7,7 @@ pub struct Config {
pub bitcoin_finality_confirmations: u32, pub bitcoin_finality_confirmations: u32,
pub bitcoin_avg_block_time: Duration, pub bitcoin_avg_block_time: Duration,
pub monero_max_finality_time: Duration, pub monero_max_finality_time: Duration,
pub bitcoin_refund_timelock: u32, pub bitcoin_cancel_timelock: u32,
pub bitcoin_punish_timelock: u32, pub bitcoin_punish_timelock: u32,
pub bitcoin_network: ::bitcoin::Network, pub bitcoin_network: ::bitcoin::Network,
} }
@ -22,7 +22,7 @@ impl Config {
// blockchain is slow // blockchain is slow
monero_max_finality_time: (*mainnet::MONERO_AVG_BLOCK_TIME).mul_f64(1.5) monero_max_finality_time: (*mainnet::MONERO_AVG_BLOCK_TIME).mul_f64(1.5)
* mainnet::MONERO_FINALITY_CONFIRMATIONS, * mainnet::MONERO_FINALITY_CONFIRMATIONS,
bitcoin_refund_timelock: mainnet::BITCOIN_REFUND_TIMELOCK, bitcoin_cancel_timelock: mainnet::BITCOIN_CANCEL_TIMELOCK,
bitcoin_punish_timelock: mainnet::BITCOIN_PUNISH_TIMELOCK, bitcoin_punish_timelock: mainnet::BITCOIN_PUNISH_TIMELOCK,
bitcoin_network: ::bitcoin::Network::Bitcoin, bitcoin_network: ::bitcoin::Network::Bitcoin,
} }
@ -37,7 +37,7 @@ impl Config {
// blockchain is slow // blockchain is slow
monero_max_finality_time: (*regtest::MONERO_AVG_BLOCK_TIME).mul_f64(1.5) monero_max_finality_time: (*regtest::MONERO_AVG_BLOCK_TIME).mul_f64(1.5)
* regtest::MONERO_FINALITY_CONFIRMATIONS, * regtest::MONERO_FINALITY_CONFIRMATIONS,
bitcoin_refund_timelock: regtest::BITCOIN_REFUND_TIMELOCK, bitcoin_cancel_timelock: regtest::BITCOIN_CANCEL_TIMELOCK,
bitcoin_punish_timelock: regtest::BITCOIN_PUNISH_TIMELOCK, bitcoin_punish_timelock: regtest::BITCOIN_PUNISH_TIMELOCK,
bitcoin_network: ::bitcoin::Network::Regtest, bitcoin_network: ::bitcoin::Network::Regtest,
} }
@ -59,7 +59,7 @@ mod mainnet {
pub static MONERO_AVG_BLOCK_TIME: Lazy<Duration> = Lazy::new(|| Duration::from_secs(2 * 60)); pub static MONERO_AVG_BLOCK_TIME: Lazy<Duration> = Lazy::new(|| Duration::from_secs(2 * 60));
// Set to 12 hours, arbitrary value to be reviewed properly // Set to 12 hours, arbitrary value to be reviewed properly
pub static BITCOIN_REFUND_TIMELOCK: u32 = 72; pub static BITCOIN_CANCEL_TIMELOCK: u32 = 72;
pub static BITCOIN_PUNISH_TIMELOCK: u32 = 72; pub static BITCOIN_PUNISH_TIMELOCK: u32 = 72;
} }
@ -77,7 +77,7 @@ mod regtest {
pub static MONERO_AVG_BLOCK_TIME: Lazy<Duration> = Lazy::new(|| Duration::from_secs(60)); pub static MONERO_AVG_BLOCK_TIME: Lazy<Duration> = Lazy::new(|| Duration::from_secs(60));
pub static BITCOIN_REFUND_TIMELOCK: u32 = 50; pub static BITCOIN_CANCEL_TIMELOCK: u32 = 50;
pub static BITCOIN_PUNISH_TIMELOCK: u32 = 50; pub static BITCOIN_PUNISH_TIMELOCK: u32 = 50;
} }

View File

@ -15,10 +15,10 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Epoch { pub enum ExpiredTimelocks {
T0, None,
T1, Cancel,
T2, Punish,
} }
#[macro_use] #[macro_use]