From 8754a9931b7dcf6a802852173cf5f690e947ee92 Mon Sep 17 00:00:00 2001 From: rishflab Date: Tue, 29 Sep 2020 15:36:50 +1000 Subject: [PATCH 01/20] Execute Alice and Bob state machines concurrently Previously we were testing the protocol by manually driving Alice and Bob's state machines. This logic has now be moved to an async state transition function that can take any possible state as input. The state transition function is called in a loop until it returns the desired state. This allows use to interrupt midway through the protocol and perform refund and punish tests. This design was chosen over a generator based implementation because the the generator based implementation results in a impure state transition function that is difficult to reason about and prone to bugs. Test related code was extracted into the tests folder. The 2b and 4b states were renamed to be consistent with the rest. Macros were used to reduce code duplication when converting child states to their parent states and vice versa. Todos were added were neccessary. --- xmr-btc/Cargo.toml | 4 + xmr-btc/src/alice.rs | 263 +++++++++--- xmr-btc/src/alice/message.rs | 114 +++++ xmr-btc/src/bitcoin.rs | 5 - xmr-btc/src/bob.rs | 178 ++++++-- xmr-btc/src/bob/message.rs | 137 ++++++ xmr-btc/src/lib.rs | 365 +--------------- xmr-btc/src/monero.rs | 24 +- xmr-btc/src/transport.rs | 8 + xmr-btc/tests/e2e.rs | 389 ++++++++++++++++++ xmr-btc/tests/node.rs | 92 +++++ xmr-btc/tests/transport.rs | 56 +++ .../wallet.rs => tests/wallet/bitcoin.rs} | 9 +- xmr-btc/tests/wallet/mod.rs | 2 + .../wallet.rs => tests/wallet/monero.rs} | 68 +-- 15 files changed, 1211 insertions(+), 503 deletions(-) create mode 100644 xmr-btc/src/alice/message.rs create mode 100644 xmr-btc/src/bob/message.rs create mode 100644 xmr-btc/src/transport.rs create mode 100644 xmr-btc/tests/e2e.rs create mode 100644 xmr-btc/tests/node.rs create mode 100644 xmr-btc/tests/transport.rs rename xmr-btc/{src/bitcoin/wallet.rs => tests/wallet/bitcoin.rs} (94%) create mode 100644 xmr-btc/tests/wallet/mod.rs rename xmr-btc/{src/monero/wallet.rs => tests/wallet/monero.rs} (92%) diff --git a/xmr-btc/Cargo.toml b/xmr-btc/Cargo.toml index c49df15d..eeed8dae 100644 --- a/xmr-btc/Cargo.toml +++ b/xmr-btc/Cargo.toml @@ -17,6 +17,8 @@ monero = "0.9" rand = "0.7" sha2 = "0.9" thiserror = "1" +tokio = { version = "0.2", default-features = false, features = ["time"] } +tracing = "0.1" [dev-dependencies] base64 = "0.12" @@ -25,3 +27,5 @@ monero-harness = { path = "../monero-harness" } reqwest = { version = "0.10", default-features = false } testcontainers = "0.10" tokio = { version = "0.2", default-features = false, features = ["blocking", "macros", "rt-core", "time", "rt-threaded"] } +tracing-subscriber = "0.2.12" +tracing = "0.1" diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 2e5c1d4f..f2aa9ad5 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -2,33 +2,178 @@ use anyhow::{anyhow, Result}; use ecdsa_fun::adaptor::{Adaptor, EncryptedSignature}; use rand::{CryptoRng, RngCore}; -use crate::{bitcoin, bitcoin::GetRawTransaction, bob, monero, monero::ImportOutput}; -use ecdsa_fun::{nonce::Deterministic, Signature}; +use crate::{ + bitcoin, + bitcoin::{BroadcastSignedTransaction, GetRawTransaction}, + bob, monero, + monero::{ImportOutput, Transfer}, + transport::SendReceive, +}; +use ecdsa_fun::nonce::Deterministic; use sha2::Sha256; +use std::convert::{TryFrom, TryInto}; -#[derive(Debug)] -pub struct Message0 { - pub(crate) A: bitcoin::PublicKey, - pub(crate) S_a_monero: monero::PublicKey, - pub(crate) S_a_bitcoin: bitcoin::PublicKey, - pub(crate) dleq_proof_s_a: cross_curve_dleq::Proof, - pub(crate) v_a: monero::PrivateViewKey, - pub(crate) redeem_address: bitcoin::Address, - pub(crate) punish_address: bitcoin::Address, +pub mod message; +pub use message::{Message, Message0, Message1, Message2, UnexpectedMessage}; + +pub async fn next_state< + 'a, + R: RngCore + CryptoRng, + B: GetRawTransaction + BroadcastSignedTransaction, + M: ImportOutput + Transfer, + T: SendReceive, +>( + bitcoin_wallet: &B, + monero_wallet: &M, + transport: &mut T, + state: State, + rng: &mut R, +) -> Result { + match state { + State::State0(state0) => { + transport + .send_message(state0.next_message(rng).into()) + .await?; + + let bob_message0: bob::Message0 = transport.receive_message().await?.try_into()?; + let state1 = state0.receive(bob_message0)?; + Ok(state1.into()) + } + State::State1(state1) => { + let bob_message1: bob::Message1 = transport.receive_message().await?.try_into()?; + let state2 = state1.receive(bob_message1); + let alice_message1: Message1 = state2.next_message(); + transport.send_message(alice_message1.into()).await?; + Ok(state2.into()) + } + State::State2(state2) => { + let bob_message2: bob::Message2 = transport.receive_message().await?.try_into()?; + let state3 = state2.receive(bob_message2)?; + tokio::time::delay_for(std::time::Duration::new(5, 0)).await; + Ok(state3.into()) + } + State::State3(state3) => { + tracing::info!("alice is watching for locked btc"); + let state4 = state3.watch_for_lock_btc(bitcoin_wallet).await?; + Ok(state4.into()) + } + State::State4(state4) => { + let state5 = state4.lock_xmr(monero_wallet).await?; + tracing::info!("alice has locked xmr"); + Ok(state5.into()) + } + State::State5(state5) => { + transport.send_message(state5.next_message().into()).await?; + // todo: pass in state4b as a parameter somewhere in this call to prevent the + // user from waiting for a message that wont be sent + let message3: bob::Message3 = transport.receive_message().await?.try_into()?; + let state6 = state5.receive(message3); + tracing::info!("alice has received bob message 3"); + tracing::info!("alice is redeeming btc"); + state6.redeem_btc(bitcoin_wallet).await.unwrap(); + Ok(state6.into()) + } + State::State6(state6) => Ok(state6.into()), + } } -#[derive(Debug)] -pub struct Message1 { - pub(crate) tx_cancel_sig: Signature, - pub(crate) tx_refund_encsig: EncryptedSignature, +#[derive(Debug, Clone)] +pub enum State { + State0(State0), + State1(State1), + State2(State2), + State3(State3), + State4(State4), + State5(State5), + State6(State6), } -#[derive(Debug)] -pub struct Message2 { - pub(crate) tx_lock_proof: monero::TransferProof, +// TODO: use macro or generics +pub fn is_state4(state: &State) -> bool { + match state { + State::State4 { .. } => true, + _ => false, + } +} +// TODO: use macro or generics +pub fn is_state5(state: &State) -> bool { + match state { + State::State5 { .. } => true, + _ => false, + } +} +// TODO: use macro or generics +pub fn is_state6(state: &State) -> bool { + match state { + State::State6 { .. } => true, + _ => false, + } } -#[derive(Debug)] +macro_rules! impl_try_from_parent_state { + ($type:ident) => { + impl TryFrom for $type { + type Error = anyhow::Error; + fn try_from(from: State) -> Result { + if let State::$type(state) = from { + Ok(state) + } else { + Err(anyhow!("Failed to convert parent state to child state")) + } + } + } + }; +} + +impl_try_from_parent_state!(State0); +impl_try_from_parent_state!(State1); +impl_try_from_parent_state!(State2); +impl_try_from_parent_state!(State3); +impl_try_from_parent_state!(State4); +impl_try_from_parent_state!(State5); +impl_try_from_parent_state!(State6); + +macro_rules! impl_from_child_state { + ($type:ident) => { + impl From<$type> for State { + fn from(from: $type) -> Self { + State::$type(from) + } + } + }; +} + +impl_from_child_state!(State0); +impl_from_child_state!(State1); +impl_from_child_state!(State2); +impl_from_child_state!(State3); +impl_from_child_state!(State4); +impl_from_child_state!(State5); +impl_from_child_state!(State6); + +impl State { + pub fn new( + rng: &mut R, + btc: bitcoin::Amount, + xmr: monero::Amount, + refund_timelock: u32, + punish_timelock: u32, + redeem_address: bitcoin::Address, + punish_address: bitcoin::Address, + ) -> Self { + Self::State0(State0::new( + rng, + btc, + xmr, + refund_timelock, + punish_timelock, + redeem_address, + punish_address, + )) + } +} + +#[derive(Debug, Clone)] pub struct State0 { a: bitcoin::SecretKey, s_a: cross_curve_dleq::Scalar, @@ -114,7 +259,7 @@ impl State0 { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct State1 { a: bitcoin::SecretKey, B: bitcoin::PublicKey, @@ -152,7 +297,7 @@ impl State1 { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct State2 { a: bitcoin::SecretKey, B: bitcoin::PublicKey, @@ -227,7 +372,7 @@ impl State2 { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct State3 { a: bitcoin::SecretKey, B: bitcoin::PublicKey, @@ -252,10 +397,13 @@ impl State3 { where W: bitcoin::GetRawTransaction, { - let _ = bitcoin_wallet + tracing::info!("{}", self.tx_lock.txid()); + let tx = bitcoin_wallet .get_raw_transaction(self.tx_lock.txid()) .await?; + tracing::info!("{}", tx.txid()); + Ok(State4 { a: self.a, B: self.B, @@ -277,7 +425,7 @@ impl State3 { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct State4 { a: bitcoin::SecretKey, B: bitcoin::PublicKey, @@ -298,7 +446,7 @@ pub struct State4 { } impl State4 { - pub async fn lock_xmr(self, monero_wallet: &W) -> Result<(State4b, monero::Amount)> + pub async fn lock_xmr(self, monero_wallet: &W) -> Result where W: monero::Transfer, { @@ -311,28 +459,26 @@ impl State4 { .transfer(S_a + S_b, self.v.public(), self.xmr) .await?; - Ok(( - State4b { - a: self.a, - B: self.B, - s_a: self.s_a, - S_b_monero: self.S_b_monero, - S_b_bitcoin: self.S_b_bitcoin, - v: self.v, - btc: self.btc, - xmr: self.xmr, - refund_timelock: self.refund_timelock, - punish_timelock: self.punish_timelock, - refund_address: self.refund_address, - redeem_address: self.redeem_address, - punish_address: self.punish_address, - tx_lock: self.tx_lock, - tx_lock_proof, - tx_punish_sig_bob: self.tx_punish_sig_bob, - tx_cancel_sig_bob: self.tx_cancel_sig_bob, - }, - fee, - )) + Ok(State5 { + a: self.a, + B: self.B, + s_a: self.s_a, + S_b_monero: self.S_b_monero, + S_b_bitcoin: self.S_b_bitcoin, + v: self.v, + btc: self.btc, + xmr: self.xmr, + refund_timelock: self.refund_timelock, + punish_timelock: self.punish_timelock, + refund_address: self.refund_address, + redeem_address: self.redeem_address, + punish_address: self.punish_address, + tx_lock: self.tx_lock, + tx_lock_proof, + tx_punish_sig_bob: self.tx_punish_sig_bob, + tx_cancel_sig_bob: self.tx_cancel_sig_bob, + lock_xmr_fee: fee, + }) } pub async fn punish( @@ -382,8 +528,8 @@ impl State4 { } } -#[derive(Debug)] -pub struct State4b { +#[derive(Debug, Clone)] +pub struct State5 { a: bitcoin::SecretKey, B: bitcoin::PublicKey, s_a: cross_curve_dleq::Scalar, @@ -401,17 +547,18 @@ pub struct State4b { tx_lock_proof: monero::TransferProof, tx_punish_sig_bob: bitcoin::Signature, tx_cancel_sig_bob: bitcoin::Signature, + lock_xmr_fee: monero::Amount, } -impl State4b { +impl State5 { pub fn next_message(&self) -> Message2 { Message2 { tx_lock_proof: self.tx_lock_proof.clone(), } } - pub fn receive(self, msg: bob::Message3) -> State5 { - State5 { + pub fn receive(self, msg: bob::Message3) -> State6 { + State6 { a: self.a, B: self.B, s_a: self.s_a, @@ -428,6 +575,7 @@ impl State4b { tx_lock: self.tx_lock, tx_punish_sig_bob: self.tx_punish_sig_bob, tx_redeem_encsig: msg.tx_redeem_encsig, + lock_xmr_fee: self.lock_xmr_fee, } } @@ -469,8 +617,8 @@ impl State4b { } } -#[derive(Debug)] -pub struct State5 { +#[derive(Debug, Clone)] +pub struct State6 { a: bitcoin::SecretKey, B: bitcoin::PublicKey, s_a: cross_curve_dleq::Scalar, @@ -487,9 +635,10 @@ pub struct State5 { tx_lock: bitcoin::TxLock, tx_punish_sig_bob: bitcoin::Signature, tx_redeem_encsig: EncryptedSignature, + lock_xmr_fee: monero::Amount, } -impl State5 { +impl State6 { pub async fn redeem_btc( &self, bitcoin_wallet: &W, @@ -513,4 +662,8 @@ impl State5 { Ok(()) } + + pub fn lock_xmr_fee(&self) -> monero::Amount { + self.lock_xmr_fee + } } diff --git a/xmr-btc/src/alice/message.rs b/xmr-btc/src/alice/message.rs new file mode 100644 index 00000000..5901a050 --- /dev/null +++ b/xmr-btc/src/alice/message.rs @@ -0,0 +1,114 @@ +use anyhow::Result; +use ecdsa_fun::adaptor::EncryptedSignature; + +use crate::{bitcoin, monero}; +use ecdsa_fun::Signature; + +use std::convert::TryFrom; + +#[derive(Debug)] +pub enum Message { + Message0(Message0), + Message1(Message1), + Message2(Message2), +} + +#[derive(Debug)] +pub struct Message0 { + pub(crate) A: bitcoin::PublicKey, + pub(crate) S_a_monero: monero::PublicKey, + pub(crate) S_a_bitcoin: bitcoin::PublicKey, + pub(crate) dleq_proof_s_a: cross_curve_dleq::Proof, + pub(crate) v_a: monero::PrivateViewKey, + pub(crate) redeem_address: bitcoin::Address, + pub(crate) punish_address: bitcoin::Address, +} + +#[derive(Debug)] +pub struct Message1 { + pub(crate) tx_cancel_sig: Signature, + pub(crate) tx_refund_encsig: EncryptedSignature, +} + +#[derive(Debug)] +pub struct Message2 { + pub(crate) tx_lock_proof: monero::TransferProof, +} + +impl From for Message { + fn from(m: Message0) -> Self { + Message::Message0(m) + } +} + +impl TryFrom for Message0 { + type Error = UnexpectedMessage; + + fn try_from(m: Message) -> Result { + match m { + Message::Message0(m) => Ok(m), + _ => Err(UnexpectedMessage { + expected_type: "Create0".to_string(), + received: m, + }), + } + } +} + +impl From for Message { + fn from(m: Message1) -> Self { + Message::Message1(m) + } +} + +impl TryFrom for Message1 { + type Error = UnexpectedMessage; + + fn try_from(m: Message) -> Result { + match m { + Message::Message1(m) => Ok(m), + _ => Err(UnexpectedMessage { + expected_type: "Create1".to_string(), + received: m, + }), + } + } +} + +impl From for Message { + fn from(m: Message2) -> Self { + Message::Message2(m) + } +} + +impl TryFrom for Message2 { + type Error = UnexpectedMessage; + + fn try_from(m: Message) -> Result { + match m { + Message::Message2(m) => Ok(m), + _ => Err(UnexpectedMessage { + expected_type: "Create2".to_string(), + received: m, + }), + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error("expected message of type {expected_type}, got {received:?}")] +pub struct UnexpectedMessage { + expected_type: String, + received: Message, +} + +impl UnexpectedMessage { + pub fn new(received: Message) -> Self { + let expected_type = std::any::type_name::(); + + Self { + expected_type: expected_type.to_string(), + received, + } + } +} diff --git a/xmr-btc/src/bitcoin.rs b/xmr-btc/src/bitcoin.rs index 25a1b458..e903bb75 100644 --- a/xmr-btc/src/bitcoin.rs +++ b/xmr-btc/src/bitcoin.rs @@ -1,6 +1,4 @@ pub mod transactions; -#[cfg(test)] -pub mod wallet; use anyhow::{anyhow, bail, Result}; use async_trait::async_trait; @@ -28,9 +26,6 @@ pub use crate::bitcoin::transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxR pub use bitcoin::{Address, Amount, OutPoint, Txid}; pub use ecdsa_fun::{adaptor::EncryptedSignature, Signature}; -#[cfg(test)] -pub use wallet::{make_wallet, Wallet}; - pub const TX_FEE: u64 = 10_000; #[derive(Debug, Clone)] diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index 25fcfadb..62841c5b 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -1,7 +1,11 @@ use crate::{ alice, - bitcoin::{self, BuildTxLockPsbt, GetRawTransaction, TxCancel}, + bitcoin::{ + self, BroadcastSignedTransaction, BuildTxLockPsbt, GetRawTransaction, SignTxLock, TxCancel, + }, monero, + monero::{CheckTransfer, ImportOutput}, + transport::SendReceive, }; use anyhow::{anyhow, Result}; use ecdsa_fun::{ @@ -11,31 +15,132 @@ use ecdsa_fun::{ }; use rand::{CryptoRng, RngCore}; use sha2::Sha256; +use std::convert::{TryFrom, TryInto}; -#[derive(Debug)] -pub struct Message0 { - pub(crate) B: bitcoin::PublicKey, - pub(crate) S_b_monero: monero::PublicKey, - pub(crate) S_b_bitcoin: bitcoin::PublicKey, - pub(crate) dleq_proof_s_b: cross_curve_dleq::Proof, - pub(crate) v_b: monero::PrivateViewKey, - pub(crate) refund_address: bitcoin::Address, +pub mod message; +pub use message::{Message, Message0, Message1, Message2, Message3, UnexpectedMessage}; + +pub async fn next_state< + 'a, + R: RngCore + CryptoRng, + B: GetRawTransaction + SignTxLock + BuildTxLockPsbt + BroadcastSignedTransaction, + M: ImportOutput + CheckTransfer, + T: SendReceive, +>( + bitcoin_wallet: &B, + monero_wallet: &M, + transport: &mut T, + state: State, + rng: &mut R, +) -> Result { + match state { + State::State0(state0) => { + transport + .send_message(state0.next_message(rng).into()) + .await?; + let message0: alice::Message0 = transport.receive_message().await?.try_into()?; + let state1 = state0.receive(bitcoin_wallet, message0).await?; + Ok(state1.into()) + } + State::State1(state1) => { + transport.send_message(state1.next_message().into()).await?; + + let message1: alice::Message1 = transport.receive_message().await?.try_into()?; + let state2 = state1.receive(message1)?; + Ok(state2.into()) + } + State::State2(state2) => { + let message2 = state2.next_message(); + let state3 = state2.lock_btc(bitcoin_wallet).await?; + tracing::info!("bob has locked btc"); + transport.send_message(message2.into()).await?; + Ok(state3.into()) + } + State::State3(state3) => { + let message2: alice::Message2 = transport.receive_message().await?.try_into()?; + + let state4 = state3.watch_for_lock_xmr(monero_wallet, message2).await?; + tracing::info!("bob has seen that alice has locked xmr"); + Ok(state4.into()) + } + State::State4(state4) => { + transport.send_message(state4.next_message().into()).await?; + + tracing::info!("bob is watching for redeem_btc"); + tokio::time::delay_for(std::time::Duration::new(5, 0)).await; + let state5 = state4.watch_for_redeem_btc(bitcoin_wallet).await?; + tracing::info!("bob has seen that alice has redeemed btc"); + state5.claim_xmr(monero_wallet).await?; + tracing::info!("bob has claimed xmr"); + Ok(state5.into()) + } + State::State5(state5) => Ok(state5.into()), + } } #[derive(Debug)] -pub struct Message1 { - pub(crate) tx_lock: bitcoin::TxLock, +pub enum State { + State0(State0), + State1(State1), + State2(State2), + State3(State3), + State4(State4), + State5(State5), } -#[derive(Debug)] -pub struct Message2 { - pub(crate) tx_punish_sig: Signature, - pub(crate) tx_cancel_sig: Signature, +macro_rules! impl_try_from_parent_state { + ($type:ident) => { + impl TryFrom for $type { + type Error = anyhow::Error; + fn try_from(from: State) -> Result { + if let State::$type(state) = from { + Ok(state) + } else { + Err(anyhow!("Failed to convert parent state to child state")) + } + } + } + }; } -#[derive(Debug)] -pub struct Message3 { - pub(crate) tx_redeem_encsig: EncryptedSignature, +impl_try_from_parent_state!(State0); +impl_try_from_parent_state!(State1); +impl_try_from_parent_state!(State2); +impl_try_from_parent_state!(State3); +impl_try_from_parent_state!(State4); +impl_try_from_parent_state!(State5); + +macro_rules! impl_from_child_state { + ($type:ident) => { + impl From<$type> for State { + fn from(from: $type) -> Self { + State::$type(from) + } + } + }; +} + +impl_from_child_state!(State0); +impl_from_child_state!(State1); +impl_from_child_state!(State2); +impl_from_child_state!(State3); +impl_from_child_state!(State4); +impl_from_child_state!(State5); + +// TODO: use macro or generics +pub fn is_state5(state: &State) -> bool { + match state { + State::State5 { .. } => true, + _ => false, + } +} + +// TODO: use macro or generics +pub fn is_state3(state: &State) -> bool { + match state { + State::State3 { .. } => true, + _ => false, + } } #[derive(Debug)] @@ -126,7 +231,7 @@ impl State0 { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct State1 { A: bitcoin::PublicKey, b: bitcoin::SecretKey, @@ -189,7 +294,7 @@ impl State1 { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct State2 { A: bitcoin::PublicKey, b: bitcoin::SecretKey, @@ -228,17 +333,18 @@ impl State2 { } } - pub async fn lock_btc(self, bitcoin_wallet: &W) -> Result + pub async fn lock_btc(self, bitcoin_wallet: &W) -> Result where W: bitcoin::SignTxLock + bitcoin::BroadcastSignedTransaction, { let signed_tx_lock = bitcoin_wallet.sign_tx_lock(self.tx_lock.clone()).await?; + tracing::info!("{}", self.tx_lock.txid()); let _ = bitcoin_wallet .broadcast_signed_transaction(signed_tx_lock) .await?; - Ok(State2b { + Ok(State3 { A: self.A, b: self.b, s_b: self.s_b, @@ -260,7 +366,7 @@ impl State2 { } #[derive(Debug, Clone)] -pub struct State2b { +pub struct State3 { A: bitcoin::PublicKey, b: bitcoin::SecretKey, s_b: cross_curve_dleq::Scalar, @@ -279,8 +385,9 @@ pub struct State2b { tx_refund_encsig: EncryptedSignature, } -impl State2b { - pub async fn watch_for_lock_xmr(self, xmr_wallet: &W, msg: alice::Message2) -> Result +impl State3 { + // todo: loop until punish? timelock has expired + pub async fn watch_for_lock_xmr(self, xmr_wallet: &W, msg: alice::Message2) -> Result where W: monero::CheckTransfer, { @@ -293,7 +400,7 @@ impl State2b { .check_transfer(S, self.v.public(), msg.tx_lock_proof, self.xmr) .await?; - Ok(State3 { + Ok(State4 { A: self.A, b: self.b, s_b: self.s_b, @@ -359,15 +466,13 @@ impl State2b { } Ok(()) } - - #[cfg(test)] pub fn tx_lock_id(&self) -> bitcoin::Txid { self.tx_lock.txid() } } #[derive(Debug, Clone)] -pub struct State3 { +pub struct State4 { A: bitcoin::PublicKey, b: bitcoin::SecretKey, s_b: cross_curve_dleq::Scalar, @@ -386,7 +491,7 @@ pub struct State3 { tx_refund_encsig: EncryptedSignature, } -impl State3 { +impl State4 { pub fn next_message(&self) -> Message3 { let tx_redeem = bitcoin::TxRedeem::new(&self.tx_lock, &self.redeem_address); let tx_redeem_encsig = self.b.encsign(self.S_a_bitcoin.clone(), tx_redeem.digest()); @@ -394,7 +499,7 @@ impl State3 { Message3 { tx_redeem_encsig } } - pub async fn watch_for_redeem_btc(self, bitcoin_wallet: &W) -> Result + pub async fn watch_for_redeem_btc(self, bitcoin_wallet: &W) -> Result where W: GetRawTransaction, { @@ -409,7 +514,7 @@ impl State3 { let s_a = monero::PrivateKey::from_scalar(monero::Scalar::from_bytes_mod_order(s_a.to_bytes())); - Ok(State4 { + Ok(State5 { A: self.A, b: self.b, s_a, @@ -431,8 +536,8 @@ impl State3 { } } -#[derive(Debug)] -pub struct State4 { +#[derive(Debug, Clone)] +pub struct State5 { A: bitcoin::PublicKey, b: bitcoin::SecretKey, s_a: monero::PrivateKey, @@ -452,7 +557,7 @@ pub struct State4 { tx_cancel_sig: Signature, } -impl State4 { +impl State5 { pub async fn claim_xmr(&self, monero_wallet: &W) -> Result<()> where W: monero::ImportOutput, @@ -469,4 +574,7 @@ impl State4 { Ok(()) } + pub fn tx_lock_id(&self) -> bitcoin::Txid { + self.tx_lock.txid() + } } diff --git a/xmr-btc/src/bob/message.rs b/xmr-btc/src/bob/message.rs new file mode 100644 index 00000000..fe767a46 --- /dev/null +++ b/xmr-btc/src/bob/message.rs @@ -0,0 +1,137 @@ +use crate::{bitcoin, monero}; +use anyhow::Result; +use ecdsa_fun::{adaptor::EncryptedSignature, Signature}; + +use std::convert::TryFrom; + +#[derive(Debug)] +pub enum Message { + Message0(Message0), + Message1(Message1), + Message2(Message2), + Message3(Message3), +} + +#[derive(Debug)] +pub struct Message0 { + pub(crate) B: bitcoin::PublicKey, + pub(crate) S_b_monero: monero::PublicKey, + pub(crate) S_b_bitcoin: bitcoin::PublicKey, + pub(crate) dleq_proof_s_b: cross_curve_dleq::Proof, + pub(crate) v_b: monero::PrivateViewKey, + pub(crate) refund_address: bitcoin::Address, +} + +#[derive(Debug)] +pub struct Message1 { + pub(crate) tx_lock: bitcoin::TxLock, +} + +#[derive(Debug)] +pub struct Message2 { + pub(crate) tx_punish_sig: Signature, + pub(crate) tx_cancel_sig: Signature, +} + +#[derive(Debug)] +pub struct Message3 { + pub(crate) tx_redeem_encsig: EncryptedSignature, +} + +impl From for Message { + fn from(m: Message0) -> Self { + Message::Message0(m) + } +} + +impl TryFrom for Message0 { + type Error = UnexpectedMessage; + + fn try_from(m: Message) -> Result { + match m { + Message::Message0(m) => Ok(m), + _ => Err(UnexpectedMessage { + expected_type: "Create0".to_string(), + received: m, + }), + } + } +} + +impl From for Message { + fn from(m: Message1) -> Self { + Message::Message1(m) + } +} + +impl TryFrom for Message1 { + type Error = UnexpectedMessage; + + fn try_from(m: Message) -> Result { + match m { + Message::Message1(m) => Ok(m), + _ => Err(UnexpectedMessage { + expected_type: "Create0".to_string(), + received: m, + }), + } + } +} + +impl From for Message { + fn from(m: Message2) -> Self { + Message::Message2(m) + } +} + +impl TryFrom for Message2 { + type Error = UnexpectedMessage; + + fn try_from(m: Message) -> Result { + match m { + Message::Message2(m) => Ok(m), + _ => Err(UnexpectedMessage { + expected_type: "Create0".to_string(), + received: m, + }), + } + } +} + +impl From for Message { + fn from(m: Message3) -> Self { + Message::Message3(m) + } +} + +impl TryFrom for Message3 { + type Error = UnexpectedMessage; + + fn try_from(m: Message) -> Result { + match m { + Message::Message3(m) => Ok(m), + _ => Err(UnexpectedMessage { + expected_type: "Create0".to_string(), + received: m, + }), + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error("expected message of type {expected_type}, got {received:?}")] +pub struct UnexpectedMessage { + expected_type: String, + received: Message, +} + +impl UnexpectedMessage { + pub fn new(received: Message) -> Self { + let expected_type = std::any::type_name::(); + + Self { + expected_type: expected_type.to_string(), + received, + } + } +} diff --git a/xmr-btc/src/lib.rs b/xmr-btc/src/lib.rs index d4e66337..986d6434 100644 --- a/xmr-btc/src/lib.rs +++ b/xmr-btc/src/lib.rs @@ -18,367 +18,4 @@ pub mod alice; pub mod bitcoin; pub mod bob; pub mod monero; - -#[cfg(test)] -mod tests { - use crate::{ - alice, bitcoin, - bitcoin::{Amount, TX_FEE}, - bob, monero, - }; - use bitcoin_harness::Bitcoind; - use monero_harness::Monero; - use rand::rngs::OsRng; - use testcontainers::clients::Cli; - - const TEN_XMR: u64 = 10_000_000_000_000; - - pub async fn init_bitcoind(tc_client: &Cli) -> Bitcoind<'_> { - let bitcoind = Bitcoind::new(tc_client, "0.19.1").expect("failed to create bitcoind"); - let _ = bitcoind.init(5).await; - - bitcoind - } - - #[tokio::test] - async fn happy_path() { - let cli = Cli::default(); - let monero = Monero::new(&cli); - let bitcoind = init_bitcoind(&cli).await; - - // must be bigger than our hardcoded fee of 10_000 - let btc_amount = bitcoin::Amount::from_sat(10_000_000); - let xmr_amount = monero::Amount::from_piconero(1_000_000_000_000); - - let fund_alice = TEN_XMR; - let fund_bob = 0; - monero.init(fund_alice, fund_bob).await.unwrap(); - - let alice_monero_wallet = monero::AliceWallet(&monero); - let bob_monero_wallet = monero::BobWallet(&monero); - - let alice_btc_wallet = bitcoin::Wallet::new("alice", &bitcoind.node_url) - .await - .unwrap(); - let bob_btc_wallet = bitcoin::make_wallet("bob", &bitcoind, btc_amount) - .await - .unwrap(); - - let alice_initial_btc_balance = alice_btc_wallet.balance().await.unwrap(); - let bob_initial_btc_balance = bob_btc_wallet.balance().await.unwrap(); - - let alice_initial_xmr_balance = alice_monero_wallet.0.get_balance_alice().await.unwrap(); - let bob_initial_xmr_balance = bob_monero_wallet.0.get_balance_bob().await.unwrap(); - - let redeem_address = alice_btc_wallet.new_address().await.unwrap(); - let punish_address = redeem_address.clone(); - let refund_address = bob_btc_wallet.new_address().await.unwrap(); - - let refund_timelock = 1; - let punish_timelock = 1; - - let alice_state0 = alice::State0::new( - &mut OsRng, - btc_amount, - xmr_amount, - refund_timelock, - punish_timelock, - redeem_address, - punish_address, - ); - let bob_state0 = bob::State0::new( - &mut OsRng, - btc_amount, - xmr_amount, - refund_timelock, - punish_timelock, - refund_address.clone(), - ); - - let alice_message0 = alice_state0.next_message(&mut OsRng); - let bob_message0 = bob_state0.next_message(&mut OsRng); - - let alice_state1 = alice_state0.receive(bob_message0).unwrap(); - let bob_state1 = bob_state0 - .receive(&bob_btc_wallet, alice_message0) - .await - .unwrap(); - - let bob_message1 = bob_state1.next_message(); - let alice_state2 = alice_state1.receive(bob_message1); - let alice_message1 = alice_state2.next_message(); - let bob_state2 = bob_state1.receive(alice_message1).unwrap(); - - let bob_message2 = bob_state2.next_message(); - let alice_state3 = alice_state2.receive(bob_message2).unwrap(); - - let bob_state2b = bob_state2.lock_btc(&bob_btc_wallet).await.unwrap(); - let lock_txid = bob_state2b.tx_lock_id(); - - let alice_state4 = alice_state3 - .watch_for_lock_btc(&alice_btc_wallet) - .await - .unwrap(); - - let (alice_state4b, lock_tx_monero_fee) = - alice_state4.lock_xmr(&alice_monero_wallet).await.unwrap(); - - let alice_message2 = alice_state4b.next_message(); - - let bob_state3 = bob_state2b - .watch_for_lock_xmr(&bob_monero_wallet, alice_message2) - .await - .unwrap(); - - let bob_message3 = bob_state3.next_message(); - let alice_state5 = alice_state4b.receive(bob_message3); - - alice_state5.redeem_btc(&alice_btc_wallet).await.unwrap(); - let bob_state4 = bob_state3 - .watch_for_redeem_btc(&bob_btc_wallet) - .await - .unwrap(); - - bob_state4.claim_xmr(&bob_monero_wallet).await.unwrap(); - - let alice_final_btc_balance = alice_btc_wallet.balance().await.unwrap(); - let bob_final_btc_balance = bob_btc_wallet.balance().await.unwrap(); - - let lock_tx_bitcoin_fee = bob_btc_wallet.transaction_fee(lock_txid).await.unwrap(); - - assert_eq!( - alice_final_btc_balance, - alice_initial_btc_balance + btc_amount - bitcoin::Amount::from_sat(bitcoin::TX_FEE) - ); - assert_eq!( - bob_final_btc_balance, - bob_initial_btc_balance - btc_amount - lock_tx_bitcoin_fee - ); - - let alice_final_xmr_balance = alice_monero_wallet.0.get_balance_alice().await.unwrap(); - bob_monero_wallet - .0 - .wait_for_bob_wallet_block_height() - .await - .unwrap(); - let bob_final_xmr_balance = bob_monero_wallet.0.get_balance_bob().await.unwrap(); - - assert_eq!( - alice_final_xmr_balance, - alice_initial_xmr_balance - u64::from(xmr_amount) - u64::from(lock_tx_monero_fee) - ); - assert_eq!( - bob_final_xmr_balance, - bob_initial_xmr_balance + u64::from(xmr_amount) - ); - } - - #[tokio::test] - async fn both_refund() { - let cli = Cli::default(); - let monero = Monero::new(&cli); - let bitcoind = init_bitcoind(&cli).await; - - // must be bigger than our hardcoded fee of 10_000 - let btc_amount = bitcoin::Amount::from_sat(10_000_000); - let xmr_amount = monero::Amount::from_piconero(1_000_000_000_000); - - let alice_btc_wallet = bitcoin::Wallet::new("alice", &bitcoind.node_url) - .await - .unwrap(); - let bob_btc_wallet = bitcoin::make_wallet("bob", &bitcoind, btc_amount) - .await - .unwrap(); - - let fund_alice = TEN_XMR; - let fund_bob = 0; - - monero.init(fund_alice, fund_bob).await.unwrap(); - let alice_monero_wallet = monero::AliceWallet(&monero); - let bob_monero_wallet = monero::BobWallet(&monero); - - let alice_initial_btc_balance = alice_btc_wallet.balance().await.unwrap(); - let bob_initial_btc_balance = bob_btc_wallet.balance().await.unwrap(); - - let bob_initial_xmr_balance = bob_monero_wallet.0.get_balance_bob().await.unwrap(); - - let redeem_address = alice_btc_wallet.new_address().await.unwrap(); - let punish_address = redeem_address.clone(); - let refund_address = bob_btc_wallet.new_address().await.unwrap(); - - let refund_timelock = 1; - let punish_timelock = 1; - - let alice_state0 = alice::State0::new( - &mut OsRng, - btc_amount, - xmr_amount, - refund_timelock, - punish_timelock, - redeem_address, - punish_address, - ); - let bob_state0 = bob::State0::new( - &mut OsRng, - btc_amount, - xmr_amount, - refund_timelock, - punish_timelock, - refund_address.clone(), - ); - - let alice_message0 = alice_state0.next_message(&mut OsRng); - let bob_message0 = bob_state0.next_message(&mut OsRng); - - let alice_state1 = alice_state0.receive(bob_message0).unwrap(); - let bob_state1 = bob_state0 - .receive(&bob_btc_wallet, alice_message0) - .await - .unwrap(); - - let bob_message1 = bob_state1.next_message(); - let alice_state2 = alice_state1.receive(bob_message1); - let alice_message1 = alice_state2.next_message(); - let bob_state2 = bob_state1.receive(alice_message1).unwrap(); - - let bob_message2 = bob_state2.next_message(); - let alice_state3 = alice_state2.receive(bob_message2).unwrap(); - - let bob_state2b = bob_state2.lock_btc(&bob_btc_wallet).await.unwrap(); - - let alice_state4 = alice_state3 - .watch_for_lock_btc(&alice_btc_wallet) - .await - .unwrap(); - - let (alice_state4b, _lock_tx_monero_fee) = - alice_state4.lock_xmr(&alice_monero_wallet).await.unwrap(); - - bob_state2b.refund_btc(&bob_btc_wallet).await.unwrap(); - - alice_state4b - .refund_xmr(&alice_btc_wallet, &alice_monero_wallet) - .await - .unwrap(); - - let alice_final_btc_balance = alice_btc_wallet.balance().await.unwrap(); - let bob_final_btc_balance = bob_btc_wallet.balance().await.unwrap(); - - // lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal - // to TX_FEE - let lock_tx_bitcoin_fee = bob_btc_wallet - .transaction_fee(bob_state2b.tx_lock_id()) - .await - .unwrap(); - - assert_eq!(alice_final_btc_balance, alice_initial_btc_balance); - assert_eq!( - bob_final_btc_balance, - // The 2 * TX_FEE corresponds to tx_refund and tx_cancel. - bob_initial_btc_balance - Amount::from_sat(2 * TX_FEE) - lock_tx_bitcoin_fee - ); - - alice_monero_wallet - .0 - .wait_for_alice_wallet_block_height() - .await - .unwrap(); - let alice_final_xmr_balance = alice_monero_wallet.0.get_balance_alice().await.unwrap(); - let bob_final_xmr_balance = bob_monero_wallet.0.get_balance_bob().await.unwrap(); - - // Because we create a new wallet when claiming Monero, we can only assert on - // this new wallet owning all of `xmr_amount` after refund - assert_eq!(alice_final_xmr_balance, u64::from(xmr_amount)); - assert_eq!(bob_final_xmr_balance, bob_initial_xmr_balance); - } - - #[tokio::test] - async fn alice_punishes() { - let cli = Cli::default(); - let bitcoind = init_bitcoind(&cli).await; - - // must be bigger than our hardcoded fee of 10_000 - let btc_amount = bitcoin::Amount::from_sat(10_000_000); - let xmr_amount = monero::Amount::from_piconero(1_000_000_000_000); - - let alice_btc_wallet = bitcoin::Wallet::new("alice", &bitcoind.node_url) - .await - .unwrap(); - let bob_btc_wallet = bitcoin::make_wallet("bob", &bitcoind, btc_amount) - .await - .unwrap(); - - let alice_initial_btc_balance = alice_btc_wallet.balance().await.unwrap(); - let bob_initial_btc_balance = bob_btc_wallet.balance().await.unwrap(); - - let redeem_address = alice_btc_wallet.new_address().await.unwrap(); - let punish_address = redeem_address.clone(); - let refund_address = bob_btc_wallet.new_address().await.unwrap(); - - let refund_timelock = 1; - let punish_timelock = 1; - - let alice_state0 = alice::State0::new( - &mut OsRng, - btc_amount, - xmr_amount, - refund_timelock, - punish_timelock, - redeem_address, - punish_address, - ); - let bob_state0 = bob::State0::new( - &mut OsRng, - btc_amount, - xmr_amount, - refund_timelock, - punish_timelock, - refund_address.clone(), - ); - - let alice_message0 = alice_state0.next_message(&mut OsRng); - let bob_message0 = bob_state0.next_message(&mut OsRng); - - let alice_state1 = alice_state0.receive(bob_message0).unwrap(); - let bob_state1 = bob_state0 - .receive(&bob_btc_wallet, alice_message0) - .await - .unwrap(); - - let bob_message1 = bob_state1.next_message(); - let alice_state2 = alice_state1.receive(bob_message1); - let alice_message1 = alice_state2.next_message(); - let bob_state2 = bob_state1.receive(alice_message1).unwrap(); - - let bob_message2 = bob_state2.next_message(); - let alice_state3 = alice_state2.receive(bob_message2).unwrap(); - - let bob_state2b = bob_state2.lock_btc(&bob_btc_wallet).await.unwrap(); - - let alice_state4 = alice_state3 - .watch_for_lock_btc(&alice_btc_wallet) - .await - .unwrap(); - - alice_state4.punish(&alice_btc_wallet).await.unwrap(); - - let alice_final_btc_balance = alice_btc_wallet.balance().await.unwrap(); - let bob_final_btc_balance = bob_btc_wallet.balance().await.unwrap(); - - // lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal - // to TX_FEE - let lock_tx_bitcoin_fee = bob_btc_wallet - .transaction_fee(bob_state2b.tx_lock_id()) - .await - .unwrap(); - - assert_eq!( - alice_final_btc_balance, - alice_initial_btc_balance + btc_amount - Amount::from_sat(2 * TX_FEE) - ); - assert_eq!( - bob_final_btc_balance, - bob_initial_btc_balance - btc_amount - lock_tx_bitcoin_fee - ); - } -} +pub mod transport; diff --git a/xmr-btc/src/monero.rs b/xmr-btc/src/monero.rs index b67caabd..aefe858d 100644 --- a/xmr-btc/src/monero.rs +++ b/xmr-btc/src/monero.rs @@ -1,6 +1,3 @@ -#[cfg(test)] -pub mod wallet; - use std::ops::Add; use anyhow::Result; @@ -16,9 +13,6 @@ pub fn random_private_key(rng: &mut R) -> PrivateKey { PrivateKey::from_scalar(scalar) } -#[cfg(test)] -pub use wallet::{AliceWallet, BobWallet}; - #[derive(Clone, Copy, Debug)] pub struct PrivateViewKey(PrivateKey); @@ -69,6 +63,9 @@ impl Amount { pub fn from_piconero(amount: u64) -> Self { Amount(amount) } + pub fn as_piconero(&self) -> u64 { + self.0 + } } impl From for u64 { @@ -83,8 +80,21 @@ pub struct TransferProof { tx_key: PrivateKey, } +impl TransferProof { + pub fn new(tx_hash: TxHash, tx_key: PrivateKey) -> Self { + Self { tx_hash, tx_key } + } + pub fn tx_hash(&self) -> TxHash { + self.tx_hash.clone() + } + pub fn tx_key(&self) -> PrivateKey { + self.tx_key + } +} + +// TODO: add constructor/ change String to fixed length byte array #[derive(Clone, Debug)] -pub struct TxHash(String); +pub struct TxHash(pub String); impl From for String { fn from(from: TxHash) -> Self { diff --git a/xmr-btc/src/transport.rs b/xmr-btc/src/transport.rs new file mode 100644 index 00000000..abf7eac5 --- /dev/null +++ b/xmr-btc/src/transport.rs @@ -0,0 +1,8 @@ +use anyhow::Result; +use async_trait::async_trait; + +#[async_trait] +pub trait SendReceive { + async fn send_message(&mut self, message: SendMsg) -> Result<()>; + async fn receive_message(&mut self) -> Result; +} diff --git a/xmr-btc/tests/e2e.rs b/xmr-btc/tests/e2e.rs new file mode 100644 index 00000000..bd40ed8a --- /dev/null +++ b/xmr-btc/tests/e2e.rs @@ -0,0 +1,389 @@ +use crate::{ + node::{AliceNode, BobNode}, + transport::Transport, +}; +use bitcoin_harness::Bitcoind; + +use monero_harness::Monero; +use rand::rngs::OsRng; + +use testcontainers::clients::Cli; +use tokio::sync::{ + mpsc, + mpsc::{Receiver, Sender}, +}; +use xmr_btc::{alice, bitcoin, bob, monero}; + +mod node; +mod transport; +mod wallet; + +const TEN_XMR: u64 = 10_000_000_000_000; +const RELATIVE_REFUND_TIMELOCK: u32 = 1; +const RELATIVE_PUNISH_TIMELOCK: u32 = 1; + +pub async fn init_bitcoind(tc_client: &Cli) -> Bitcoind<'_> { + let bitcoind = Bitcoind::new(tc_client, "0.19.1").expect("failed to create bitcoind"); + let _ = bitcoind.init(5).await; + + bitcoind +} + +pub struct InitialBalances { + alice_xmr: u64, + alice_btc: bitcoin::Amount, + bob_xmr: u64, + bob_btc: bitcoin::Amount, +} + +pub struct SwapAmounts { + xmr: monero::Amount, + btc: bitcoin::Amount, +} + +pub fn init_alice_and_bob_transports() -> ( + Transport, + Transport, +) { + let (a_sender, b_receiver): (Sender, Receiver) = + mpsc::channel(5); + let (b_sender, a_receiver): (Sender, Receiver) = mpsc::channel(5); + + let a_transport = Transport { + sender: a_sender, + receiver: a_receiver, + }; + + let b_transport = Transport { + sender: b_sender, + receiver: b_receiver, + }; + + (a_transport, b_transport) +} + +pub async fn init_test<'a>( + monero: &'a Monero<'a>, + bitcoind: &Bitcoind<'_>, +) -> ( + alice::State0, + bob::State0, + AliceNode<'a>, + BobNode<'a>, + InitialBalances, + SwapAmounts, +) { + // must be bigger than our hardcoded fee of 10_000 + let btc_amount = bitcoin::Amount::from_sat(10_000_000); + let xmr_amount = monero::Amount::from_piconero(1_000_000_000_000); + + let swap_amounts = SwapAmounts { + xmr: xmr_amount, + btc: btc_amount, + }; + + let fund_alice = TEN_XMR; + let fund_bob = 0; + monero.init(fund_alice, fund_bob).await.unwrap(); + + let alice_monero_wallet = wallet::monero::AliceWallet(&monero); + let bob_monero_wallet = wallet::monero::BobWallet(&monero); + + let alice_btc_wallet = wallet::bitcoin::Wallet::new("alice", &bitcoind.node_url) + .await + .unwrap(); + let bob_btc_wallet = wallet::bitcoin::make_wallet("bob", &bitcoind, btc_amount) + .await + .unwrap(); + + let (alice_transport, bob_transport) = init_alice_and_bob_transports(); + + let alice = AliceNode::new(alice_transport, alice_btc_wallet, alice_monero_wallet); + + let bob = BobNode::new(bob_transport, bob_btc_wallet, bob_monero_wallet); + + let alice_initial_btc_balance = alice.bitcoin_wallet.balance().await.unwrap(); + let bob_initial_btc_balance = bob.bitcoin_wallet.balance().await.unwrap(); + + let alice_initial_xmr_balance = alice.monero_wallet.0.get_balance_alice().await.unwrap(); + let bob_initial_xmr_balance = bob.monero_wallet.0.get_balance_bob().await.unwrap(); + + let redeem_address = alice.bitcoin_wallet.new_address().await.unwrap(); + let punish_address = redeem_address.clone(); + let refund_address = bob.bitcoin_wallet.new_address().await.unwrap(); + + let alice_state0 = alice::State0::new( + &mut OsRng, + btc_amount, + xmr_amount, + RELATIVE_REFUND_TIMELOCK, + RELATIVE_PUNISH_TIMELOCK, + redeem_address.clone(), + punish_address.clone(), + ); + let bob_state0 = bob::State0::new( + &mut OsRng, + btc_amount, + xmr_amount, + RELATIVE_REFUND_TIMELOCK, + RELATIVE_PUNISH_TIMELOCK, + refund_address.clone(), + ); + let initial_balances = InitialBalances { + alice_xmr: alice_initial_xmr_balance, + alice_btc: alice_initial_btc_balance, + bob_xmr: bob_initial_xmr_balance, + bob_btc: bob_initial_btc_balance, + }; + ( + alice_state0, + bob_state0, + alice, + bob, + initial_balances, + swap_amounts, + ) +} + +#[cfg(test)] +mod tests { + use crate::{ + init_bitcoind, init_test, + node::{run_alice_until, run_bob_until}, + }; + + use futures::future; + use monero_harness::Monero; + use rand::rngs::OsRng; + use std::convert::TryInto; + use testcontainers::clients::Cli; + + use tracing_subscriber::util::SubscriberInitExt; + use xmr_btc::{ + alice, bitcoin, + bitcoin::{Amount, TX_FEE}, + bob, + }; + + #[tokio::test] + async fn happy_path() { + let _guard = tracing_subscriber::fmt() + .with_env_filter("info") + .set_default(); + + let cli = Cli::default(); + let monero = Monero::new(&cli); + let bitcoind = init_bitcoind(&cli).await; + + let ( + alice_state0, + bob_state0, + mut alice_node, + mut bob_node, + initial_balances, + swap_amounts, + ) = init_test(&monero, &bitcoind).await; + + let (alice_state, bob_state) = future::try_join( + run_alice_until( + &mut alice_node, + alice_state0.into(), + alice::is_state6, + &mut OsRng, + ), + run_bob_until(&mut bob_node, bob_state0.into(), bob::is_state5, &mut OsRng), + ) + .await + .unwrap(); + + let alice_state6: alice::State6 = alice_state.try_into().unwrap(); + let bob_state5: bob::State5 = bob_state.try_into().unwrap(); + + let alice_final_btc_balance = alice_node.bitcoin_wallet.balance().await.unwrap(); + let bob_final_btc_balance = bob_node.bitcoin_wallet.balance().await.unwrap(); + + let lock_tx_bitcoin_fee = bob_node + .bitcoin_wallet + .transaction_fee(bob_state5.tx_lock_id()) + .await + .unwrap(); + + assert_eq!( + alice_final_btc_balance, + initial_balances.alice_btc + swap_amounts.btc + - bitcoin::Amount::from_sat(bitcoin::TX_FEE) + ); + assert_eq!( + bob_final_btc_balance, + initial_balances.bob_btc - swap_amounts.btc - lock_tx_bitcoin_fee + ); + + let alice_final_xmr_balance = alice_node + .monero_wallet + .0 + .get_balance_alice() + .await + .unwrap(); + + bob_node + .monero_wallet + .0 + .wait_for_bob_wallet_block_height() + .await + .unwrap(); + + let bob_final_xmr_balance = bob_node.monero_wallet.0.get_balance_bob().await.unwrap(); + + assert_eq!( + alice_final_xmr_balance, + initial_balances.alice_xmr + - u64::from(swap_amounts.xmr) + - u64::from(alice_state6.lock_xmr_fee()) + ); + assert_eq!( + bob_final_xmr_balance, + initial_balances.bob_xmr + u64::from(swap_amounts.xmr) + ); + } + + #[tokio::test] + async fn both_refund() { + let _guard = tracing_subscriber::fmt() + .with_env_filter("info") + .set_default(); + + let cli = Cli::default(); + let monero = Monero::new(&cli); + let bitcoind = init_bitcoind(&cli).await; + + let ( + alice_state0, + bob_state0, + mut alice_node, + mut bob_node, + initial_balances, + swap_amounts, + ) = init_test(&monero, &bitcoind).await; + + let (alice_state, bob_state) = future::try_join( + run_alice_until( + &mut alice_node, + alice_state0.into(), + alice::is_state5, + &mut OsRng, + ), + run_bob_until(&mut bob_node, bob_state0.into(), bob::is_state3, &mut OsRng), + ) + .await + .unwrap(); + + let alice_state5: alice::State5 = alice_state.try_into().unwrap(); + let bob_state3: bob::State3 = bob_state.try_into().unwrap(); + + bob_state3 + .refund_btc(&bob_node.bitcoin_wallet) + .await + .unwrap(); + alice_state5 + .refund_xmr(&alice_node.bitcoin_wallet, &alice_node.monero_wallet) + .await + .unwrap(); + + let alice_final_btc_balance = alice_node.bitcoin_wallet.balance().await.unwrap(); + let bob_final_btc_balance = bob_node.bitcoin_wallet.balance().await.unwrap(); + + // lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal + // to TX_FEE + let lock_tx_bitcoin_fee = bob_node + .bitcoin_wallet + .transaction_fee(bob_state3.tx_lock_id()) + .await + .unwrap(); + + assert_eq!(alice_final_btc_balance, initial_balances.alice_btc); + assert_eq!( + bob_final_btc_balance, + // The 2 * TX_FEE corresponds to tx_refund and tx_cancel. + initial_balances.bob_btc - Amount::from_sat(2 * TX_FEE) - lock_tx_bitcoin_fee + ); + + alice_node + .monero_wallet + .0 + .wait_for_alice_wallet_block_height() + .await + .unwrap(); + let alice_final_xmr_balance = alice_node + .monero_wallet + .0 + .get_balance_alice() + .await + .unwrap(); + let bob_final_xmr_balance = bob_node.monero_wallet.0.get_balance_bob().await.unwrap(); + + // Because we create a new wallet when claiming Monero, we can only assert on + // this new wallet owning all of `xmr_amount` after refund + assert_eq!(alice_final_xmr_balance, u64::from(swap_amounts.xmr)); + assert_eq!(bob_final_xmr_balance, initial_balances.bob_xmr); + } + + #[tokio::test] + async fn alice_punishes() { + let _guard = tracing_subscriber::fmt() + .with_env_filter("info") + .set_default(); + + let cli = Cli::default(); + let monero = Monero::new(&cli); + let bitcoind = init_bitcoind(&cli).await; + + let ( + alice_state0, + bob_state0, + mut alice_node, + mut bob_node, + initial_balances, + swap_amounts, + ) = init_test(&monero, &bitcoind).await; + + let (alice_state, bob_state) = future::try_join( + run_alice_until( + &mut alice_node, + alice_state0.into(), + alice::is_state4, + &mut OsRng, + ), + run_bob_until(&mut bob_node, bob_state0.into(), bob::is_state3, &mut OsRng), + ) + .await + .unwrap(); + + let alice_state4: alice::State4 = alice_state.try_into().unwrap(); + let bob_state3: bob::State3 = bob_state.try_into().unwrap(); + + alice_state4 + .punish(&alice_node.bitcoin_wallet) + .await + .unwrap(); + + let alice_final_btc_balance = alice_node.bitcoin_wallet.balance().await.unwrap(); + let bob_final_btc_balance = bob_node.bitcoin_wallet.balance().await.unwrap(); + + // lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal + // to TX_FEE + let lock_tx_bitcoin_fee = bob_node + .bitcoin_wallet + .transaction_fee(bob_state3.tx_lock_id()) + .await + .unwrap(); + + assert_eq!( + alice_final_btc_balance, + initial_balances.alice_btc + swap_amounts.btc - Amount::from_sat(2 * TX_FEE) + ); + assert_eq!( + bob_final_btc_balance, + initial_balances.bob_btc - swap_amounts.btc - lock_tx_bitcoin_fee + ); + } +} diff --git a/xmr-btc/tests/node.rs b/xmr-btc/tests/node.rs new file mode 100644 index 00000000..74351a64 --- /dev/null +++ b/xmr-btc/tests/node.rs @@ -0,0 +1,92 @@ +use crate::{transport::Transport, wallet}; +use anyhow::Result; +use rand::{CryptoRng, RngCore}; +use xmr_btc::{alice, bob}; + +// TODO: merge this with bob node +// This struct is responsible for I/O +pub struct AliceNode<'a> { + transport: Transport, + pub bitcoin_wallet: wallet::bitcoin::Wallet, + pub monero_wallet: wallet::monero::AliceWallet<'a>, +} + +impl<'a> AliceNode<'a> { + pub fn new( + transport: Transport, + bitcoin_wallet: wallet::bitcoin::Wallet, + monero_wallet: wallet::monero::AliceWallet<'a>, + ) -> AliceNode<'a> { + Self { + transport, + bitcoin_wallet, + monero_wallet, + } + } +} + +pub async fn run_alice_until<'a, R: RngCore + CryptoRng>( + alice: &mut AliceNode<'a>, + initial_state: alice::State, + is_state: fn(&alice::State) -> bool, + rng: &mut R, +) -> Result { + let mut result = initial_state; + loop { + result = alice::next_state( + &alice.bitcoin_wallet, + &alice.monero_wallet, + &mut alice.transport, + result, + rng, + ) + .await?; + if is_state(&result) { + return Ok(result); + } + } +} + +// TODO: merge this with alice node +// This struct is responsible for I/O +pub struct BobNode<'a> { + transport: Transport, + pub bitcoin_wallet: wallet::bitcoin::Wallet, + pub monero_wallet: wallet::monero::BobWallet<'a>, +} + +impl<'a> BobNode<'a> { + pub fn new( + transport: Transport, + bitcoin_wallet: wallet::bitcoin::Wallet, + monero_wallet: wallet::monero::BobWallet<'a>, + ) -> BobNode<'a> { + Self { + transport, + bitcoin_wallet, + monero_wallet, + } + } +} + +pub async fn run_bob_until<'a, R: RngCore + CryptoRng>( + bob: &mut BobNode<'a>, + initial_state: bob::State, + is_state: fn(&bob::State) -> bool, + rng: &mut R, +) -> Result { + let mut result = initial_state; + loop { + result = bob::next_state( + &bob.bitcoin_wallet, + &bob.monero_wallet, + &mut bob.transport, + result, + rng, + ) + .await?; + if is_state(&result) { + return Ok(result); + } + } +} diff --git a/xmr-btc/tests/transport.rs b/xmr-btc/tests/transport.rs new file mode 100644 index 00000000..b4be445d --- /dev/null +++ b/xmr-btc/tests/transport.rs @@ -0,0 +1,56 @@ +use anyhow::{anyhow, Result}; + +use async_trait::async_trait; +use tokio::{ + stream::StreamExt, + sync::mpsc::{Receiver, Sender}, +}; +use xmr_btc::{alice, bob, transport::SendReceive}; + +#[derive(Debug)] +pub struct Transport { + pub sender: Sender, + pub receiver: Receiver, +} + +#[async_trait] +impl SendReceive for Transport { + async fn send_message(&mut self, message: alice::Message) -> Result<()> { + let _ = self + .sender + .send(message) + .await + .map_err(|_| anyhow!("failed to send message"))?; + Ok(()) + } + + async fn receive_message(&mut self) -> Result { + let message = self + .receiver + .next() + .await + .ok_or_else(|| anyhow!("failed to receive message"))?; + Ok(message) + } +} + +#[async_trait] +impl SendReceive for Transport { + async fn send_message(&mut self, message: bob::Message) -> Result<()> { + let _ = self + .sender + .send(message) + .await + .map_err(|_| anyhow!("failed to send message"))?; + Ok(()) + } + + async fn receive_message(&mut self) -> Result { + let message = self + .receiver + .next() + .await + .ok_or_else(|| anyhow!("failed to receive message"))?; + Ok(message) + } +} diff --git a/xmr-btc/src/bitcoin/wallet.rs b/xmr-btc/tests/wallet/bitcoin.rs similarity index 94% rename from xmr-btc/src/bitcoin/wallet.rs rename to xmr-btc/tests/wallet/bitcoin.rs index b357d823..57d936b0 100644 --- a/xmr-btc/src/bitcoin/wallet.rs +++ b/xmr-btc/tests/wallet/bitcoin.rs @@ -1,6 +1,3 @@ -use crate::bitcoin::{ - BroadcastSignedTransaction, BuildTxLockPsbt, GetRawTransaction, SignTxLock, TxLock, -}; use anyhow::Result; use async_trait::async_trait; use bitcoin::{util::psbt::PartiallySignedTransaction, Address, Amount, Transaction, Txid}; @@ -8,6 +5,9 @@ use bitcoin_harness::{bitcoind_rpc::PsbtBase64, Bitcoind}; use reqwest::Url; use std::time::Duration; use tokio::time; +use xmr_btc::bitcoin::{ + BroadcastSignedTransaction, BuildTxLockPsbt, GetRawTransaction, SignTxLock, TxLock, +}; #[derive(Debug)] pub struct Wallet(pub bitcoin_harness::Wallet); @@ -109,7 +109,10 @@ impl BroadcastSignedTransaction for Wallet { #[async_trait] impl GetRawTransaction for Wallet { async fn get_raw_transaction(&self, txid: Txid) -> Result { + // TODO: put into loop instead of delaying + time::delay_for(Duration::from_millis(5000)).await; let tx = self.0.get_raw_transaction(txid).await?; + tracing::info!("{}", tx.txid()); Ok(tx) } diff --git a/xmr-btc/tests/wallet/mod.rs b/xmr-btc/tests/wallet/mod.rs new file mode 100644 index 00000000..c2b89100 --- /dev/null +++ b/xmr-btc/tests/wallet/mod.rs @@ -0,0 +1,2 @@ +pub mod bitcoin; +pub mod monero; diff --git a/xmr-btc/src/monero/wallet.rs b/xmr-btc/tests/wallet/monero.rs similarity index 92% rename from xmr-btc/src/monero/wallet.rs rename to xmr-btc/tests/wallet/monero.rs index 2834f134..6f47d3b4 100644 --- a/xmr-btc/src/monero/wallet.rs +++ b/xmr-btc/tests/wallet/monero.rs @@ -1,12 +1,12 @@ -use crate::monero::{ - Amount, CheckTransfer, ImportOutput, PrivateViewKey, PublicKey, PublicViewKey, Transfer, - TransferProof, TxHash, -}; use anyhow::{bail, Result}; use async_trait::async_trait; use monero::{Address, Network, PrivateKey}; use monero_harness::Monero; use std::str::FromStr; +use xmr_btc::monero::{ + Amount, CheckTransfer, ImportOutput, PrivateViewKey, PublicKey, PublicViewKey, Transfer, + TransferProof, TxHash, +}; #[derive(Debug)] pub struct AliceWallet<'c>(pub &'c Monero<'c>); @@ -24,7 +24,7 @@ impl Transfer for AliceWallet<'_> { let res = self .0 - .transfer_from_alice(amount.0, &destination_address.to_string()) + .transfer_from_alice(amount.as_piconero(), &destination_address.to_string()) .await?; let tx_hash = TxHash(res.tx_hash); @@ -32,7 +32,33 @@ impl Transfer for AliceWallet<'_> { let fee = Amount::from_piconero(res.fee); - Ok((TransferProof { tx_hash, tx_key }, fee)) + Ok((TransferProof::new(tx_hash, tx_key), fee)) + } +} + +#[async_trait] +impl ImportOutput for AliceWallet<'_> { + async fn import_output( + &self, + private_spend_key: PrivateKey, + private_view_key: PrivateViewKey, + ) -> Result<()> { + let public_spend_key = PublicKey::from_private_key(&private_spend_key); + let public_view_key = PublicKey::from_private_key(&private_view_key.into()); + + let address = Address::standard(Network::Mainnet, public_spend_key, public_view_key); + + let _ = self + .0 + .alice_wallet_rpc_client() + .generate_from_keys( + &address.to_string(), + &private_spend_key.to_string(), + &PrivateKey::from(private_view_key).to_string(), + ) + .await?; + + Ok(()) } } @@ -54,8 +80,8 @@ impl CheckTransfer for BobWallet<'_> { let res = cli .check_tx_key( - &String::from(transfer_proof.tx_hash), - &transfer_proof.tx_key.to_string(), + &String::from(transfer_proof.tx_hash()), + &transfer_proof.tx_key().to_string(), &address.to_string(), ) .await?; @@ -97,29 +123,3 @@ impl ImportOutput for BobWallet<'_> { Ok(()) } } - -#[async_trait] -impl ImportOutput for AliceWallet<'_> { - async fn import_output( - &self, - private_spend_key: PrivateKey, - private_view_key: PrivateViewKey, - ) -> Result<()> { - let public_spend_key = PublicKey::from_private_key(&private_spend_key); - let public_view_key = PublicKey::from_private_key(&private_view_key.into()); - - let address = Address::standard(Network::Mainnet, public_spend_key, public_view_key); - - let _ = self - .0 - .alice_wallet_rpc_client() - .generate_from_keys( - &address.to_string(), - &private_spend_key.to_string(), - &PrivateKey::from(private_view_key).to_string(), - ) - .await?; - - Ok(()) - } -} From 148e49a959d7bbed97a0780fa0d894879637507d Mon Sep 17 00:00:00 2001 From: rishflab Date: Wed, 7 Oct 2020 11:26:38 +1100 Subject: [PATCH 02/20] Remove redundant lifetime parameter --- xmr-btc/src/alice.rs | 1 - xmr-btc/src/bob.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index f2aa9ad5..eb5edd8d 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -17,7 +17,6 @@ pub mod message; pub use message::{Message, Message0, Message1, Message2, UnexpectedMessage}; pub async fn next_state< - 'a, R: RngCore + CryptoRng, B: GetRawTransaction + BroadcastSignedTransaction, M: ImportOutput + Transfer, diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index 62841c5b..d8bdd18d 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -21,7 +21,6 @@ pub mod message; pub use message::{Message, Message0, Message1, Message2, Message3, UnexpectedMessage}; pub async fn next_state< - 'a, R: RngCore + CryptoRng, B: GetRawTransaction + SignTxLock + BuildTxLockPsbt + BroadcastSignedTransaction, M: ImportOutput + CheckTransfer, From f6f4ec2bdbcd9cc34b363cd18c863908e5298ff7 Mon Sep 17 00:00:00 2001 From: rishflab Date: Wed, 7 Oct 2020 11:32:13 +1100 Subject: [PATCH 03/20] Fix dependencies --- xmr-btc/Cargo.toml | 3 ++- xmr-btc/tests/e2e.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/xmr-btc/Cargo.toml b/xmr-btc/Cargo.toml index eeed8dae..65ce3ae5 100644 --- a/xmr-btc/Cargo.toml +++ b/xmr-btc/Cargo.toml @@ -27,5 +27,6 @@ monero-harness = { path = "../monero-harness" } reqwest = { version = "0.10", default-features = false } testcontainers = "0.10" tokio = { version = "0.2", default-features = false, features = ["blocking", "macros", "rt-core", "time", "rt-threaded"] } -tracing-subscriber = "0.2.12" +tracing-subscriber = "0.2" tracing = "0.1" +futures = "0.3" diff --git a/xmr-btc/tests/e2e.rs b/xmr-btc/tests/e2e.rs index bd40ed8a..da1fb70b 100644 --- a/xmr-btc/tests/e2e.rs +++ b/xmr-btc/tests/e2e.rs @@ -152,12 +152,12 @@ mod tests { node::{run_alice_until, run_bob_until}, }; - use futures::future; use monero_harness::Monero; use rand::rngs::OsRng; - use std::convert::TryInto; use testcontainers::clients::Cli; + use futures::future; + use std::convert::TryInto; use tracing_subscriber::util::SubscriberInitExt; use xmr_btc::{ alice, bitcoin, From 4e031ff9a27f44078def03ed7663a4a711cdb49d Mon Sep 17 00:00:00 2001 From: rishflab Date: Thu, 8 Oct 2020 10:53:30 +1100 Subject: [PATCH 04/20] Loop on blockchain call instead of delaying Previously there was a delay making a get raw transaction call to give some time for a transaction to be confirmed on the blockchain. This has been replaced with a loop that waits until the call is succesful. --- xmr-btc/src/alice.rs | 15 ++++++++------- xmr-btc/src/bitcoin.rs | 4 ++-- xmr-btc/src/bob.rs | 11 +++++++---- xmr-btc/tests/wallet/bitcoin.rs | 18 +++++++++--------- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index eb5edd8d..4a017b65 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -4,7 +4,7 @@ use rand::{CryptoRng, RngCore}; use crate::{ bitcoin, - bitcoin::{BroadcastSignedTransaction, GetRawTransaction}, + bitcoin::{BroadcastSignedTransaction, WatchForRawTransaction}, bob, monero, monero::{ImportOutput, Transfer}, transport::SendReceive, @@ -18,7 +18,7 @@ pub use message::{Message, Message0, Message1, Message2, UnexpectedMessage}; pub async fn next_state< R: RngCore + CryptoRng, - B: GetRawTransaction + BroadcastSignedTransaction, + B: WatchForRawTransaction + BroadcastSignedTransaction, M: ImportOutput + Transfer, T: SendReceive, >( @@ -48,7 +48,6 @@ pub async fn next_state< State::State2(state2) => { let bob_message2: bob::Message2 = transport.receive_message().await?.try_into()?; let state3 = state2.receive(bob_message2)?; - tokio::time::delay_for(std::time::Duration::new(5, 0)).await; Ok(state3.into()) } State::State3(state3) => { @@ -394,11 +393,11 @@ pub struct State3 { impl State3 { pub async fn watch_for_lock_btc(self, bitcoin_wallet: &W) -> Result where - W: bitcoin::GetRawTransaction, + W: bitcoin::WatchForRawTransaction, { tracing::info!("{}", self.tx_lock.txid()); let tx = bitcoin_wallet - .get_raw_transaction(self.tx_lock.txid()) + .watch_for_raw_transaction(self.tx_lock.txid()) .await?; tracing::info!("{}", tx.txid()); @@ -581,7 +580,7 @@ impl State5 { // watch for refund on btc, recover s_b and refund xmr pub async fn refund_xmr(self, bitcoin_wallet: &B, monero_wallet: &M) -> Result<()> where - B: GetRawTransaction, + B: WatchForRawTransaction, M: ImportOutput, { let tx_cancel = bitcoin::TxCancel::new( @@ -595,7 +594,9 @@ impl State5 { let tx_refund_encsig = self.a.encsign(self.S_b_bitcoin.clone(), tx_refund.digest()); - let tx_refund_candidate = bitcoin_wallet.get_raw_transaction(tx_refund.txid()).await?; + let tx_refund_candidate = bitcoin_wallet + .watch_for_raw_transaction(tx_refund.txid()) + .await?; let tx_refund_sig = tx_refund.extract_signature_by_key(tx_refund_candidate, self.a.public())?; diff --git a/xmr-btc/src/bitcoin.rs b/xmr-btc/src/bitcoin.rs index e903bb75..1a6dec22 100644 --- a/xmr-btc/src/bitcoin.rs +++ b/xmr-btc/src/bitcoin.rs @@ -188,8 +188,8 @@ pub trait BroadcastSignedTransaction { } #[async_trait] -pub trait GetRawTransaction { - async fn get_raw_transaction(&self, txid: Txid) -> Result; +pub trait WatchForRawTransaction { + async fn watch_for_raw_transaction(&self, txid: Txid) -> Result; } pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Result { diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index d8bdd18d..b9109e09 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -1,7 +1,8 @@ use crate::{ alice, bitcoin::{ - self, BroadcastSignedTransaction, BuildTxLockPsbt, GetRawTransaction, SignTxLock, TxCancel, + self, BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, TxCancel, + WatchForRawTransaction, }, monero, monero::{CheckTransfer, ImportOutput}, @@ -22,7 +23,7 @@ pub use message::{Message, Message0, Message1, Message2, Message3, UnexpectedMes pub async fn next_state< R: RngCore + CryptoRng, - B: GetRawTransaction + SignTxLock + BuildTxLockPsbt + BroadcastSignedTransaction, + B: WatchForRawTransaction + SignTxLock + BuildTxLockPsbt + BroadcastSignedTransaction, M: ImportOutput + CheckTransfer, T: SendReceive, >( @@ -500,12 +501,14 @@ impl State4 { pub async fn watch_for_redeem_btc(self, bitcoin_wallet: &W) -> Result where - W: GetRawTransaction, + W: WatchForRawTransaction, { let tx_redeem = bitcoin::TxRedeem::new(&self.tx_lock, &self.redeem_address); let tx_redeem_encsig = self.b.encsign(self.S_a_bitcoin.clone(), tx_redeem.digest()); - let tx_redeem_candidate = bitcoin_wallet.get_raw_transaction(tx_redeem.txid()).await?; + let tx_redeem_candidate = bitcoin_wallet + .watch_for_raw_transaction(tx_redeem.txid()) + .await?; let tx_redeem_sig = tx_redeem.extract_signature_by_key(tx_redeem_candidate, self.b.public())?; diff --git a/xmr-btc/tests/wallet/bitcoin.rs b/xmr-btc/tests/wallet/bitcoin.rs index 57d936b0..524baaa4 100644 --- a/xmr-btc/tests/wallet/bitcoin.rs +++ b/xmr-btc/tests/wallet/bitcoin.rs @@ -6,7 +6,7 @@ use reqwest::Url; use std::time::Duration; use tokio::time; use xmr_btc::bitcoin::{ - BroadcastSignedTransaction, BuildTxLockPsbt, GetRawTransaction, SignTxLock, TxLock, + BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, TxLock, WatchForRawTransaction, }; #[derive(Debug)] @@ -107,13 +107,13 @@ impl BroadcastSignedTransaction for Wallet { } #[async_trait] -impl GetRawTransaction for Wallet { - async fn get_raw_transaction(&self, txid: Txid) -> Result { - // TODO: put into loop instead of delaying - time::delay_for(Duration::from_millis(5000)).await; - let tx = self.0.get_raw_transaction(txid).await?; - tracing::info!("{}", tx.txid()); - - Ok(tx) +impl WatchForRawTransaction for Wallet { + async fn watch_for_raw_transaction(&self, txid: Txid) -> Result { + loop { + if let Ok(tx) = self.0.get_raw_transaction(txid).await { + return Ok(tx); + } + time::delay_for(Duration::from_millis(200)).await; + } } } From 25edd90fb054ce81bea08ad312fa99632a2aeddd Mon Sep 17 00:00:00 2001 From: rishflab Date: Thu, 8 Oct 2020 11:07:37 +1100 Subject: [PATCH 05/20] Remove empty lines between imports --- xmr-btc/src/alice.rs | 11 ++++++----- xmr-btc/src/alice/message.rs | 6 ++---- xmr-btc/src/bitcoin.rs | 4 ++-- xmr-btc/src/bob/message.rs | 1 - xmr-btc/src/monero.rs | 6 ++---- xmr-btc/tests/e2e.rs | 2 -- xmr-btc/tests/transport.rs | 1 - 7 files changed, 12 insertions(+), 19 deletions(-) diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 4a017b65..07ed68e0 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -1,7 +1,3 @@ -use anyhow::{anyhow, Result}; -use ecdsa_fun::adaptor::{Adaptor, EncryptedSignature}; -use rand::{CryptoRng, RngCore}; - use crate::{ bitcoin, bitcoin::{BroadcastSignedTransaction, WatchForRawTransaction}, @@ -9,7 +5,12 @@ use crate::{ monero::{ImportOutput, Transfer}, transport::SendReceive, }; -use ecdsa_fun::nonce::Deterministic; +use anyhow::{anyhow, Result}; +use ecdsa_fun::{ + adaptor::{Adaptor, EncryptedSignature}, + nonce::Deterministic, +}; +use rand::{CryptoRng, RngCore}; use sha2::Sha256; use std::convert::{TryFrom, TryInto}; diff --git a/xmr-btc/src/alice/message.rs b/xmr-btc/src/alice/message.rs index 5901a050..89965f81 100644 --- a/xmr-btc/src/alice/message.rs +++ b/xmr-btc/src/alice/message.rs @@ -1,10 +1,8 @@ use anyhow::Result; -use ecdsa_fun::adaptor::EncryptedSignature; +use ecdsa_fun::{adaptor::EncryptedSignature, Signature}; +use std::convert::TryFrom; use crate::{bitcoin, monero}; -use ecdsa_fun::Signature; - -use std::convert::TryFrom; #[derive(Debug)] pub enum Message { diff --git a/xmr-btc/src/bitcoin.rs b/xmr-btc/src/bitcoin.rs index 1a6dec22..5bad1f9d 100644 --- a/xmr-btc/src/bitcoin.rs +++ b/xmr-btc/src/bitcoin.rs @@ -8,6 +8,7 @@ use bitcoin::{ util::psbt::PartiallySignedTransaction, SigHash, Transaction, }; +pub use bitcoin::{Address, Amount, OutPoint, Txid}; use ecdsa_fun::{ adaptor::Adaptor, fun::{ @@ -17,14 +18,13 @@ use ecdsa_fun::{ nonce::Deterministic, ECDSA, }; +pub use ecdsa_fun::{adaptor::EncryptedSignature, Signature}; use miniscript::{Descriptor, Segwitv0}; use rand::{CryptoRng, RngCore}; use sha2::Sha256; use std::str::FromStr; pub use crate::bitcoin::transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxRefund}; -pub use bitcoin::{Address, Amount, OutPoint, Txid}; -pub use ecdsa_fun::{adaptor::EncryptedSignature, Signature}; pub const TX_FEE: u64 = 10_000; diff --git a/xmr-btc/src/bob/message.rs b/xmr-btc/src/bob/message.rs index fe767a46..8164dbd6 100644 --- a/xmr-btc/src/bob/message.rs +++ b/xmr-btc/src/bob/message.rs @@ -1,7 +1,6 @@ use crate::{bitcoin, monero}; use anyhow::Result; use ecdsa_fun::{adaptor::EncryptedSignature, Signature}; - use std::convert::TryFrom; #[derive(Debug)] diff --git a/xmr-btc/src/monero.rs b/xmr-btc/src/monero.rs index aefe858d..d4832f1b 100644 --- a/xmr-btc/src/monero.rs +++ b/xmr-btc/src/monero.rs @@ -1,11 +1,9 @@ -use std::ops::Add; - use anyhow::Result; use async_trait::async_trait; -use rand::{CryptoRng, RngCore}; - pub use curve25519_dalek::scalar::Scalar; pub use monero::{Address, PrivateKey, PublicKey}; +use rand::{CryptoRng, RngCore}; +use std::ops::Add; pub fn random_private_key(rng: &mut R) -> PrivateKey { let scalar = Scalar::random(rng); diff --git a/xmr-btc/tests/e2e.rs b/xmr-btc/tests/e2e.rs index da1fb70b..b0174cec 100644 --- a/xmr-btc/tests/e2e.rs +++ b/xmr-btc/tests/e2e.rs @@ -3,10 +3,8 @@ use crate::{ transport::Transport, }; use bitcoin_harness::Bitcoind; - use monero_harness::Monero; use rand::rngs::OsRng; - use testcontainers::clients::Cli; use tokio::sync::{ mpsc, diff --git a/xmr-btc/tests/transport.rs b/xmr-btc/tests/transport.rs index b4be445d..f69b2683 100644 --- a/xmr-btc/tests/transport.rs +++ b/xmr-btc/tests/transport.rs @@ -1,5 +1,4 @@ use anyhow::{anyhow, Result}; - use async_trait::async_trait; use tokio::{ stream::StreamExt, From d01c05e5f529398354e6e569b57edbc7c2349070 Mon Sep 17 00:00:00 2001 From: rishflab Date: Thu, 8 Oct 2020 11:08:48 +1100 Subject: [PATCH 06/20] Use intellij rust gitignore --- .gitignore | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/.gitignore b/.gitignore index 088ba6ba..d89bb47f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,148 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/rust,clion+all,emacs +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,clion+all,emacs + +### CLion+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### CLion+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Emacs ### +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +# network security +/network-security.data + + +### Rust ### # Generated by Cargo # will have compiled files and executables /target/ @@ -8,3 +153,5 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk + +# End of https://www.toptal.com/developers/gitignore/api/rust,clion+all,emacs From 213034dc1f132283bc1b61d7510dc0032e7fb68d Mon Sep 17 00:00:00 2001 From: rishflab Date: Thu, 8 Oct 2020 11:21:21 +1100 Subject: [PATCH 07/20] Remove unused clone from states --- xmr-btc/Cargo.toml | 1 - xmr-btc/src/alice.rs | 16 ++++++++-------- xmr-btc/src/bob.rs | 13 +++++-------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/xmr-btc/Cargo.toml b/xmr-btc/Cargo.toml index 65ce3ae5..c3b4c0ea 100644 --- a/xmr-btc/Cargo.toml +++ b/xmr-btc/Cargo.toml @@ -17,7 +17,6 @@ monero = "0.9" rand = "0.7" sha2 = "0.9" thiserror = "1" -tokio = { version = "0.2", default-features = false, features = ["time"] } tracing = "0.1" [dev-dependencies] diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 07ed68e0..4bbb04aa 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -76,7 +76,7 @@ pub async fn next_state< } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum State { State0(State0), State1(State1), @@ -172,7 +172,7 @@ impl State { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct State0 { a: bitcoin::SecretKey, s_a: cross_curve_dleq::Scalar, @@ -258,7 +258,7 @@ impl State0 { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct State1 { a: bitcoin::SecretKey, B: bitcoin::PublicKey, @@ -296,7 +296,7 @@ impl State1 { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct State2 { a: bitcoin::SecretKey, B: bitcoin::PublicKey, @@ -371,7 +371,7 @@ impl State2 { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct State3 { a: bitcoin::SecretKey, B: bitcoin::PublicKey, @@ -424,7 +424,7 @@ impl State3 { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct State4 { a: bitcoin::SecretKey, B: bitcoin::PublicKey, @@ -527,7 +527,7 @@ impl State4 { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct State5 { a: bitcoin::SecretKey, B: bitcoin::PublicKey, @@ -618,7 +618,7 @@ impl State5 { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct State6 { a: bitcoin::SecretKey, B: bitcoin::PublicKey, diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index b9109e09..53bf1431 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -58,16 +58,13 @@ pub async fn next_state< } State::State3(state3) => { let message2: alice::Message2 = transport.receive_message().await?.try_into()?; - let state4 = state3.watch_for_lock_xmr(monero_wallet, message2).await?; tracing::info!("bob has seen that alice has locked xmr"); Ok(state4.into()) } State::State4(state4) => { transport.send_message(state4.next_message().into()).await?; - tracing::info!("bob is watching for redeem_btc"); - tokio::time::delay_for(std::time::Duration::new(5, 0)).await; let state5 = state4.watch_for_redeem_btc(bitcoin_wallet).await?; tracing::info!("bob has seen that alice has redeemed btc"); state5.claim_xmr(monero_wallet).await?; @@ -231,7 +228,7 @@ impl State0 { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct State1 { A: bitcoin::PublicKey, b: bitcoin::SecretKey, @@ -294,7 +291,7 @@ impl State1 { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct State2 { A: bitcoin::PublicKey, b: bitcoin::SecretKey, @@ -365,7 +362,7 @@ impl State2 { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct State3 { A: bitcoin::PublicKey, b: bitcoin::SecretKey, @@ -471,7 +468,7 @@ impl State3 { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct State4 { A: bitcoin::PublicKey, b: bitcoin::SecretKey, @@ -538,7 +535,7 @@ impl State4 { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct State5 { A: bitcoin::PublicKey, b: bitcoin::SecretKey, From 36ed3a0b8b917f17c39fd3c39c51a267d36f5164 Mon Sep 17 00:00:00 2001 From: rishflab Date: Thu, 8 Oct 2020 11:27:54 +1100 Subject: [PATCH 08/20] Rename ImportOutput to reflect purpose --- xmr-btc/src/alice.rs | 8 ++++---- xmr-btc/src/bob.rs | 10 ++++++---- xmr-btc/src/monero.rs | 4 ++-- xmr-btc/tests/wallet/monero.rs | 12 ++++++------ 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 4bbb04aa..d6ecbc67 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -2,7 +2,7 @@ use crate::{ bitcoin, bitcoin::{BroadcastSignedTransaction, WatchForRawTransaction}, bob, monero, - monero::{ImportOutput, Transfer}, + monero::{CreateWalletForOutput, Transfer}, transport::SendReceive, }; use anyhow::{anyhow, Result}; @@ -20,7 +20,7 @@ pub use message::{Message, Message0, Message1, Message2, UnexpectedMessage}; pub async fn next_state< R: RngCore + CryptoRng, B: WatchForRawTransaction + BroadcastSignedTransaction, - M: ImportOutput + Transfer, + M: CreateWalletForOutput + Transfer, T: SendReceive, >( bitcoin_wallet: &B, @@ -582,7 +582,7 @@ impl State5 { pub async fn refund_xmr(self, bitcoin_wallet: &B, monero_wallet: &M) -> Result<()> where B: WatchForRawTransaction, - M: ImportOutput, + M: CreateWalletForOutput, { let tx_cancel = bitcoin::TxCancel::new( &self.tx_lock, @@ -611,7 +611,7 @@ impl State5 { // NOTE: This actually generates and opens a new wallet, closing the currently // open one. monero_wallet - .import_output(monero::PrivateKey::from_scalar(s), self.v) + .create_and_load_wallet_for_output(monero::PrivateKey::from_scalar(s), self.v) .await?; Ok(()) diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index 53bf1431..44a8a407 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -5,7 +5,7 @@ use crate::{ WatchForRawTransaction, }, monero, - monero::{CheckTransfer, ImportOutput}, + monero::{CheckTransfer, CreateWalletForOutput}, transport::SendReceive, }; use anyhow::{anyhow, Result}; @@ -24,7 +24,7 @@ pub use message::{Message, Message0, Message1, Message2, Message3, UnexpectedMes pub async fn next_state< R: RngCore + CryptoRng, B: WatchForRawTransaction + SignTxLock + BuildTxLockPsbt + BroadcastSignedTransaction, - M: ImportOutput + CheckTransfer, + M: CreateWalletForOutput + CheckTransfer, T: SendReceive, >( bitcoin_wallet: &B, @@ -559,7 +559,7 @@ pub struct State5 { impl State5 { pub async fn claim_xmr(&self, monero_wallet: &W) -> Result<()> where - W: monero::ImportOutput, + W: monero::CreateWalletForOutput, { let s_b = monero::PrivateKey { scalar: self.s_b.into_ed25519(), @@ -569,7 +569,9 @@ impl State5 { // NOTE: This actually generates and opens a new wallet, closing the currently // open one. - monero_wallet.import_output(s, self.v).await?; + monero_wallet + .create_and_load_wallet_for_output(s, self.v) + .await?; Ok(()) } diff --git a/xmr-btc/src/monero.rs b/xmr-btc/src/monero.rs index d4832f1b..459fa708 100644 --- a/xmr-btc/src/monero.rs +++ b/xmr-btc/src/monero.rs @@ -122,8 +122,8 @@ pub trait CheckTransfer { } #[async_trait] -pub trait ImportOutput { - async fn import_output( +pub trait CreateWalletForOutput { + async fn create_and_load_wallet_for_output( &self, private_spend_key: PrivateKey, private_view_key: PrivateViewKey, diff --git a/xmr-btc/tests/wallet/monero.rs b/xmr-btc/tests/wallet/monero.rs index 6f47d3b4..ca5e9039 100644 --- a/xmr-btc/tests/wallet/monero.rs +++ b/xmr-btc/tests/wallet/monero.rs @@ -4,8 +4,8 @@ use monero::{Address, Network, PrivateKey}; use monero_harness::Monero; use std::str::FromStr; use xmr_btc::monero::{ - Amount, CheckTransfer, ImportOutput, PrivateViewKey, PublicKey, PublicViewKey, Transfer, - TransferProof, TxHash, + Amount, CheckTransfer, CreateWalletForOutput, PrivateViewKey, PublicKey, PublicViewKey, + Transfer, TransferProof, TxHash, }; #[derive(Debug)] @@ -37,8 +37,8 @@ impl Transfer for AliceWallet<'_> { } #[async_trait] -impl ImportOutput for AliceWallet<'_> { - async fn import_output( +impl CreateWalletForOutput for AliceWallet<'_> { + async fn create_and_load_wallet_for_output( &self, private_spend_key: PrivateKey, private_view_key: PrivateViewKey, @@ -99,8 +99,8 @@ impl CheckTransfer for BobWallet<'_> { } #[async_trait] -impl ImportOutput for BobWallet<'_> { - async fn import_output( +impl CreateWalletForOutput for BobWallet<'_> { + async fn create_and_load_wallet_for_output( &self, private_spend_key: PrivateKey, private_view_key: PrivateViewKey, From a759f39b1d94ffce7fdc9d2b17949e4a7adcd029 Mon Sep 17 00:00:00 2001 From: rishflab Date: Thu, 8 Oct 2020 11:49:53 +1100 Subject: [PATCH 09/20] Fix module ambiguity in tests --- xmr-btc/tests/e2e.rs | 11 +++++------ xmr-btc/tests/harness/mod.rs | 3 +++ xmr-btc/tests/{ => harness}/node.rs | 2 +- xmr-btc/tests/{ => harness}/transport.rs | 0 xmr-btc/tests/{ => harness}/wallet/bitcoin.rs | 0 xmr-btc/tests/{ => harness}/wallet/mod.rs | 0 xmr-btc/tests/{ => harness}/wallet/monero.rs | 0 7 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 xmr-btc/tests/harness/mod.rs rename xmr-btc/tests/{ => harness}/node.rs (97%) rename xmr-btc/tests/{ => harness}/transport.rs (100%) rename xmr-btc/tests/{ => harness}/wallet/bitcoin.rs (100%) rename xmr-btc/tests/{ => harness}/wallet/mod.rs (100%) rename xmr-btc/tests/{ => harness}/wallet/monero.rs (100%) diff --git a/xmr-btc/tests/e2e.rs b/xmr-btc/tests/e2e.rs index b0174cec..6693ce00 100644 --- a/xmr-btc/tests/e2e.rs +++ b/xmr-btc/tests/e2e.rs @@ -1,8 +1,9 @@ -use crate::{ +use crate::harness::wallet; +use bitcoin_harness::Bitcoind; +use harness::{ node::{AliceNode, BobNode}, transport::Transport, }; -use bitcoin_harness::Bitcoind; use monero_harness::Monero; use rand::rngs::OsRng; use testcontainers::clients::Cli; @@ -12,9 +13,7 @@ use tokio::sync::{ }; use xmr_btc::{alice, bitcoin, bob, monero}; -mod node; -mod transport; -mod wallet; +mod harness; const TEN_XMR: u64 = 10_000_000_000_000; const RELATIVE_REFUND_TIMELOCK: u32 = 1; @@ -146,8 +145,8 @@ pub async fn init_test<'a>( #[cfg(test)] mod tests { use crate::{ + harness::node::{run_alice_until, run_bob_until}, init_bitcoind, init_test, - node::{run_alice_until, run_bob_until}, }; use monero_harness::Monero; diff --git a/xmr-btc/tests/harness/mod.rs b/xmr-btc/tests/harness/mod.rs new file mode 100644 index 00000000..38d3808a --- /dev/null +++ b/xmr-btc/tests/harness/mod.rs @@ -0,0 +1,3 @@ +pub mod node; +pub mod transport; +pub mod wallet; diff --git a/xmr-btc/tests/node.rs b/xmr-btc/tests/harness/node.rs similarity index 97% rename from xmr-btc/tests/node.rs rename to xmr-btc/tests/harness/node.rs index 74351a64..88ee0a6a 100644 --- a/xmr-btc/tests/node.rs +++ b/xmr-btc/tests/harness/node.rs @@ -1,4 +1,4 @@ -use crate::{transport::Transport, wallet}; +use crate::harness::{transport::Transport, wallet}; use anyhow::Result; use rand::{CryptoRng, RngCore}; use xmr_btc::{alice, bob}; diff --git a/xmr-btc/tests/transport.rs b/xmr-btc/tests/harness/transport.rs similarity index 100% rename from xmr-btc/tests/transport.rs rename to xmr-btc/tests/harness/transport.rs diff --git a/xmr-btc/tests/wallet/bitcoin.rs b/xmr-btc/tests/harness/wallet/bitcoin.rs similarity index 100% rename from xmr-btc/tests/wallet/bitcoin.rs rename to xmr-btc/tests/harness/wallet/bitcoin.rs diff --git a/xmr-btc/tests/wallet/mod.rs b/xmr-btc/tests/harness/wallet/mod.rs similarity index 100% rename from xmr-btc/tests/wallet/mod.rs rename to xmr-btc/tests/harness/wallet/mod.rs diff --git a/xmr-btc/tests/wallet/monero.rs b/xmr-btc/tests/harness/wallet/monero.rs similarity index 100% rename from xmr-btc/tests/wallet/monero.rs rename to xmr-btc/tests/harness/wallet/monero.rs From 697e1195cd804e49546587536221e89e1d466bd0 Mon Sep 17 00:00:00 2001 From: rishflab Date: Thu, 8 Oct 2020 16:07:49 +1100 Subject: [PATCH 10/20] Fix Cargo.toml fmt --- xmr-btc/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xmr-btc/Cargo.toml b/xmr-btc/Cargo.toml index c3b4c0ea..0160696c 100644 --- a/xmr-btc/Cargo.toml +++ b/xmr-btc/Cargo.toml @@ -22,10 +22,10 @@ tracing = "0.1" [dev-dependencies] base64 = "0.12" bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "d402b36d3d6406150e3bfb71492ff4a0a7cb290e" } +futures = "0.3" monero-harness = { path = "../monero-harness" } reqwest = { version = "0.10", default-features = false } testcontainers = "0.10" tokio = { version = "0.2", default-features = false, features = ["blocking", "macros", "rt-core", "time", "rt-threaded"] } -tracing-subscriber = "0.2" tracing = "0.1" -futures = "0.3" +tracing-subscriber = "0.2" From 6b7193b974fc941d278319d0dec63a0a5791f0ee Mon Sep 17 00:00:00 2001 From: rishflab Date: Thu, 8 Oct 2020 16:18:34 +1100 Subject: [PATCH 11/20] Fix clippy warnings --- xmr-btc/src/alice.rs | 18 +++++------------- xmr-btc/src/bob.rs | 10 ++-------- xmr-btc/tests/e2e.rs | 2 +- 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index d6ecbc67..6cdf75d8 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -69,13 +69,14 @@ pub async fn next_state< let state6 = state5.receive(message3); tracing::info!("alice has received bob message 3"); tracing::info!("alice is redeeming btc"); - state6.redeem_btc(bitcoin_wallet).await.unwrap(); + state6.redeem_btc(bitcoin_wallet).await?; Ok(state6.into()) } State::State6(state6) => Ok(state6.into()), } } +#[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum State { State0(State0), @@ -89,24 +90,15 @@ pub enum State { // TODO: use macro or generics pub fn is_state4(state: &State) -> bool { - match state { - State::State4 { .. } => true, - _ => false, - } + matches!(state, State::State4 { .. }) } // TODO: use macro or generics pub fn is_state5(state: &State) -> bool { - match state { - State::State5 { .. } => true, - _ => false, - } + matches!(state, State::State5 { .. }) } // TODO: use macro or generics pub fn is_state6(state: &State) -> bool { - match state { - State::State6 { .. } => true, - _ => false, - } + matches!(state, State::State6 { .. }) } macro_rules! impl_try_from_parent_state { diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index 44a8a407..f1b28905 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -126,18 +126,12 @@ impl_from_child_state!(State5); // TODO: use macro or generics pub fn is_state5(state: &State) -> bool { - match state { - State::State5 { .. } => true, - _ => false, - } + matches!(state, State::State5 { .. }) } // TODO: use macro or generics pub fn is_state3(state: &State) -> bool { - match state { - State::State3 { .. } => true, - _ => false, - } + matches!(state, State::State3 { .. }) } #[derive(Debug)] diff --git a/xmr-btc/tests/e2e.rs b/xmr-btc/tests/e2e.rs index 6693ce00..c9c7f519 100644 --- a/xmr-btc/tests/e2e.rs +++ b/xmr-btc/tests/e2e.rs @@ -124,7 +124,7 @@ pub async fn init_test<'a>( xmr_amount, RELATIVE_REFUND_TIMELOCK, RELATIVE_PUNISH_TIMELOCK, - refund_address.clone(), + refund_address, ); let initial_balances = InitialBalances { alice_xmr: alice_initial_xmr_balance, From 154b3edcf9ceaa0d4f68774a4273bf8d6f46de0b Mon Sep 17 00:00:00 2001 From: rishflab Date: Thu, 8 Oct 2020 17:00:31 +1100 Subject: [PATCH 12/20] Split SendReceive into seperate traits --- xmr-btc/src/alice.rs | 4 ++-- xmr-btc/src/bob.rs | 4 ++-- xmr-btc/src/transport.rs | 6 +++++- xmr-btc/tests/harness/transport.rs | 15 ++++++++++++--- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 6cdf75d8..ce311c8d 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -3,7 +3,7 @@ use crate::{ bitcoin::{BroadcastSignedTransaction, WatchForRawTransaction}, bob, monero, monero::{CreateWalletForOutput, Transfer}, - transport::SendReceive, + transport::{Receive, Send}, }; use anyhow::{anyhow, Result}; use ecdsa_fun::{ @@ -21,7 +21,7 @@ pub async fn next_state< R: RngCore + CryptoRng, B: WatchForRawTransaction + BroadcastSignedTransaction, M: CreateWalletForOutput + Transfer, - T: SendReceive, + T: Send + Receive, >( bitcoin_wallet: &B, monero_wallet: &M, diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index f1b28905..d0809678 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -6,7 +6,7 @@ use crate::{ }, monero, monero::{CheckTransfer, CreateWalletForOutput}, - transport::SendReceive, + transport::{Receive, Send}, }; use anyhow::{anyhow, Result}; use ecdsa_fun::{ @@ -25,7 +25,7 @@ pub async fn next_state< R: RngCore + CryptoRng, B: WatchForRawTransaction + SignTxLock + BuildTxLockPsbt + BroadcastSignedTransaction, M: CreateWalletForOutput + CheckTransfer, - T: SendReceive, + T: Send + Receive, >( bitcoin_wallet: &B, monero_wallet: &M, diff --git a/xmr-btc/src/transport.rs b/xmr-btc/src/transport.rs index abf7eac5..2a7db76a 100644 --- a/xmr-btc/src/transport.rs +++ b/xmr-btc/src/transport.rs @@ -2,7 +2,11 @@ use anyhow::Result; use async_trait::async_trait; #[async_trait] -pub trait SendReceive { +pub trait Send { async fn send_message(&mut self, message: SendMsg) -> Result<()>; +} + +#[async_trait] +pub trait Receive { async fn receive_message(&mut self) -> Result; } diff --git a/xmr-btc/tests/harness/transport.rs b/xmr-btc/tests/harness/transport.rs index f69b2683..6b7e007e 100644 --- a/xmr-btc/tests/harness/transport.rs +++ b/xmr-btc/tests/harness/transport.rs @@ -4,7 +4,10 @@ use tokio::{ stream::StreamExt, sync::mpsc::{Receiver, Sender}, }; -use xmr_btc::{alice, bob, transport::SendReceive}; +use xmr_btc::{ + alice, bob, + transport::{Receive, Send}, +}; #[derive(Debug)] pub struct Transport { @@ -13,7 +16,7 @@ pub struct Transport { } #[async_trait] -impl SendReceive for Transport { +impl Send for Transport { async fn send_message(&mut self, message: alice::Message) -> Result<()> { let _ = self .sender @@ -22,7 +25,10 @@ impl SendReceive for Transport for Transport { async fn receive_message(&mut self) -> Result { let message = self .receiver @@ -34,7 +40,7 @@ impl SendReceive for Transport for Transport { +impl Send for Transport { async fn send_message(&mut self, message: bob::Message) -> Result<()> { let _ = self .sender @@ -43,7 +49,10 @@ impl SendReceive for Transport for Transport { async fn receive_message(&mut self) -> Result { let message = self .receiver From 48e5eb8d6c53e53e7ee3ce38ce6abb6c29653230 Mon Sep 17 00:00:00 2001 From: rishflab Date: Fri, 9 Oct 2020 08:48:00 +1100 Subject: [PATCH 13/20] Remove redundant type declarations --- xmr-btc/src/alice.rs | 10 +++++----- xmr-btc/src/bob.rs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index ce311c8d..61ef1334 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -35,19 +35,19 @@ pub async fn next_state< .send_message(state0.next_message(rng).into()) .await?; - let bob_message0: bob::Message0 = transport.receive_message().await?.try_into()?; + let bob_message0 = transport.receive_message().await?.try_into()?; let state1 = state0.receive(bob_message0)?; Ok(state1.into()) } State::State1(state1) => { - let bob_message1: bob::Message1 = transport.receive_message().await?.try_into()?; + let bob_message1 = transport.receive_message().await?.try_into()?; let state2 = state1.receive(bob_message1); - let alice_message1: Message1 = state2.next_message(); + let alice_message1 = state2.next_message(); transport.send_message(alice_message1.into()).await?; Ok(state2.into()) } State::State2(state2) => { - let bob_message2: bob::Message2 = transport.receive_message().await?.try_into()?; + let bob_message2 = transport.receive_message().await?.try_into()?; let state3 = state2.receive(bob_message2)?; Ok(state3.into()) } @@ -65,7 +65,7 @@ pub async fn next_state< transport.send_message(state5.next_message().into()).await?; // todo: pass in state4b as a parameter somewhere in this call to prevent the // user from waiting for a message that wont be sent - let message3: bob::Message3 = transport.receive_message().await?.try_into()?; + let message3 = transport.receive_message().await?.try_into()?; let state6 = state5.receive(message3); tracing::info!("alice has received bob message 3"); tracing::info!("alice is redeeming btc"); diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index d0809678..1a97f202 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -38,14 +38,14 @@ pub async fn next_state< transport .send_message(state0.next_message(rng).into()) .await?; - let message0: alice::Message0 = transport.receive_message().await?.try_into()?; + let message0 = transport.receive_message().await?.try_into()?; let state1 = state0.receive(bitcoin_wallet, message0).await?; Ok(state1.into()) } State::State1(state1) => { transport.send_message(state1.next_message().into()).await?; - let message1: alice::Message1 = transport.receive_message().await?.try_into()?; + let message1 = transport.receive_message().await?.try_into()?; let state2 = state1.receive(message1)?; Ok(state2.into()) } @@ -57,7 +57,7 @@ pub async fn next_state< Ok(state3.into()) } State::State3(state3) => { - let message2: alice::Message2 = transport.receive_message().await?.try_into()?; + let message2 = transport.receive_message().await?.try_into()?; let state4 = state3.watch_for_lock_xmr(monero_wallet, message2).await?; tracing::info!("bob has seen that alice has locked xmr"); Ok(state4.into()) From 9f046944a542c2ace0e83ffea9c3724197ecc8e4 Mon Sep 17 00:00:00 2001 From: rishflab Date: Fri, 9 Oct 2020 08:49:17 +1100 Subject: [PATCH 14/20] Add context to log messages --- xmr-btc/src/alice.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 61ef1334..169c3f81 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -388,12 +388,12 @@ impl State3 { where W: bitcoin::WatchForRawTransaction, { - tracing::info!("{}", self.tx_lock.txid()); + tracing::info!("watching for lock btc with txid: {}", self.tx_lock.txid()); let tx = bitcoin_wallet .watch_for_raw_transaction(self.tx_lock.txid()) .await?; - tracing::info!("{}", tx.txid()); + tracing::info!("tx lock seen with txid: {}", tx.txid()); Ok(State4 { a: self.a, From 61a8a3e428f70828f4138cb700427af345d8f64b Mon Sep 17 00:00:00 2001 From: rishflab Date: Fri, 9 Oct 2020 08:56:20 +1100 Subject: [PATCH 15/20] Improve formatting of tests --- xmr-btc/tests/e2e.rs | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/xmr-btc/tests/e2e.rs b/xmr-btc/tests/e2e.rs index c9c7f519..5b91ac1f 100644 --- a/xmr-btc/tests/e2e.rs +++ b/xmr-btc/tests/e2e.rs @@ -94,7 +94,6 @@ pub async fn init_test<'a>( .unwrap(); let (alice_transport, bob_transport) = init_alice_and_bob_transports(); - let alice = AliceNode::new(alice_transport, alice_btc_wallet, alice_monero_wallet); let bob = BobNode::new(bob_transport, bob_btc_wallet, bob_monero_wallet); @@ -205,16 +204,6 @@ mod tests { .await .unwrap(); - assert_eq!( - alice_final_btc_balance, - initial_balances.alice_btc + swap_amounts.btc - - bitcoin::Amount::from_sat(bitcoin::TX_FEE) - ); - assert_eq!( - bob_final_btc_balance, - initial_balances.bob_btc - swap_amounts.btc - lock_tx_bitcoin_fee - ); - let alice_final_xmr_balance = alice_node .monero_wallet .0 @@ -231,6 +220,16 @@ mod tests { let bob_final_xmr_balance = bob_node.monero_wallet.0.get_balance_bob().await.unwrap(); + assert_eq!( + alice_final_btc_balance, + initial_balances.alice_btc + swap_amounts.btc + - bitcoin::Amount::from_sat(bitcoin::TX_FEE) + ); + assert_eq!( + bob_final_btc_balance, + initial_balances.bob_btc - swap_amounts.btc - lock_tx_bitcoin_fee + ); + assert_eq!( alice_final_xmr_balance, initial_balances.alice_xmr @@ -297,13 +296,6 @@ mod tests { .await .unwrap(); - assert_eq!(alice_final_btc_balance, initial_balances.alice_btc); - assert_eq!( - bob_final_btc_balance, - // The 2 * TX_FEE corresponds to tx_refund and tx_cancel. - initial_balances.bob_btc - Amount::from_sat(2 * TX_FEE) - lock_tx_bitcoin_fee - ); - alice_node .monero_wallet .0 @@ -318,6 +310,13 @@ mod tests { .unwrap(); let bob_final_xmr_balance = bob_node.monero_wallet.0.get_balance_bob().await.unwrap(); + assert_eq!(alice_final_btc_balance, initial_balances.alice_btc); + assert_eq!( + bob_final_btc_balance, + // The 2 * TX_FEE corresponds to tx_refund and tx_cancel. + initial_balances.bob_btc - Amount::from_sat(2 * TX_FEE) - lock_tx_bitcoin_fee + ); + // Because we create a new wallet when claiming Monero, we can only assert on // this new wallet owning all of `xmr_amount` after refund assert_eq!(alice_final_xmr_balance, u64::from(swap_amounts.xmr)); From bf576bf251adf87a684a86d282f7aaab05f2d0f6 Mon Sep 17 00:00:00 2001 From: rishflab Date: Fri, 9 Oct 2020 09:28:51 +1100 Subject: [PATCH 16/20] Move test functions out of lib --- xmr-btc/src/alice.rs | 13 ------------- xmr-btc/src/bob.rs | 10 ---------- xmr-btc/tests/e2e.rs | 34 ++++++++++++++++++++++++---------- xmr-btc/tests/harness/mod.rs | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 33 deletions(-) diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 169c3f81..8072f777 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -88,19 +88,6 @@ pub enum State { State6(State6), } -// TODO: use macro or generics -pub fn is_state4(state: &State) -> bool { - matches!(state, State::State4 { .. }) -} -// TODO: use macro or generics -pub fn is_state5(state: &State) -> bool { - matches!(state, State::State5 { .. }) -} -// TODO: use macro or generics -pub fn is_state6(state: &State) -> bool { - matches!(state, State::State6 { .. }) -} - macro_rules! impl_try_from_parent_state { ($type:ident) => { impl TryFrom for $type { diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index 1a97f202..8b73bf70 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -124,16 +124,6 @@ impl_from_child_state!(State3); impl_from_child_state!(State4); impl_from_child_state!(State5); -// TODO: use macro or generics -pub fn is_state5(state: &State) -> bool { - matches!(state, State::State5 { .. }) -} - -// TODO: use macro or generics -pub fn is_state3(state: &State) -> bool { - matches!(state, State::State3 { .. }) -} - #[derive(Debug)] pub struct State0 { b: bitcoin::SecretKey, diff --git a/xmr-btc/tests/e2e.rs b/xmr-btc/tests/e2e.rs index 5b91ac1f..f17f4d13 100644 --- a/xmr-btc/tests/e2e.rs +++ b/xmr-btc/tests/e2e.rs @@ -144,16 +144,15 @@ pub async fn init_test<'a>( #[cfg(test)] mod tests { use crate::{ + harness, harness::node::{run_alice_until, run_bob_until}, init_bitcoind, init_test, }; - + use futures::future; use monero_harness::Monero; use rand::rngs::OsRng; - use testcontainers::clients::Cli; - - use futures::future; use std::convert::TryInto; + use testcontainers::clients::Cli; use tracing_subscriber::util::SubscriberInitExt; use xmr_btc::{ alice, bitcoin, @@ -184,10 +183,15 @@ mod tests { run_alice_until( &mut alice_node, alice_state0.into(), - alice::is_state6, + harness::alice::is_state6, + &mut OsRng, + ), + run_bob_until( + &mut bob_node, + bob_state0.into(), + harness::bob::is_state5, &mut OsRng, ), - run_bob_until(&mut bob_node, bob_state0.into(), bob::is_state5, &mut OsRng), ) .await .unwrap(); @@ -265,10 +269,15 @@ mod tests { run_alice_until( &mut alice_node, alice_state0.into(), - alice::is_state5, + harness::alice::is_state5, + &mut OsRng, + ), + run_bob_until( + &mut bob_node, + bob_state0.into(), + harness::bob::is_state3, &mut OsRng, ), - run_bob_until(&mut bob_node, bob_state0.into(), bob::is_state3, &mut OsRng), ) .await .unwrap(); @@ -346,10 +355,15 @@ mod tests { run_alice_until( &mut alice_node, alice_state0.into(), - alice::is_state4, + harness::alice::is_state4, + &mut OsRng, + ), + run_bob_until( + &mut bob_node, + bob_state0.into(), + harness::bob::is_state3, &mut OsRng, ), - run_bob_until(&mut bob_node, bob_state0.into(), bob::is_state3, &mut OsRng), ) .await .unwrap(); diff --git a/xmr-btc/tests/harness/mod.rs b/xmr-btc/tests/harness/mod.rs index 38d3808a..4f2cba46 100644 --- a/xmr-btc/tests/harness/mod.rs +++ b/xmr-btc/tests/harness/mod.rs @@ -1,3 +1,36 @@ pub mod node; pub mod transport; pub mod wallet; + +pub mod bob { + use xmr_btc::bob::State; + + // TODO: use macro or generics + pub fn is_state5(state: &State) -> bool { + matches!(state, State::State5 { .. }) + } + + // TODO: use macro or generics + pub fn is_state3(state: &State) -> bool { + matches!(state, State::State3 { .. }) + } +} + +pub mod alice { + use xmr_btc::alice::State; + + // TODO: use macro or generics + pub fn is_state4(state: &State) -> bool { + matches!(state, State::State4 { .. }) + } + + // TODO: use macro or generics + pub fn is_state5(state: &State) -> bool { + matches!(state, State::State5 { .. }) + } + + // TODO: use macro or generics + pub fn is_state6(state: &State) -> bool { + matches!(state, State::State6 { .. }) + } +} From 666c121bb32e1bbe835619586bc5fb000965007b Mon Sep 17 00:00:00 2001 From: rishflab Date: Fri, 9 Oct 2020 09:46:24 +1100 Subject: [PATCH 17/20] Implement transport traits with generics --- xmr-btc/src/alice.rs | 4 +-- xmr-btc/src/bob.rs | 4 +-- xmr-btc/src/transport.rs | 4 +-- xmr-btc/tests/harness/transport.rs | 45 +++++++++--------------------- 4 files changed, 19 insertions(+), 38 deletions(-) diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 8072f777..00eccfc5 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -3,7 +3,7 @@ use crate::{ bitcoin::{BroadcastSignedTransaction, WatchForRawTransaction}, bob, monero, monero::{CreateWalletForOutput, Transfer}, - transport::{Receive, Send}, + transport::{ReceiveMessage, SendMessage}, }; use anyhow::{anyhow, Result}; use ecdsa_fun::{ @@ -21,7 +21,7 @@ pub async fn next_state< R: RngCore + CryptoRng, B: WatchForRawTransaction + BroadcastSignedTransaction, M: CreateWalletForOutput + Transfer, - T: Send + Receive, + T: SendMessage + ReceiveMessage, >( bitcoin_wallet: &B, monero_wallet: &M, diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index 8b73bf70..94b4f424 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -6,7 +6,7 @@ use crate::{ }, monero, monero::{CheckTransfer, CreateWalletForOutput}, - transport::{Receive, Send}, + transport::{ReceiveMessage, SendMessage}, }; use anyhow::{anyhow, Result}; use ecdsa_fun::{ @@ -25,7 +25,7 @@ pub async fn next_state< R: RngCore + CryptoRng, B: WatchForRawTransaction + SignTxLock + BuildTxLockPsbt + BroadcastSignedTransaction, M: CreateWalletForOutput + CheckTransfer, - T: Send + Receive, + T: SendMessage + ReceiveMessage, >( bitcoin_wallet: &B, monero_wallet: &M, diff --git a/xmr-btc/src/transport.rs b/xmr-btc/src/transport.rs index 2a7db76a..f71992fb 100644 --- a/xmr-btc/src/transport.rs +++ b/xmr-btc/src/transport.rs @@ -2,11 +2,11 @@ use anyhow::Result; use async_trait::async_trait; #[async_trait] -pub trait Send { +pub trait SendMessage { async fn send_message(&mut self, message: SendMsg) -> Result<()>; } #[async_trait] -pub trait Receive { +pub trait ReceiveMessage { async fn receive_message(&mut self) -> Result; } diff --git a/xmr-btc/tests/harness/transport.rs b/xmr-btc/tests/harness/transport.rs index 6b7e007e..1c912b02 100644 --- a/xmr-btc/tests/harness/transport.rs +++ b/xmr-btc/tests/harness/transport.rs @@ -4,10 +4,7 @@ use tokio::{ stream::StreamExt, sync::mpsc::{Receiver, Sender}, }; -use xmr_btc::{ - alice, bob, - transport::{Receive, Send}, -}; +use xmr_btc::transport::{ReceiveMessage, SendMessage}; #[derive(Debug)] pub struct Transport { @@ -16,8 +13,12 @@ pub struct Transport { } #[async_trait] -impl Send for Transport { - async fn send_message(&mut self, message: alice::Message) -> Result<()> { +impl SendMessage for Transport +where + SendMsg: Send + Sync, + RecvMsg: std::marker::Send, +{ + async fn send_message(&mut self, message: SendMsg) -> Result<()> { let _ = self .sender .send(message) @@ -28,32 +29,12 @@ impl Send for Transport { } #[async_trait] -impl Receive for Transport { - async fn receive_message(&mut self) -> Result { - let message = self - .receiver - .next() - .await - .ok_or_else(|| anyhow!("failed to receive message"))?; - Ok(message) - } -} - -#[async_trait] -impl Send for Transport { - async fn send_message(&mut self, message: bob::Message) -> Result<()> { - let _ = self - .sender - .send(message) - .await - .map_err(|_| anyhow!("failed to send message"))?; - Ok(()) - } -} - -#[async_trait] -impl Receive for Transport { - async fn receive_message(&mut self) -> Result { +impl ReceiveMessage for Transport +where + SendMsg: std::marker::Send, + RecvMsg: Send + Sync, +{ + async fn receive_message(&mut self) -> Result { let message = self .receiver .next() From 51760041f063055d192367dc4c79df2b10fb1f9d Mon Sep 17 00:00:00 2001 From: rishflab Date: Fri, 9 Oct 2020 10:17:36 +1100 Subject: [PATCH 18/20] Remove duplicated macro definitions --- xmr-btc/src/alice.rs | 55 +++++------------- xmr-btc/src/alice/message.rs | 82 ++------------------------- xmr-btc/src/bob.rs | 51 +++++------------ xmr-btc/src/bob/message.rs | 104 +++-------------------------------- xmr-btc/src/lib.rs | 31 +++++++++++ 5 files changed, 73 insertions(+), 250 deletions(-) diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 00eccfc5..13917d3e 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -15,7 +15,7 @@ use sha2::Sha256; use std::convert::{TryFrom, TryInto}; pub mod message; -pub use message::{Message, Message0, Message1, Message2, UnexpectedMessage}; +pub use message::{Message, Message0, Message1, Message2}; pub async fn next_state< R: RngCore + CryptoRng, @@ -88,46 +88,21 @@ pub enum State { State6(State6), } -macro_rules! impl_try_from_parent_state { - ($type:ident) => { - impl TryFrom for $type { - type Error = anyhow::Error; - fn try_from(from: State) -> Result { - if let State::$type(state) = from { - Ok(state) - } else { - Err(anyhow!("Failed to convert parent state to child state")) - } - } - } - }; -} +impl_try_from_parent_enum!(State0, State); +impl_try_from_parent_enum!(State1, State); +impl_try_from_parent_enum!(State2, State); +impl_try_from_parent_enum!(State3, State); +impl_try_from_parent_enum!(State4, State); +impl_try_from_parent_enum!(State5, State); +impl_try_from_parent_enum!(State6, State); -impl_try_from_parent_state!(State0); -impl_try_from_parent_state!(State1); -impl_try_from_parent_state!(State2); -impl_try_from_parent_state!(State3); -impl_try_from_parent_state!(State4); -impl_try_from_parent_state!(State5); -impl_try_from_parent_state!(State6); - -macro_rules! impl_from_child_state { - ($type:ident) => { - impl From<$type> for State { - fn from(from: $type) -> Self { - State::$type(from) - } - } - }; -} - -impl_from_child_state!(State0); -impl_from_child_state!(State1); -impl_from_child_state!(State2); -impl_from_child_state!(State3); -impl_from_child_state!(State4); -impl_from_child_state!(State5); -impl_from_child_state!(State6); +impl_from_child_enum!(State0, State); +impl_from_child_enum!(State1, State); +impl_from_child_enum!(State2, State); +impl_from_child_enum!(State3, State); +impl_from_child_enum!(State4, State); +impl_from_child_enum!(State5, State); +impl_from_child_enum!(State6, State); impl State { pub fn new( diff --git a/xmr-btc/src/alice/message.rs b/xmr-btc/src/alice/message.rs index 89965f81..7c95604b 100644 --- a/xmr-btc/src/alice/message.rs +++ b/xmr-btc/src/alice/message.rs @@ -33,80 +33,10 @@ pub struct Message2 { pub(crate) tx_lock_proof: monero::TransferProof, } -impl From for Message { - fn from(m: Message0) -> Self { - Message::Message0(m) - } -} +impl_try_from_parent_enum!(Message0, Message); +impl_try_from_parent_enum!(Message1, Message); +impl_try_from_parent_enum!(Message2, Message); -impl TryFrom for Message0 { - type Error = UnexpectedMessage; - - fn try_from(m: Message) -> Result { - match m { - Message::Message0(m) => Ok(m), - _ => Err(UnexpectedMessage { - expected_type: "Create0".to_string(), - received: m, - }), - } - } -} - -impl From for Message { - fn from(m: Message1) -> Self { - Message::Message1(m) - } -} - -impl TryFrom for Message1 { - type Error = UnexpectedMessage; - - fn try_from(m: Message) -> Result { - match m { - Message::Message1(m) => Ok(m), - _ => Err(UnexpectedMessage { - expected_type: "Create1".to_string(), - received: m, - }), - } - } -} - -impl From for Message { - fn from(m: Message2) -> Self { - Message::Message2(m) - } -} - -impl TryFrom for Message2 { - type Error = UnexpectedMessage; - - fn try_from(m: Message) -> Result { - match m { - Message::Message2(m) => Ok(m), - _ => Err(UnexpectedMessage { - expected_type: "Create2".to_string(), - received: m, - }), - } - } -} - -#[derive(Debug, thiserror::Error)] -#[error("expected message of type {expected_type}, got {received:?}")] -pub struct UnexpectedMessage { - expected_type: String, - received: Message, -} - -impl UnexpectedMessage { - pub fn new(received: Message) -> Self { - let expected_type = std::any::type_name::(); - - Self { - expected_type: expected_type.to_string(), - received, - } - } -} +impl_from_child_enum!(Message0, Message); +impl_from_child_enum!(Message1, Message); +impl_from_child_enum!(Message2, Message); diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index 94b4f424..6bf8c16a 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -19,7 +19,7 @@ use sha2::Sha256; use std::convert::{TryFrom, TryInto}; pub mod message; -pub use message::{Message, Message0, Message1, Message2, Message3, UnexpectedMessage}; +pub use message::{Message, Message0, Message1, Message2, Message3}; pub async fn next_state< R: RngCore + CryptoRng, @@ -85,44 +85,19 @@ pub enum State { State5(State5), } -macro_rules! impl_try_from_parent_state { - ($type:ident) => { - impl TryFrom for $type { - type Error = anyhow::Error; - fn try_from(from: State) -> Result { - if let State::$type(state) = from { - Ok(state) - } else { - Err(anyhow!("Failed to convert parent state to child state")) - } - } - } - }; -} +impl_try_from_parent_enum!(State0, State); +impl_try_from_parent_enum!(State1, State); +impl_try_from_parent_enum!(State2, State); +impl_try_from_parent_enum!(State3, State); +impl_try_from_parent_enum!(State4, State); +impl_try_from_parent_enum!(State5, State); -impl_try_from_parent_state!(State0); -impl_try_from_parent_state!(State1); -impl_try_from_parent_state!(State2); -impl_try_from_parent_state!(State3); -impl_try_from_parent_state!(State4); -impl_try_from_parent_state!(State5); - -macro_rules! impl_from_child_state { - ($type:ident) => { - impl From<$type> for State { - fn from(from: $type) -> Self { - State::$type(from) - } - } - }; -} - -impl_from_child_state!(State0); -impl_from_child_state!(State1); -impl_from_child_state!(State2); -impl_from_child_state!(State3); -impl_from_child_state!(State4); -impl_from_child_state!(State5); +impl_from_child_enum!(State0, State); +impl_from_child_enum!(State1, State); +impl_from_child_enum!(State2, State); +impl_from_child_enum!(State3, State); +impl_from_child_enum!(State4, State); +impl_from_child_enum!(State5, State); #[derive(Debug)] pub struct State0 { diff --git a/xmr-btc/src/bob/message.rs b/xmr-btc/src/bob/message.rs index 8164dbd6..de02b7a5 100644 --- a/xmr-btc/src/bob/message.rs +++ b/xmr-btc/src/bob/message.rs @@ -37,100 +37,12 @@ pub struct Message3 { pub(crate) tx_redeem_encsig: EncryptedSignature, } -impl From for Message { - fn from(m: Message0) -> Self { - Message::Message0(m) - } -} +impl_try_from_parent_enum!(Message0, Message); +impl_try_from_parent_enum!(Message1, Message); +impl_try_from_parent_enum!(Message2, Message); +impl_try_from_parent_enum!(Message3, Message); -impl TryFrom for Message0 { - type Error = UnexpectedMessage; - - fn try_from(m: Message) -> Result { - match m { - Message::Message0(m) => Ok(m), - _ => Err(UnexpectedMessage { - expected_type: "Create0".to_string(), - received: m, - }), - } - } -} - -impl From for Message { - fn from(m: Message1) -> Self { - Message::Message1(m) - } -} - -impl TryFrom for Message1 { - type Error = UnexpectedMessage; - - fn try_from(m: Message) -> Result { - match m { - Message::Message1(m) => Ok(m), - _ => Err(UnexpectedMessage { - expected_type: "Create0".to_string(), - received: m, - }), - } - } -} - -impl From for Message { - fn from(m: Message2) -> Self { - Message::Message2(m) - } -} - -impl TryFrom for Message2 { - type Error = UnexpectedMessage; - - fn try_from(m: Message) -> Result { - match m { - Message::Message2(m) => Ok(m), - _ => Err(UnexpectedMessage { - expected_type: "Create0".to_string(), - received: m, - }), - } - } -} - -impl From for Message { - fn from(m: Message3) -> Self { - Message::Message3(m) - } -} - -impl TryFrom for Message3 { - type Error = UnexpectedMessage; - - fn try_from(m: Message) -> Result { - match m { - Message::Message3(m) => Ok(m), - _ => Err(UnexpectedMessage { - expected_type: "Create0".to_string(), - received: m, - }), - } - } -} - -#[derive(Debug, thiserror::Error)] -#[error("expected message of type {expected_type}, got {received:?}")] -pub struct UnexpectedMessage { - expected_type: String, - received: Message, -} - -impl UnexpectedMessage { - pub fn new(received: Message) -> Self { - let expected_type = std::any::type_name::(); - - Self { - expected_type: expected_type.to_string(), - received, - } - } -} +impl_from_child_enum!(Message0, Message); +impl_from_child_enum!(Message1, Message); +impl_from_child_enum!(Message2, Message); +impl_from_child_enum!(Message3, Message); diff --git a/xmr-btc/src/lib.rs b/xmr-btc/src/lib.rs index 986d6434..790cb477 100644 --- a/xmr-btc/src/lib.rs +++ b/xmr-btc/src/lib.rs @@ -14,6 +14,37 @@ #![forbid(unsafe_code)] #![allow(non_snake_case)] +#[macro_use] +mod utils { + + macro_rules! impl_try_from_parent_enum { + ($type:ident, $parent:ident) => { + impl TryFrom<$parent> for $type { + type Error = anyhow::Error; + fn try_from(from: $parent) -> Result { + if let $parent::$type(inner) = from { + Ok(inner) + } else { + Err(anyhow::anyhow!( + "Failed to convert parent state to child state" + )) + } + } + } + }; + } + + macro_rules! impl_from_child_enum { + ($type:ident, $parent:ident) => { + impl From<$type> for $parent { + fn from(from: $type) -> Self { + $parent::$type(from) + } + } + }; + } +} + pub mod alice; pub mod bitcoin; pub mod bob; From 2c6bb8fa19f6adffd038d713c05e625351a5f201 Mon Sep 17 00:00:00 2001 From: rishflab Date: Fri, 9 Oct 2020 10:45:35 +1100 Subject: [PATCH 19/20] Remove redundant attribute --- xmr-btc/tests/e2e.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/xmr-btc/tests/e2e.rs b/xmr-btc/tests/e2e.rs index f17f4d13..fb2ddeec 100644 --- a/xmr-btc/tests/e2e.rs +++ b/xmr-btc/tests/e2e.rs @@ -141,7 +141,6 @@ pub async fn init_test<'a>( ) } -#[cfg(test)] mod tests { use crate::{ harness, From c778f4e2043ca007d4b181ef26a14d530593e8f0 Mon Sep 17 00:00:00 2001 From: rishflab Date: Fri, 9 Oct 2020 11:46:33 +1100 Subject: [PATCH 20/20] Remove incorrect todo --- xmr-btc/src/bob.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index 6bf8c16a..5db4465f 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -342,7 +342,6 @@ pub struct State3 { } impl State3 { - // todo: loop until punish? timelock has expired pub async fn watch_for_lock_xmr(self, xmr_wallet: &W, msg: alice::Message2) -> Result where W: monero::CheckTransfer,