diff --git a/swap/src/database.rs b/swap/src/database.rs index 3034eef1..6c7a0459 100644 --- a/swap/src/database.rs +++ b/swap/src/database.rs @@ -4,8 +4,13 @@ use state::Swap; use std::path::Path; use uuid::Uuid; +mod alice; +mod bob; pub mod state; +pub use alice::*; +pub use bob::*; + #[derive(Debug)] pub struct Database(sled::Db); @@ -84,9 +89,8 @@ where #[cfg(test)] mod tests { - use crate::database::state::{Alice, AliceEndState, Bob, BobEndState}; - use super::*; + use crate::database::{Alice, AliceEndState, Bob, BobEndState}; #[tokio::test] async fn can_write_and_read_to_multiple_keys() { diff --git a/swap/src/database/alice.rs b/swap/src/database/alice.rs new file mode 100644 index 00000000..f2069d67 --- /dev/null +++ b/swap/src/database/alice.rs @@ -0,0 +1,172 @@ +use crate::{alice::swap::AliceState, SwapAmounts}; +use bitcoin::hashes::core::fmt::Display; +use serde::{Deserialize, Serialize}; +use xmr_btc::{ + alice, + bitcoin::{EncryptedSignature, TxCancel, TxRefund}, + monero, + serde::monero_private_key, +}; + +// Large enum variant is fine because this is only used for storage +// and is dropped once written in DB. +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub enum Alice { + Started { + amounts: SwapAmounts, + state0: alice::State0, + }, + Negotiated(alice::State3), + BtcLocked(alice::State3), + XmrLocked(alice::State3), + EncSigLearned { + encrypted_signature: EncryptedSignature, + state3: alice::State3, + }, + CancelTimelockExpired(alice::State3), + BtcCancelled(alice::State3), + BtcPunishable(alice::State3), + BtcRefunded { + state3: alice::State3, + #[serde(with = "monero_private_key")] + spend_key: monero::PrivateKey, + }, + Done(AliceEndState), +} + +#[derive(Copy, Clone, strum::Display, Debug, Deserialize, Serialize, PartialEq)] +pub enum AliceEndState { + SafelyAborted, + BtcRedeemed, + XmrRefunded, + BtcPunished, +} + +impl From<&AliceState> for Alice { + fn from(alice_state: &AliceState) -> Self { + match alice_state { + AliceState::Negotiated { state3, .. } => Alice::Negotiated(state3.as_ref().clone()), + AliceState::BtcLocked { state3, .. } => Alice::BtcLocked(state3.as_ref().clone()), + AliceState::XmrLocked { state3 } => Alice::XmrLocked(state3.as_ref().clone()), + AliceState::EncSigLearned { + state3, + encrypted_signature, + } => Alice::EncSigLearned { + state3: state3.as_ref().clone(), + encrypted_signature: encrypted_signature.clone(), + }, + AliceState::BtcRedeemed => Alice::Done(AliceEndState::BtcRedeemed), + AliceState::BtcCancelled { state3, .. } => Alice::BtcCancelled(state3.as_ref().clone()), + AliceState::BtcRefunded { spend_key, state3 } => Alice::BtcRefunded { + spend_key: *spend_key, + state3: state3.as_ref().clone(), + }, + AliceState::BtcPunishable { state3, .. } => { + Alice::BtcPunishable(state3.as_ref().clone()) + } + AliceState::XmrRefunded => Alice::Done(AliceEndState::XmrRefunded), + AliceState::CancelTimelockExpired { state3 } => { + Alice::CancelTimelockExpired(state3.as_ref().clone()) + } + AliceState::BtcPunished => Alice::Done(AliceEndState::BtcPunished), + AliceState::SafelyAborted => Alice::Done(AliceEndState::SafelyAborted), + AliceState::Started { amounts, state0 } => Alice::Started { + amounts: *amounts, + state0: state0.clone(), + }, + } + } +} + +impl From for AliceState { + fn from(db_state: Alice) -> Self { + match db_state { + Alice::Started { amounts, state0 } => AliceState::Started { amounts, state0 }, + Alice::Negotiated(state3) => AliceState::Negotiated { + channel: None, + amounts: SwapAmounts { + btc: state3.btc, + xmr: state3.xmr, + }, + state3: Box::new(state3), + }, + Alice::BtcLocked(state3) => AliceState::BtcLocked { + channel: None, + amounts: SwapAmounts { + btc: state3.btc, + xmr: state3.xmr, + }, + state3: Box::new(state3), + }, + Alice::XmrLocked(state3) => AliceState::XmrLocked { + state3: Box::new(state3), + }, + Alice::EncSigLearned { + state3: state, + encrypted_signature, + } => AliceState::EncSigLearned { + state3: Box::new(state), + encrypted_signature, + }, + Alice::CancelTimelockExpired(state3) => AliceState::CancelTimelockExpired { + state3: Box::new(state3), + }, + Alice::BtcCancelled(state) => { + let tx_cancel = TxCancel::new( + &state.tx_lock, + state.cancel_timelock, + state.a.public(), + state.B, + ); + + AliceState::BtcCancelled { + state3: Box::new(state), + tx_cancel, + } + } + Alice::BtcPunishable(state3) => { + let tx_cancel = TxCancel::new( + &state3.tx_lock, + state3.cancel_timelock, + state3.a.public(), + state3.B, + ); + let tx_refund = TxRefund::new(&tx_cancel, &state3.refund_address); + AliceState::BtcPunishable { + tx_refund, + state3: Box::new(state3), + } + } + Alice::BtcRefunded { + state3, spend_key, .. + } => AliceState::BtcRefunded { + spend_key, + state3: Box::new(state3), + }, + Alice::Done(end_state) => match end_state { + AliceEndState::SafelyAborted => AliceState::SafelyAborted, + AliceEndState::BtcRedeemed => AliceState::BtcRedeemed, + AliceEndState::XmrRefunded => AliceState::XmrRefunded, + AliceEndState::BtcPunished => AliceState::BtcPunished, + }, + } + } +} + +impl Display for Alice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Alice::Started { .. } => write!(f, "Started"), + Alice::Negotiated(_) => f.write_str("Negotiated"), + Alice::BtcLocked(_) => f.write_str("Bitcoin locked"), + Alice::XmrLocked(_) => f.write_str("Monero locked"), + Alice::CancelTimelockExpired(_) => f.write_str("Cancel timelock is expired"), + Alice::BtcCancelled(_) => f.write_str("Bitcoin cancel transaction published"), + Alice::BtcPunishable(_) => f.write_str("Bitcoin punishable"), + Alice::BtcRefunded { .. } => f.write_str("Monero refundable"), + Alice::Done(end_state) => write!(f, "Done: {}", end_state), + Alice::EncSigLearned { .. } => f.write_str("Encrypted signature learned"), + } + } +} diff --git a/swap/src/database/bob.rs b/swap/src/database/bob.rs new file mode 100644 index 00000000..0f462138 --- /dev/null +++ b/swap/src/database/bob.rs @@ -0,0 +1,92 @@ +use crate::{bob::swap::BobState, SwapAmounts}; +use bitcoin::hashes::core::fmt::Display; +use serde::{Deserialize, Serialize}; +use xmr_btc::bob; + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub enum Bob { + Started { + state0: bob::State0, + amounts: SwapAmounts, + }, + Negotiated { + state2: bob::State2, + }, + BtcLocked { + state3: bob::State3, + }, + XmrLocked { + state4: bob::State4, + }, + EncSigSent { + state4: bob::State4, + }, + BtcRedeemed(bob::State5), + CancelTimelockExpired(bob::State4), + BtcCancelled(bob::State4), + Done(BobEndState), +} + +#[derive(Clone, strum::Display, Debug, Deserialize, Serialize, PartialEq)] +pub enum BobEndState { + SafelyAborted, + XmrRedeemed, + BtcRefunded(Box), + BtcPunished, +} + +impl From for Bob { + fn from(bob_state: BobState) -> Self { + match bob_state { + BobState::Started { state0, amounts } => Bob::Started { state0, amounts }, + BobState::Negotiated(state2) => Bob::Negotiated { state2 }, + BobState::BtcLocked(state3) => Bob::BtcLocked { state3 }, + BobState::XmrLocked(state4) => Bob::XmrLocked { state4 }, + BobState::EncSigSent(state4) => Bob::EncSigSent { state4 }, + BobState::BtcRedeemed(state5) => Bob::BtcRedeemed(state5), + BobState::CancelTimelockExpired(state4) => Bob::CancelTimelockExpired(state4), + BobState::BtcCancelled(state4) => Bob::BtcCancelled(state4), + BobState::BtcRefunded(state4) => Bob::Done(BobEndState::BtcRefunded(Box::new(state4))), + BobState::XmrRedeemed => Bob::Done(BobEndState::XmrRedeemed), + BobState::BtcPunished => Bob::Done(BobEndState::BtcPunished), + BobState::SafelyAborted => Bob::Done(BobEndState::SafelyAborted), + } + } +} + +impl From for BobState { + fn from(db_state: Bob) -> Self { + match db_state { + Bob::Started { state0, amounts } => BobState::Started { state0, amounts }, + Bob::Negotiated { state2 } => BobState::Negotiated(state2), + Bob::BtcLocked { state3 } => BobState::BtcLocked(state3), + Bob::XmrLocked { state4 } => BobState::XmrLocked(state4), + Bob::EncSigSent { state4 } => BobState::EncSigSent(state4), + Bob::BtcRedeemed(state5) => BobState::BtcRedeemed(state5), + Bob::CancelTimelockExpired(state4) => BobState::CancelTimelockExpired(state4), + Bob::BtcCancelled(state4) => BobState::BtcCancelled(state4), + Bob::Done(end_state) => match end_state { + BobEndState::SafelyAborted => BobState::SafelyAborted, + BobEndState::XmrRedeemed => BobState::XmrRedeemed, + BobEndState::BtcRefunded(state4) => BobState::BtcRefunded(*state4), + BobEndState::BtcPunished => BobState::BtcPunished, + }, + } + } +} + +impl Display for Bob { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Bob::Started { .. } => write!(f, "Started"), + Bob::Negotiated { .. } => f.write_str("Negotiated"), + Bob::BtcLocked { .. } => f.write_str("Bitcoin locked"), + Bob::XmrLocked { .. } => f.write_str("Monero locked"), + Bob::CancelTimelockExpired(_) => f.write_str("Cancel timelock is expired"), + Bob::BtcCancelled(_) => f.write_str("Bitcoin refundable"), + Bob::BtcRedeemed(_) => f.write_str("Monero redeemable"), + Bob::Done(end_state) => write!(f, "Done: {}", end_state), + Bob::EncSigSent { .. } => f.write_str("Encrypted signature sent"), + } + } +} diff --git a/swap/src/database/state.rs b/swap/src/database/state.rs index ecfaf122..63e13ce8 100644 --- a/swap/src/database/state.rs +++ b/swap/src/database/state.rs @@ -1,12 +1,6 @@ -use crate::{alice::swap::AliceState, bob::swap::BobState, SwapAmounts}; +use crate::database::{Alice, Bob}; use serde::{Deserialize, Serialize}; use std::fmt::Display; -use xmr_btc::{ - alice, - bitcoin::{EncryptedSignature, TxCancel, TxRefund}, - bob, monero, - serde::monero_private_key, -}; #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub enum Swap { @@ -14,224 +8,6 @@ pub enum Swap { Bob(Bob), } -// Large enum variant is fine because this is only used for storage -// and is dropped once written in DB. -#[allow(clippy::large_enum_variant)] -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] -pub enum Alice { - Started { - amounts: SwapAmounts, - state0: alice::State0, - }, - Negotiated(alice::State3), - BtcLocked(alice::State3), - XmrLocked(alice::State3), - EncSigLearned { - encrypted_signature: EncryptedSignature, - state3: alice::State3, - }, - CancelTimelockExpired(alice::State3), - BtcCancelled(alice::State3), - BtcPunishable(alice::State3), - BtcRefunded { - state3: alice::State3, - #[serde(with = "monero_private_key")] - spend_key: monero::PrivateKey, - }, - Done(AliceEndState), -} - -#[derive(Copy, Clone, strum::Display, Debug, Deserialize, Serialize, PartialEq)] -pub enum AliceEndState { - SafelyAborted, - BtcRedeemed, - XmrRefunded, - BtcPunished, -} - -impl From<&AliceState> for Alice { - fn from(alice_state: &AliceState) -> Self { - match alice_state { - AliceState::Negotiated { state3, .. } => Alice::Negotiated(state3.as_ref().clone()), - AliceState::BtcLocked { state3, .. } => Alice::BtcLocked(state3.as_ref().clone()), - AliceState::XmrLocked { state3 } => Alice::XmrLocked(state3.as_ref().clone()), - AliceState::EncSigLearned { - state3, - encrypted_signature, - } => Alice::EncSigLearned { - state3: state3.as_ref().clone(), - encrypted_signature: encrypted_signature.clone(), - }, - AliceState::BtcRedeemed => Alice::Done(AliceEndState::BtcRedeemed), - AliceState::BtcCancelled { state3, .. } => Alice::BtcCancelled(state3.as_ref().clone()), - AliceState::BtcRefunded { spend_key, state3 } => Alice::BtcRefunded { - spend_key: *spend_key, - state3: state3.as_ref().clone(), - }, - AliceState::BtcPunishable { state3, .. } => { - Alice::BtcPunishable(state3.as_ref().clone()) - } - AliceState::XmrRefunded => Alice::Done(AliceEndState::XmrRefunded), - AliceState::CancelTimelockExpired { state3 } => { - Alice::CancelTimelockExpired(state3.as_ref().clone()) - } - AliceState::BtcPunished => Alice::Done(AliceEndState::BtcPunished), - AliceState::SafelyAborted => Alice::Done(AliceEndState::SafelyAborted), - AliceState::Started { amounts, state0 } => Alice::Started { - amounts: *amounts, - state0: state0.clone(), - }, - } - } -} - -impl From for AliceState { - fn from(db_state: Alice) -> Self { - match db_state { - Alice::Started { amounts, state0 } => AliceState::Started { amounts, state0 }, - Alice::Negotiated(state3) => AliceState::Negotiated { - channel: None, - amounts: SwapAmounts { - btc: state3.btc, - xmr: state3.xmr, - }, - state3: Box::new(state3), - }, - Alice::BtcLocked(state3) => AliceState::BtcLocked { - channel: None, - amounts: SwapAmounts { - btc: state3.btc, - xmr: state3.xmr, - }, - state3: Box::new(state3), - }, - Alice::XmrLocked(state3) => AliceState::XmrLocked { - state3: Box::new(state3), - }, - Alice::EncSigLearned { - state3: state, - encrypted_signature, - } => AliceState::EncSigLearned { - state3: Box::new(state), - encrypted_signature, - }, - Alice::CancelTimelockExpired(state3) => AliceState::CancelTimelockExpired { - state3: Box::new(state3), - }, - Alice::BtcCancelled(state) => { - let tx_cancel = TxCancel::new( - &state.tx_lock, - state.cancel_timelock, - state.a.public(), - state.B, - ); - - AliceState::BtcCancelled { - state3: Box::new(state), - tx_cancel, - } - } - Alice::BtcPunishable(state3) => { - let tx_cancel = TxCancel::new( - &state3.tx_lock, - state3.cancel_timelock, - state3.a.public(), - state3.B, - ); - let tx_refund = TxRefund::new(&tx_cancel, &state3.refund_address); - AliceState::BtcPunishable { - tx_refund, - state3: Box::new(state3), - } - } - Alice::BtcRefunded { - state3, spend_key, .. - } => AliceState::BtcRefunded { - spend_key, - state3: Box::new(state3), - }, - Alice::Done(end_state) => match end_state { - AliceEndState::SafelyAborted => AliceState::SafelyAborted, - AliceEndState::BtcRedeemed => AliceState::BtcRedeemed, - AliceEndState::XmrRefunded => AliceState::XmrRefunded, - AliceEndState::BtcPunished => AliceState::BtcPunished, - }, - } - } -} - -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] -pub enum Bob { - Started { - state0: bob::State0, - amounts: SwapAmounts, - }, - Negotiated { - state2: bob::State2, - }, - BtcLocked { - state3: bob::State3, - }, - XmrLocked { - state4: bob::State4, - }, - EncSigSent { - state4: bob::State4, - }, - BtcRedeemed(bob::State5), - CancelTimelockExpired(bob::State4), - BtcCancelled(bob::State4), - Done(BobEndState), -} - -#[derive(Clone, strum::Display, Debug, Deserialize, Serialize, PartialEq)] -pub enum BobEndState { - SafelyAborted, - XmrRedeemed, - BtcRefunded(Box), - BtcPunished, -} - -impl From for Bob { - fn from(bob_state: BobState) -> Self { - match bob_state { - BobState::Started { state0, amounts } => Bob::Started { state0, amounts }, - BobState::Negotiated(state2) => Bob::Negotiated { state2 }, - BobState::BtcLocked(state3) => Bob::BtcLocked { state3 }, - BobState::XmrLocked(state4) => Bob::XmrLocked { state4 }, - BobState::EncSigSent(state4) => Bob::EncSigSent { state4 }, - BobState::BtcRedeemed(state5) => Bob::BtcRedeemed(state5), - BobState::CancelTimelockExpired(state4) => Bob::CancelTimelockExpired(state4), - BobState::BtcCancelled(state4) => Bob::BtcCancelled(state4), - BobState::BtcRefunded(state4) => Bob::Done(BobEndState::BtcRefunded(Box::new(state4))), - BobState::XmrRedeemed => Bob::Done(BobEndState::XmrRedeemed), - BobState::BtcPunished => Bob::Done(BobEndState::BtcPunished), - BobState::SafelyAborted => Bob::Done(BobEndState::SafelyAborted), - } - } -} - -impl From for BobState { - fn from(db_state: Bob) -> Self { - match db_state { - Bob::Started { state0, amounts } => BobState::Started { state0, amounts }, - Bob::Negotiated { state2 } => BobState::Negotiated(state2), - Bob::BtcLocked { state3 } => BobState::BtcLocked(state3), - Bob::XmrLocked { state4 } => BobState::XmrLocked(state4), - Bob::EncSigSent { state4 } => BobState::EncSigSent(state4), - Bob::BtcRedeemed(state5) => BobState::BtcRedeemed(state5), - Bob::CancelTimelockExpired(state4) => BobState::CancelTimelockExpired(state4), - Bob::BtcCancelled(state4) => BobState::BtcCancelled(state4), - Bob::Done(end_state) => match end_state { - BobEndState::SafelyAborted => BobState::SafelyAborted, - BobEndState::XmrRedeemed => BobState::XmrRedeemed, - BobEndState::BtcRefunded(state4) => BobState::BtcRefunded(*state4), - BobEndState::BtcPunished => BobState::BtcPunished, - }, - } - } -} - impl From for Swap { fn from(from: Alice) -> Self { Swap::Alice(from) @@ -252,36 +28,3 @@ impl Display for Swap { } } } - -impl Display for Alice { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Alice::Started { .. } => write!(f, "Started"), - Alice::Negotiated(_) => f.write_str("Negotiated"), - Alice::BtcLocked(_) => f.write_str("Bitcoin locked"), - Alice::XmrLocked(_) => f.write_str("Monero locked"), - Alice::CancelTimelockExpired(_) => f.write_str("Cancel timelock is expired"), - Alice::BtcCancelled(_) => f.write_str("Bitcoin cancel transaction published"), - Alice::BtcPunishable(_) => f.write_str("Bitcoin punishable"), - Alice::BtcRefunded { .. } => f.write_str("Monero refundable"), - Alice::Done(end_state) => write!(f, "Done: {}", end_state), - Alice::EncSigLearned { .. } => f.write_str("Encrypted signature learned"), - } - } -} - -impl Display for Bob { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Bob::Started { .. } => write!(f, "Started"), - Bob::Negotiated { .. } => f.write_str("Negotiated"), - Bob::BtcLocked { .. } => f.write_str("Bitcoin locked"), - Bob::XmrLocked { .. } => f.write_str("Monero locked"), - Bob::CancelTimelockExpired(_) => f.write_str("Cancel timelock is expired"), - Bob::BtcCancelled(_) => f.write_str("Bitcoin refundable"), - Bob::BtcRedeemed(_) => f.write_str("Monero redeemable"), - Bob::Done(end_state) => write!(f, "Done: {}", end_state), - Bob::EncSigSent { .. } => f.write_str("Encrypted signature sent"), - } - } -} diff --git a/swap/tests/happy_path_restart_alice.rs b/swap/tests/happy_path_restart_alice.rs index 9fa554ea..b35d33c3 100644 --- a/swap/tests/happy_path_restart_alice.rs +++ b/swap/tests/happy_path_restart_alice.rs @@ -114,7 +114,7 @@ async fn given_alice_restarts_after_encsig_is_learned_resume_swap() { let resume_state = if let swap::database::state::Swap::Alice(state) = alice_db.get_state(alice_swap_id).unwrap() { - assert!(matches!(state, swap::database::state::Alice::EncSigLearned {..})); + assert!(matches!(state, swap::database::Alice::EncSigLearned {..})); state.into() } else { unreachable!() diff --git a/swap/tests/happy_path_restart_bob_after_comm.rs b/swap/tests/happy_path_restart_bob_after_comm.rs index 1086bb97..31d60016 100644 --- a/swap/tests/happy_path_restart_bob_after_comm.rs +++ b/swap/tests/happy_path_restart_bob_after_comm.rs @@ -116,7 +116,7 @@ async fn given_bob_restarts_after_encsig_is_sent_resume_swap() { let resume_state = if let swap::database::state::Swap::Bob(state) = bob_db.get_state(bob_swap_id).unwrap() { - assert!(matches!(state, swap::database::state::Bob::EncSigSent {..})); + assert!(matches!(state, swap::database::Bob::EncSigSent {..})); state.into() } else { unreachable!()