From ea08778b2f12cd109fd8dded5f523388eed43eee Mon Sep 17 00:00:00 2001 From: rishflab Date: Mon, 2 Nov 2020 16:00:10 +1100 Subject: [PATCH 01/22] Save state for Bob at specific points in the swap --- swap/Cargo.toml | 2 +- swap/src/alice.rs | 2 ++ swap/src/bob.rs | 41 +++++++++++++++++++++++++++++++++++++---- swap/src/main.rs | 10 ++++++++++ swap/src/storage.rs | 32 +++++++++++++++++++++++++++++++- xmr-btc/src/bob.rs | 2 +- 6 files changed, 82 insertions(+), 7 deletions(-) diff --git a/swap/Cargo.toml b/swap/Cargo.toml index af99bcba..8cb330b7 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -19,7 +19,7 @@ genawaiter = "0.99.1" libp2p = { version = "0.29", default-features = false, features = ["tcp-tokio", "yamux", "mplex", "dns", "noise", "request-response"] } libp2p-tokio-socks5 = "0.4" log = { version = "0.4", features = ["serde"] } -monero = "0.9" +monero = { version = "0.9", features = ["serde_support"] } monero-harness = { path = "../monero-harness" } rand = "0.7" reqwest = { version = "0.10", default-features = false, features = ["socks"] } diff --git a/swap/src/alice.rs b/swap/src/alice.rs index 036e4dc8..136f045f 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -31,6 +31,7 @@ use crate::{ transport::SwapTransport, TokioExecutor, }, + storage::Database, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; use xmr_btc::{ @@ -43,6 +44,7 @@ use xmr_btc::{ pub async fn swap( bitcoin_wallet: Arc, monero_wallet: Arc, + _db: Database, listen: Multiaddr, transport: SwapTransport, behaviour: Alice, diff --git a/swap/src/bob.rs b/swap/src/bob.rs index 2f973b99..b1296c7c 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -22,14 +22,14 @@ mod message3; use self::{amounts::*, message0::*, message1::*, message2::*, message3::*}; use crate::{ - bitcoin, - bitcoin::TX_LOCK_MINE_TIMEOUT, + bitcoin::{self, TX_LOCK_MINE_TIMEOUT}, monero, network::{ peer_tracker::{self, PeerTracker}, transport::SwapTransport, TokioExecutor, }, + storage::{self, Database}, Cmd, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; use xmr_btc::{ @@ -43,6 +43,7 @@ use xmr_btc::{ pub async fn swap( bitcoin_wallet: Arc, monero_wallet: Arc, + db: Database, btc: u64, addr: Multiaddr, mut cmd_tx: Sender, @@ -141,6 +142,9 @@ pub async fn swap( other => panic!("unexpected event: {:?}", other), }; + db.insert_latest_state(&storage::Bob::Handshaken(state2.clone())) + .await?; + swarm.send_message2(alice.clone(), state2.next_message()); info!("Handshake complete"); @@ -151,7 +155,7 @@ pub async fn swap( network.clone(), monero_wallet.clone(), bitcoin_wallet.clone(), - state2, + state2.clone(), TX_LOCK_MINE_TIMEOUT, ); @@ -166,8 +170,17 @@ pub async fn swap( let _ = bitcoin_wallet .broadcast_signed_transaction(signed_tx_lock) .await?; + db.insert_latest_state(&storage::Bob::BtcLocked(state2.clone())) + .await?; } GeneratorState::Yielded(bob::Action::SendBtcRedeemEncsig(tx_redeem_encsig)) => { + // FIXME: We _know_ that this action is only yielded if the monero has been + // locked. This only works because we know that this is the case, but it may be + // cleaner to save the state inside an implementation of `watch_for_transfer` or + // modify the library code to make this easier + db.insert_latest_state(&storage::Bob::XmrLocked(state2.clone())) + .await?; + let mut guard = network.as_ref().lock().await; guard.0.send_message3(alice.clone(), tx_redeem_encsig); info!("Sent Bitcoin redeem encsig"); @@ -185,21 +198,41 @@ pub async fn swap( spend_key, view_key, }) => { + // FIXME: We _know_ that this action is only yielded if the bitcoin has been + // redeemed. This only works because we know that this is the case, but it may + // be cleaner to save the state inside an implementation of `watch_for_transfer` + // or modify the library code to make this easier + db.insert_latest_state(&storage::Bob::BtcRedeemed(state2.clone())) + .await?; + monero_wallet .create_and_load_wallet_for_output(spend_key, view_key) .await?; } GeneratorState::Yielded(bob::Action::CancelBtc(tx_cancel)) => { + db.insert_latest_state(&storage::Bob::BtcRefundable(state2.clone())) + .await?; + let _ = bitcoin_wallet .broadcast_signed_transaction(tx_cancel) .await?; + + db.insert_latest_state(&storage::Bob::BtcRefundable(state2.clone())) + .await?; } GeneratorState::Yielded(bob::Action::RefundBtc(tx_refund)) => { + db.insert_latest_state(&storage::Bob::BtcRefundable(state2.clone())) + .await?; + let _ = bitcoin_wallet .broadcast_signed_transaction(tx_refund) .await?; } - GeneratorState::Complete(()) => return Ok(()), + GeneratorState::Complete(()) => { + db.insert_latest_state(&storage::Bob::SwapComplete).await?; + + return Ok(()); + } } } } diff --git a/swap/src/main.rs b/swap/src/main.rs index 2f8fc74c..3f7a701e 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -27,6 +27,7 @@ use swap::{ network::transport::{build, build_tor, SwapTransport}, Cmd, Rsp, SwapAmounts, }; +use tempfile::tempdir; use tracing::info; mod cli; @@ -80,9 +81,12 @@ async fn main() -> Result<()> { let monero_wallet = Arc::new(monero::Wallet::new(monerod_url)); + let db = Database::open(db_dir.path()).unwrap(); + swap_as_alice( bitcoin_wallet, monero_wallet, + db listen_addr, transport, behaviour, @@ -113,9 +117,12 @@ async fn main() -> Result<()> { let monero_wallet = Arc::new(monero::Wallet::new(monerod_url)); + let db = Database::open(db_dir.path()).unwrap(); + swap_as_bob( bitcoin_wallet, monero_wallet, + db satoshis, alice_addr, transport, @@ -149,6 +156,7 @@ async fn create_tor_service( async fn swap_as_alice( bitcoin_wallet: Arc, monero_wallet: Arc, + db: Database, addr: Multiaddr, transport: SwapTransport, behaviour: Alice, @@ -159,6 +167,7 @@ async fn swap_as_alice( async fn swap_as_bob( bitcoin_wallet: Arc, monero_wallet: Arc, + db: Database, sats: u64, alice: Multiaddr, transport: SwapTransport, @@ -169,6 +178,7 @@ async fn swap_as_bob( tokio::spawn(bob::swap( bitcoin_wallet, monero_wallet, + db, sats, alice, cmd_tx, diff --git a/swap/src/storage.rs b/swap/src/storage.rs index 206dbfa1..7c9942e4 100644 --- a/swap/src/storage.rs +++ b/swap/src/storage.rs @@ -1,6 +1,36 @@ use anyhow::{anyhow, Context, Result}; -use serde::{de::DeserializeOwned, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::path::Path; +use xmr_btc::{alice, bitcoin::EncryptedSignature, bob, serde::monero_private_key}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum Bob { + Handshaken(bob::State2), + BtcLocked(bob::State2), + XmrLocked(bob::State2), + BtcRedeemed(bob::State2), + BtcRefundable(bob::State2), + SwapComplete, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum Alice { + Handshaken(alice::State3), + BtcLocked(alice::State3), + XmrLocked(alice::State3), + ReceivedEncSig { + state: alice::State3, + enc_sig: EncryptedSignature, + }, + BtcCancelled(alice::State3), + BtcRefunded { + state: alice::State3, + #[serde(with = "monero_private_key")] + s_b: monero::PrivateKey, + }, + SwapComplete, +} pub struct Database where diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index 89cb6492..d92303b2 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -495,7 +495,7 @@ impl State1 { } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct State2 { pub A: bitcoin::PublicKey, pub b: bitcoin::SecretKey, From 934ddb366a8e472f3bf40f9abda3dac8652817e6 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Mon, 2 Nov 2020 17:14:56 +1100 Subject: [PATCH 02/22] Save state for Alice at specific points in the swap --- swap/src/alice.rs | 38 ++++++++++++++++++++++++++++++++++---- swap/src/bob.rs | 11 ----------- swap/src/storage.rs | 33 ++++++++++++++++++--------------- 3 files changed, 52 insertions(+), 30 deletions(-) diff --git a/swap/src/alice.rs b/swap/src/alice.rs index 136f045f..10092169 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -31,7 +31,7 @@ use crate::{ transport::SwapTransport, TokioExecutor, }, - storage::Database, + storage::{self, Database}, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; use xmr_btc::{ @@ -44,7 +44,7 @@ use xmr_btc::{ pub async fn swap( bitcoin_wallet: Arc, monero_wallet: Arc, - _db: Database, + db: Database, listen: Multiaddr, transport: SwapTransport, behaviour: Alice, @@ -175,6 +175,9 @@ pub async fn swap( other => panic!("Unexpected event: {:?}", other), }; + db.insert_latest_state(&storage::Alice::Handshaken(state3.clone())) + .await?; + info!("Handshake complete, we now have State3 for Alice."); let network = Arc::new(Mutex::new(Network { @@ -185,7 +188,7 @@ pub async fn swap( let mut action_generator = action_generator( network.clone(), bitcoin_wallet.clone(), - state3, + state3.clone(), TX_LOCK_MINE_TIMEOUT, ); @@ -200,33 +203,60 @@ pub async fn swap( public_spend_key, public_view_key, }) => { + db.insert_latest_state(&storage::Alice::BtcLocked(state3.clone())) + .await?; + let (transfer_proof, _) = monero_wallet .transfer(public_spend_key, public_view_key, amount) .await?; + db.insert_latest_state(&storage::Alice::XmrLocked(state3.clone())) + .await?; + let mut guard = network.as_ref().lock().await; guard.send_message2(transfer_proof).await; info!("Sent transfer proof"); } GeneratorState::Yielded(Action::RedeemBtc(tx)) => { + db.insert_latest_state(&storage::Alice::BtcRedeemable { + state: state3.clone(), + redeem_tx: tx.clone(), + }) + .await?; + let _ = bitcoin_wallet.broadcast_signed_transaction(tx).await?; } GeneratorState::Yielded(Action::CancelBtc(tx)) => { let _ = bitcoin_wallet.broadcast_signed_transaction(tx).await?; } GeneratorState::Yielded(Action::PunishBtc(tx)) => { + db.insert_latest_state(&storage::Alice::BtcPunishable(state3.clone())) + .await?; + let _ = bitcoin_wallet.broadcast_signed_transaction(tx).await?; } GeneratorState::Yielded(Action::CreateMoneroWalletForOutput { spend_key, view_key, }) => { + db.insert_latest_state(&storage::Alice::BtcRefunded { + state: state3.clone(), + spend_key, + view_key, + }) + .await?; + monero_wallet .create_and_load_wallet_for_output(spend_key, view_key) .await?; } - GeneratorState::Complete(()) => return Ok(()), + GeneratorState::Complete(()) => { + db.insert_latest_state(&storage::Alice::SwapComplete) + .await?; + + return Ok(()); + } } } } diff --git a/swap/src/bob.rs b/swap/src/bob.rs index b1296c7c..c72831f2 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -174,10 +174,6 @@ pub async fn swap( .await?; } GeneratorState::Yielded(bob::Action::SendBtcRedeemEncsig(tx_redeem_encsig)) => { - // FIXME: We _know_ that this action is only yielded if the monero has been - // locked. This only works because we know that this is the case, but it may be - // cleaner to save the state inside an implementation of `watch_for_transfer` or - // modify the library code to make this easier db.insert_latest_state(&storage::Bob::XmrLocked(state2.clone())) .await?; @@ -198,10 +194,6 @@ pub async fn swap( spend_key, view_key, }) => { - // FIXME: We _know_ that this action is only yielded if the bitcoin has been - // redeemed. This only works because we know that this is the case, but it may - // be cleaner to save the state inside an implementation of `watch_for_transfer` - // or modify the library code to make this easier db.insert_latest_state(&storage::Bob::BtcRedeemed(state2.clone())) .await?; @@ -216,9 +208,6 @@ pub async fn swap( let _ = bitcoin_wallet .broadcast_signed_transaction(tx_cancel) .await?; - - db.insert_latest_state(&storage::Bob::BtcRefundable(state2.clone())) - .await?; } GeneratorState::Yielded(bob::Action::RefundBtc(tx_refund)) => { db.insert_latest_state(&storage::Bob::BtcRefundable(state2.clone())) diff --git a/swap/src/storage.rs b/swap/src/storage.rs index 7c9942e4..21541858 100644 --- a/swap/src/storage.rs +++ b/swap/src/storage.rs @@ -1,17 +1,7 @@ use anyhow::{anyhow, Context, Result}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::path::Path; -use xmr_btc::{alice, bitcoin::EncryptedSignature, bob, serde::monero_private_key}; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum Bob { - Handshaken(bob::State2), - BtcLocked(bob::State2), - XmrLocked(bob::State2), - BtcRedeemed(bob::State2), - BtcRefundable(bob::State2), - SwapComplete, -} +use xmr_btc::{alice, bob, monero, serde::monero_private_key}; #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug, Deserialize, Serialize)] @@ -19,19 +9,30 @@ pub enum Alice { Handshaken(alice::State3), BtcLocked(alice::State3), XmrLocked(alice::State3), - ReceivedEncSig { + BtcRedeemable { state: alice::State3, - enc_sig: EncryptedSignature, + redeem_tx: bitcoin::Transaction, }, - BtcCancelled(alice::State3), + BtcPunishable(alice::State3), BtcRefunded { state: alice::State3, #[serde(with = "monero_private_key")] - s_b: monero::PrivateKey, + spend_key: monero::PrivateKey, + view_key: monero::PrivateViewKey, }, SwapComplete, } +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum Bob { + Handshaken(bob::State2), + BtcLocked(bob::State2), + XmrLocked(bob::State2), + BtcRedeemed(bob::State2), + BtcRefundable(bob::State2), + SwapComplete, +} + pub struct Database where T: Serialize + DeserializeOwned, @@ -57,6 +58,8 @@ where }) } + // TODO: Add method to update state + pub async fn insert_latest_state(&self, state: &T) -> Result<()> { let key = serialize(&Self::LAST_STATE_KEY)?; let new_value = serialize(&state).context("Could not serialize new state value")?; From a26890001bb36d0cb0fa90ef66b078d887b86f69 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Mon, 2 Nov 2020 17:37:46 +1100 Subject: [PATCH 03/22] Fix swap e2e test --- swap/tests/e2e.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 9c8eda1b..8f2d031f 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -16,7 +16,7 @@ mod e2e_test { "swap=debug,xmr_btc=debug,hyper=off,reqwest=off,monero_harness=info,testcontainers=info,libp2p=debug", ) .with_ansi(false) - .set_default(); + .set_default(); let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" .parse() @@ -69,14 +69,21 @@ mod e2e_test { let alice_behaviour = alice::Alice::default(); let alice_transport = build(alice_behaviour.identity()).unwrap(); + + let db_dir = tempdir().unwrap(); + let db = Database::open(db_dir.path()).unwrap(); + let alice_swap = alice::swap( alice_btc_wallet.clone(), alice_xmr_wallet.clone(), + db, alice_multiaddr.clone(), alice_transport, alice_behaviour, ); + let db_dir = tempdir().unwrap(); + let db = Database::open(db_dir.path()).unwrap(); let (cmd_tx, mut _cmd_rx) = mpsc::channel(1); let (mut rsp_tx, rsp_rx) = mpsc::channel(1); let bob_behaviour = bob::Bob::default(); @@ -84,6 +91,7 @@ mod e2e_test { let bob_swap = bob::swap( bob_btc_wallet.clone(), bob_xmr_wallet.clone(), + db, btc.as_sat(), alice_multiaddr, cmd_tx, From 823add218eb7c3779b188285628c589266882216 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Mon, 2 Nov 2020 17:44:53 +1100 Subject: [PATCH 04/22] Remove temporary test tracing guards --- swap/tests/e2e.rs | 8 -------- swap/tests/tor.rs | 5 ----- xmr-btc/tests/e2e.rs | 13 ------------- 3 files changed, 26 deletions(-) diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 8f2d031f..90590e69 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -7,17 +7,9 @@ mod e2e_test { use std::sync::Arc; use swap::{alice, bob, network::transport::build}; use testcontainers::clients::Cli; - use tracing_subscriber::util::SubscriberInitExt; #[tokio::test] async fn swap() { - let _guard = tracing_subscriber::fmt() - .with_env_filter( - "swap=debug,xmr_btc=debug,hyper=off,reqwest=off,monero_harness=info,testcontainers=info,libp2p=debug", - ) - .with_ansi(false) - .set_default(); - let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" .parse() .expect("failed to parse Alice's address"); diff --git a/swap/tests/tor.rs b/swap/tests/tor.rs index 1a0a6de9..7fd3537f 100644 --- a/swap/tests/tor.rs +++ b/swap/tests/tor.rs @@ -13,7 +13,6 @@ mod tor_test { onion::TorSecretKeyV3, utils::{run_tor, AutoKillChild}, }; - use tracing_subscriber::util::SubscriberInitExt; async fn hello_world( _req: hyper::Request, @@ -76,10 +75,6 @@ mod tor_test { #[tokio::test] async fn test_tor_control_port() -> Result<()> { - let _guard = tracing_subscriber::fmt() - .with_env_filter("info") - .set_default(); - // start tmp tor let (_child, control_port, proxy_port, _tmp_torrc) = run_tmp_tor()?; diff --git a/xmr-btc/tests/e2e.rs b/xmr-btc/tests/e2e.rs index 4c93bd47..373c759e 100644 --- a/xmr-btc/tests/e2e.rs +++ b/xmr-btc/tests/e2e.rs @@ -13,7 +13,6 @@ mod tests { 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}, @@ -22,10 +21,6 @@ mod tests { #[tokio::test] async fn happy_path() { - let _guard = tracing_subscriber::fmt() - .with_env_filter("info") - .set_default(); - let cli = Cli::default(); let (monero, _container) = Monero::new(&cli, Some("hp".to_string()), vec![ "alice".to_string(), @@ -101,10 +96,6 @@ mod tests { #[tokio::test] async fn both_refund() { - let _guard = tracing_subscriber::fmt() - .with_env_filter("info") - .set_default(); - let cli = Cli::default(); let (monero, _container) = Monero::new(&cli, Some("br".to_string()), vec![ "alice".to_string(), @@ -182,10 +173,6 @@ mod tests { #[tokio::test] async fn alice_punishes() { - let _guard = tracing_subscriber::fmt() - .with_env_filter("info") - .set_default(); - let cli = Cli::default(); let (monero, _containers) = Monero::new(&cli, Some("ap".to_string()), vec![ "alice".to_string(), From 02075c2a1dc6fdf5357cdec3d604040e2701aaac Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Tue, 3 Nov 2020 14:23:03 +1100 Subject: [PATCH 05/22] Support saving multiple swaps By replacing `LAST_STATE_KEY` with a swap ID passed as an argument to inserting and reading from the database. --- swap/Cargo.toml | 1 + swap/src/alice.rs | 16 +++++++++------- swap/src/bob.rs | 17 ++++++++++------- swap/src/storage.rs | 21 ++++++++++----------- 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 8cb330b7..3d66058a 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -39,6 +39,7 @@ tracing-futures = { version = "0.2", features = ["std-future", "futures-03"] } tracing-log = "0.1" tracing-subscriber = { version = "0.2", default-features = false, features = ["fmt", "ansi", "env-filter"] } url = "2.1" +uuid = { version = "0.8", features = ["serde", "v4"] } void = "1" xmr-btc = { path = "../xmr-btc" } diff --git a/swap/src/alice.rs b/swap/src/alice.rs index 10092169..a314366e 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -13,6 +13,7 @@ use rand::rngs::OsRng; use std::{sync::Arc, time::Duration}; use tokio::sync::Mutex; use tracing::{debug, info, warn}; +use uuid::Uuid; mod amounts; mod message0; @@ -175,7 +176,8 @@ pub async fn swap( other => panic!("Unexpected event: {:?}", other), }; - db.insert_latest_state(&storage::Alice::Handshaken(state3.clone())) + let swap_id = Uuid::new_v4(); + db.insert_latest_state(swap_id, &storage::Alice::Handshaken(state3.clone())) .await?; info!("Handshake complete, we now have State3 for Alice."); @@ -203,14 +205,14 @@ pub async fn swap( public_spend_key, public_view_key, }) => { - db.insert_latest_state(&storage::Alice::BtcLocked(state3.clone())) + db.insert_latest_state(swap_id, &storage::Alice::BtcLocked(state3.clone())) .await?; let (transfer_proof, _) = monero_wallet .transfer(public_spend_key, public_view_key, amount) .await?; - db.insert_latest_state(&storage::Alice::XmrLocked(state3.clone())) + db.insert_latest_state(swap_id, &storage::Alice::XmrLocked(state3.clone())) .await?; let mut guard = network.as_ref().lock().await; @@ -219,7 +221,7 @@ pub async fn swap( } GeneratorState::Yielded(Action::RedeemBtc(tx)) => { - db.insert_latest_state(&storage::Alice::BtcRedeemable { + db.insert_latest_state(swap_id, &storage::Alice::BtcRedeemable { state: state3.clone(), redeem_tx: tx.clone(), }) @@ -231,7 +233,7 @@ pub async fn swap( let _ = bitcoin_wallet.broadcast_signed_transaction(tx).await?; } GeneratorState::Yielded(Action::PunishBtc(tx)) => { - db.insert_latest_state(&storage::Alice::BtcPunishable(state3.clone())) + db.insert_latest_state(swap_id, &storage::Alice::BtcPunishable(state3.clone())) .await?; let _ = bitcoin_wallet.broadcast_signed_transaction(tx).await?; @@ -240,7 +242,7 @@ pub async fn swap( spend_key, view_key, }) => { - db.insert_latest_state(&storage::Alice::BtcRefunded { + db.insert_latest_state(swap_id, &storage::Alice::BtcRefunded { state: state3.clone(), spend_key, view_key, @@ -252,7 +254,7 @@ pub async fn swap( .await?; } GeneratorState::Complete(()) => { - db.insert_latest_state(&storage::Alice::SwapComplete) + db.insert_latest_state(swap_id, &storage::Alice::SwapComplete) .await?; return Ok(()); diff --git a/swap/src/bob.rs b/swap/src/bob.rs index c72831f2..bbd7533c 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -13,6 +13,7 @@ use rand::rngs::OsRng; use std::{process, sync::Arc, time::Duration}; use tokio::sync::Mutex; use tracing::{debug, info, warn}; +use uuid::Uuid; mod amounts; mod message0; @@ -142,7 +143,8 @@ pub async fn swap( other => panic!("unexpected event: {:?}", other), }; - db.insert_latest_state(&storage::Bob::Handshaken(state2.clone())) + let swap_id = Uuid::new_v4(); + db.insert_latest_state(swap_id, &storage::Bob::Handshaken(state2.clone())) .await?; swarm.send_message2(alice.clone(), state2.next_message()); @@ -170,11 +172,11 @@ pub async fn swap( let _ = bitcoin_wallet .broadcast_signed_transaction(signed_tx_lock) .await?; - db.insert_latest_state(&storage::Bob::BtcLocked(state2.clone())) + db.insert_latest_state(swap_id, &storage::Bob::BtcLocked(state2.clone())) .await?; } GeneratorState::Yielded(bob::Action::SendBtcRedeemEncsig(tx_redeem_encsig)) => { - db.insert_latest_state(&storage::Bob::XmrLocked(state2.clone())) + db.insert_latest_state(swap_id, &storage::Bob::XmrLocked(state2.clone())) .await?; let mut guard = network.as_ref().lock().await; @@ -194,7 +196,7 @@ pub async fn swap( spend_key, view_key, }) => { - db.insert_latest_state(&storage::Bob::BtcRedeemed(state2.clone())) + db.insert_latest_state(swap_id, &storage::Bob::BtcRedeemed(state2.clone())) .await?; monero_wallet @@ -202,7 +204,7 @@ pub async fn swap( .await?; } GeneratorState::Yielded(bob::Action::CancelBtc(tx_cancel)) => { - db.insert_latest_state(&storage::Bob::BtcRefundable(state2.clone())) + db.insert_latest_state(swap_id, &storage::Bob::BtcRefundable(state2.clone())) .await?; let _ = bitcoin_wallet @@ -210,7 +212,7 @@ pub async fn swap( .await?; } GeneratorState::Yielded(bob::Action::RefundBtc(tx_refund)) => { - db.insert_latest_state(&storage::Bob::BtcRefundable(state2.clone())) + db.insert_latest_state(swap_id, &storage::Bob::BtcRefundable(state2.clone())) .await?; let _ = bitcoin_wallet @@ -218,7 +220,8 @@ pub async fn swap( .await?; } GeneratorState::Complete(()) => { - db.insert_latest_state(&storage::Bob::SwapComplete).await?; + db.insert_latest_state(swap_id, &storage::Bob::SwapComplete) + .await?; return Ok(()); } diff --git a/swap/src/storage.rs b/swap/src/storage.rs index 21541858..14c6c896 100644 --- a/swap/src/storage.rs +++ b/swap/src/storage.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, Context, Result}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::path::Path; +use uuid::Uuid; use xmr_btc::{alice, bob, monero, serde::monero_private_key}; #[allow(clippy::large_enum_variant)] @@ -45,9 +46,6 @@ impl Database where T: Serialize + DeserializeOwned, { - // TODO: serialize using lazy/one-time initlisation - const LAST_STATE_KEY: &'static str = "latest_state"; - pub fn open(path: &Path) -> Result { let db = sled::open(path).with_context(|| format!("Could not open the DB at {:?}", path))?; @@ -60,8 +58,8 @@ where // TODO: Add method to update state - pub async fn insert_latest_state(&self, state: &T) -> Result<()> { - let key = serialize(&Self::LAST_STATE_KEY)?; + pub async fn insert_latest_state(&self, swap_id: Uuid, state: &T) -> Result<()> { + let key = serialize(&swap_id)?; let new_value = serialize(&state).context("Could not serialize new state value")?; let old_value = self.db.get(&key)?; @@ -79,8 +77,8 @@ where .context("Could not flush db") } - pub fn get_latest_state(&self) -> anyhow::Result { - let key = serialize(&Self::LAST_STATE_KEY)?; + pub fn get_latest_state(&self, swap_id: Uuid) -> anyhow::Result { + let key = serialize(&swap_id)?; let encoded = self .db @@ -172,20 +170,21 @@ mod tests { tx_punish_sig, }; - db.insert_latest_state(&state) + let swap_id = Uuid::new_v4(); + db.insert_latest_state(swap_id, &state) .await .expect("Failed to save state the first time"); let recovered: TestState = db - .get_latest_state() + .get_latest_state(swap_id) .expect("Failed to recover state the first time"); // We insert and recover twice to ensure database implementation allows the // caller to write to an existing key - db.insert_latest_state(&recovered) + db.insert_latest_state(swap_id, &recovered) .await .expect("Failed to save state the second time"); let recovered: TestState = db - .get_latest_state() + .get_latest_state(swap_id) .expect("Failed to recover state the second time"); assert_eq!(state, recovered); From f9cfc2abe357350dfa6d0b3e5b09dd9c12e98c0a Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Tue, 3 Nov 2020 15:26:47 +1100 Subject: [PATCH 06/22] Remove generics from Database --- swap/src/alice.rs | 43 ++++++++----- swap/src/bob.rs | 16 ++--- swap/src/main.rs | 4 +- swap/src/storage.rs | 147 +++++++++++++++++++------------------------ xmr-btc/src/alice.rs | 2 +- xmr-btc/src/bob.rs | 2 +- 6 files changed, 103 insertions(+), 111 deletions(-) diff --git a/swap/src/alice.rs b/swap/src/alice.rs index a314366e..0b89d2b4 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -45,7 +45,7 @@ use xmr_btc::{ pub async fn swap( bitcoin_wallet: Arc, monero_wallet: Arc, - db: Database, + db: Database, listen: Multiaddr, transport: SwapTransport, behaviour: Alice, @@ -177,7 +177,7 @@ pub async fn swap( }; let swap_id = Uuid::new_v4(); - db.insert_latest_state(swap_id, &storage::Alice::Handshaken(state3.clone())) + db.insert_latest_state(swap_id, storage::Alice::Handshaken(state3.clone()).into()) .await?; info!("Handshake complete, we now have State3 for Alice."); @@ -205,14 +205,14 @@ pub async fn swap( public_spend_key, public_view_key, }) => { - db.insert_latest_state(swap_id, &storage::Alice::BtcLocked(state3.clone())) + db.insert_latest_state(swap_id, storage::Alice::BtcLocked(state3.clone()).into()) .await?; let (transfer_proof, _) = monero_wallet .transfer(public_spend_key, public_view_key, amount) .await?; - db.insert_latest_state(swap_id, &storage::Alice::XmrLocked(state3.clone())) + db.insert_latest_state(swap_id, storage::Alice::XmrLocked(state3.clone()).into()) .await?; let mut guard = network.as_ref().lock().await; @@ -221,10 +221,14 @@ pub async fn swap( } GeneratorState::Yielded(Action::RedeemBtc(tx)) => { - db.insert_latest_state(swap_id, &storage::Alice::BtcRedeemable { - state: state3.clone(), - redeem_tx: tx.clone(), - }) + db.insert_latest_state( + swap_id, + storage::Alice::BtcRedeemable { + state: state3.clone(), + redeem_tx: tx.clone(), + } + .into(), + ) .await?; let _ = bitcoin_wallet.broadcast_signed_transaction(tx).await?; @@ -233,8 +237,11 @@ pub async fn swap( let _ = bitcoin_wallet.broadcast_signed_transaction(tx).await?; } GeneratorState::Yielded(Action::PunishBtc(tx)) => { - db.insert_latest_state(swap_id, &storage::Alice::BtcPunishable(state3.clone())) - .await?; + db.insert_latest_state( + swap_id, + storage::Alice::BtcPunishable(state3.clone()).into(), + ) + .await?; let _ = bitcoin_wallet.broadcast_signed_transaction(tx).await?; } @@ -242,11 +249,15 @@ pub async fn swap( spend_key, view_key, }) => { - db.insert_latest_state(swap_id, &storage::Alice::BtcRefunded { - state: state3.clone(), - spend_key, - view_key, - }) + db.insert_latest_state( + swap_id, + storage::Alice::BtcRefunded { + state: state3.clone(), + spend_key, + view_key, + } + .into(), + ) .await?; monero_wallet @@ -254,7 +265,7 @@ pub async fn swap( .await?; } GeneratorState::Complete(()) => { - db.insert_latest_state(swap_id, &storage::Alice::SwapComplete) + db.insert_latest_state(swap_id, storage::Alice::SwapComplete.into()) .await?; return Ok(()); diff --git a/swap/src/bob.rs b/swap/src/bob.rs index bbd7533c..f9217dec 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -44,7 +44,7 @@ use xmr_btc::{ pub async fn swap( bitcoin_wallet: Arc, monero_wallet: Arc, - db: Database, + db: Database, btc: u64, addr: Multiaddr, mut cmd_tx: Sender, @@ -144,7 +144,7 @@ pub async fn swap( }; let swap_id = Uuid::new_v4(); - db.insert_latest_state(swap_id, &storage::Bob::Handshaken(state2.clone())) + db.insert_latest_state(swap_id, storage::Bob::Handshaken(state2.clone()).into()) .await?; swarm.send_message2(alice.clone(), state2.next_message()); @@ -172,11 +172,11 @@ pub async fn swap( let _ = bitcoin_wallet .broadcast_signed_transaction(signed_tx_lock) .await?; - db.insert_latest_state(swap_id, &storage::Bob::BtcLocked(state2.clone())) + db.insert_latest_state(swap_id, storage::Bob::BtcLocked(state2.clone()).into()) .await?; } GeneratorState::Yielded(bob::Action::SendBtcRedeemEncsig(tx_redeem_encsig)) => { - db.insert_latest_state(swap_id, &storage::Bob::XmrLocked(state2.clone())) + db.insert_latest_state(swap_id, storage::Bob::XmrLocked(state2.clone()).into()) .await?; let mut guard = network.as_ref().lock().await; @@ -196,7 +196,7 @@ pub async fn swap( spend_key, view_key, }) => { - db.insert_latest_state(swap_id, &storage::Bob::BtcRedeemed(state2.clone())) + db.insert_latest_state(swap_id, storage::Bob::BtcRedeemed(state2.clone()).into()) .await?; monero_wallet @@ -204,7 +204,7 @@ pub async fn swap( .await?; } GeneratorState::Yielded(bob::Action::CancelBtc(tx_cancel)) => { - db.insert_latest_state(swap_id, &storage::Bob::BtcRefundable(state2.clone())) + db.insert_latest_state(swap_id, storage::Bob::BtcRefundable(state2.clone()).into()) .await?; let _ = bitcoin_wallet @@ -212,7 +212,7 @@ pub async fn swap( .await?; } GeneratorState::Yielded(bob::Action::RefundBtc(tx_refund)) => { - db.insert_latest_state(swap_id, &storage::Bob::BtcRefundable(state2.clone())) + db.insert_latest_state(swap_id, storage::Bob::BtcRefundable(state2.clone()).into()) .await?; let _ = bitcoin_wallet @@ -220,7 +220,7 @@ pub async fn swap( .await?; } GeneratorState::Complete(()) => { - db.insert_latest_state(swap_id, &storage::Bob::SwapComplete) + db.insert_latest_state(swap_id, storage::Bob::SwapComplete.into()) .await?; return Ok(()); diff --git a/swap/src/main.rs b/swap/src/main.rs index 3f7a701e..5f57553d 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -156,7 +156,7 @@ async fn create_tor_service( async fn swap_as_alice( bitcoin_wallet: Arc, monero_wallet: Arc, - db: Database, + db: Database, addr: Multiaddr, transport: SwapTransport, behaviour: Alice, @@ -167,7 +167,7 @@ async fn swap_as_alice( async fn swap_as_bob( bitcoin_wallet: Arc, monero_wallet: Arc, - db: Database, + db: Database, sats: u64, alice: Multiaddr, transport: SwapTransport, diff --git a/swap/src/storage.rs b/swap/src/storage.rs index 14c6c896..7f538fde 100644 --- a/swap/src/storage.rs +++ b/swap/src/storage.rs @@ -5,7 +5,14 @@ use uuid::Uuid; use xmr_btc::{alice, bob, monero, serde::monero_private_key}; #[allow(clippy::large_enum_variant)] -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub enum Swap { + Alice(Alice), + Bob(Bob), +} + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub enum Alice { Handshaken(alice::State3), BtcLocked(alice::State3), @@ -24,7 +31,7 @@ pub enum Alice { SwapComplete, } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub enum Bob { Handshaken(bob::State2), BtcLocked(bob::State2), @@ -34,54 +41,54 @@ pub enum Bob { SwapComplete, } -pub struct Database -where - T: Serialize + DeserializeOwned, -{ - db: sled::Db, - _marker: std::marker::PhantomData, +impl From for Swap { + fn from(from: Alice) -> Self { + Swap::Alice(from) + } } -impl Database -where - T: Serialize + DeserializeOwned, -{ +impl From for Swap { + fn from(from: Bob) -> Self { + Swap::Bob(from) + } +} + +pub struct Database(sled::Db); + +impl Database { pub fn open(path: &Path) -> Result { let db = sled::open(path).with_context(|| format!("Could not open the DB at {:?}", path))?; - Ok(Database { - db, - _marker: Default::default(), - }) + Ok(Database(db)) } // TODO: Add method to update state - pub async fn insert_latest_state(&self, swap_id: Uuid, state: &T) -> Result<()> { + pub async fn insert_latest_state(&self, swap_id: Uuid, state: Swap) -> Result<()> { let key = serialize(&swap_id)?; let new_value = serialize(&state).context("Could not serialize new state value")?; - let old_value = self.db.get(&key)?; + let old_value = self.0.get(&key)?; - self.db + self.0 .compare_and_swap(key, old_value, Some(new_value)) .context("Could not write in the DB")? .context("Stored swap somehow changed, aborting saving")?; // TODO: see if this can be done through sled config - self.db + self.0 .flush_async() .await .map(|_| ()) .context("Could not flush db") } - pub fn get_latest_state(&self, swap_id: Uuid) -> anyhow::Result { + pub fn get_latest_state(&self, swap_id: Uuid) -> anyhow::Result { let key = serialize(&swap_id)?; let encoded = self - .db + .0 .get(&key)? .ok_or_else(|| anyhow!("State does not exist {:?}", key))?; @@ -106,87 +113,61 @@ where #[cfg(test)] mod tests { - #![allow(non_snake_case)] use super::*; - use bitcoin::SigHash; - use rand::rngs::OsRng; - use serde::{Deserialize, Serialize}; - use std::str::FromStr; - use xmr_btc::{cross_curve_dleq, monero, serde::monero_private_key}; - - #[derive(Debug, Serialize, Deserialize, PartialEq)] - pub struct TestState { - A: xmr_btc::bitcoin::PublicKey, - a: xmr_btc::bitcoin::SecretKey, - s_a: cross_curve_dleq::Scalar, - #[serde(with = "monero_private_key")] - s_b: monero::PrivateKey, - S_a_monero: ::monero::PublicKey, - S_a_bitcoin: xmr_btc::bitcoin::PublicKey, - v: xmr_btc::monero::PrivateViewKey, - #[serde(with = "::bitcoin::util::amount::serde::as_sat")] - btc: ::bitcoin::Amount, - xmr: xmr_btc::monero::Amount, - refund_timelock: u32, - refund_address: ::bitcoin::Address, - transaction: ::bitcoin::Transaction, - tx_punish_sig: xmr_btc::bitcoin::Signature, + + #[tokio::test] + async fn can_write_and_read_to_multiple_keys() { + let db_dir = tempfile::tempdir().unwrap(); + let db = Database::open(db_dir.path()).unwrap(); + + let state_1 = Swap::Alice(Alice::SwapComplete); + let swap_id_1 = Uuid::new_v4(); + db.insert_latest_state(swap_id_1, state_1.clone()) + .await + .expect("Failed to save second state"); + + let state_2 = Swap::Bob(Bob::SwapComplete); + let swap_id_2 = Uuid::new_v4(); + db.insert_latest_state(swap_id_2, state_2.clone()) + .await + .expect("Failed to save first state"); + + let recovered_1 = db + .get_latest_state(swap_id_1) + .expect("Failed to recover first state"); + + let recovered_2 = db + .get_latest_state(swap_id_2) + .expect("Failed to recover second state"); + + assert_eq!(recovered_1, state_1); + assert_eq!(recovered_2, state_2); } #[tokio::test] - async fn recover_state_from_db() { + async fn can_write_twice_to_one_key() { let db_dir = tempfile::tempdir().unwrap(); let db = Database::open(db_dir.path()).unwrap(); - let a = xmr_btc::bitcoin::SecretKey::new_random(&mut OsRng); - let s_a = cross_curve_dleq::Scalar::random(&mut OsRng); - let s_b = monero::PrivateKey::from_scalar(monero::Scalar::random(&mut OsRng)); - let v_a = xmr_btc::monero::PrivateViewKey::new_random(&mut OsRng); - let S_a_monero = monero::PublicKey::from_private_key(&monero::PrivateKey { - scalar: s_a.into_ed25519(), - }); - let S_a_bitcoin = s_a.into_secp256k1().into(); - let tx_punish_sig = a.sign(SigHash::default()); - - let state = TestState { - A: a.public(), - a, - s_b, - s_a, - S_a_monero, - S_a_bitcoin, - v: v_a, - btc: ::bitcoin::Amount::from_sat(100), - xmr: xmr_btc::monero::Amount::from_piconero(1000), - refund_timelock: 0, - refund_address: ::bitcoin::Address::from_str("1L5wSMgerhHg8GZGcsNmAx5EXMRXSKR3He") - .unwrap(), - transaction: ::bitcoin::Transaction { - version: 0, - lock_time: 0, - input: vec![::bitcoin::TxIn::default()], - output: vec![::bitcoin::TxOut::default()], - }, - tx_punish_sig, - }; + let state = Swap::Alice(Alice::SwapComplete); let swap_id = Uuid::new_v4(); - db.insert_latest_state(swap_id, &state) + db.insert_latest_state(swap_id, state.clone()) .await .expect("Failed to save state the first time"); - let recovered: TestState = db + let recovered = db .get_latest_state(swap_id) .expect("Failed to recover state the first time"); // We insert and recover twice to ensure database implementation allows the // caller to write to an existing key - db.insert_latest_state(swap_id, &recovered) + db.insert_latest_state(swap_id, recovered) .await .expect("Failed to save state the second time"); - let recovered: TestState = db + let recovered = db .get_latest_state(swap_id) .expect("Failed to recover state the second time"); - assert_eq!(state, recovered); + assert_eq!(recovered, state); } } diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 99859e8d..b0382e63 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -684,7 +684,7 @@ impl State2 { } } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub struct State3 { pub a: bitcoin::SecretKey, pub B: bitcoin::PublicKey, diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index d92303b2..26c26374 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -495,7 +495,7 @@ impl State1 { } } -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub struct State2 { pub A: bitcoin::PublicKey, pub b: bitcoin::SecretKey, From ecbfed9c11c40ab92496407985080a0495be146b Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Tue, 3 Nov 2020 15:49:00 +1100 Subject: [PATCH 07/22] Add Database API to fetch all swaps --- swap/src/storage.rs | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/swap/src/storage.rs b/swap/src/storage.rs index 7f538fde..3a797df9 100644 --- a/swap/src/storage.rs +++ b/swap/src/storage.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::path::Path; use uuid::Uuid; @@ -95,6 +95,25 @@ impl Database { let state = deserialize(&encoded).context("Could not deserialize state")?; Ok(state) } + + pub fn all(&self) -> Result> { + self.0 + .iter() + .map(|item| match item { + Ok((key, value)) => { + let swap_id = deserialize::(&key); + let swap = deserialize::(&value).context("failed to deserialize swap"); + + match (swap_id, swap) { + (Ok(swap_id), Ok(swap)) => Ok((swap_id, swap)), + (Ok(_), Err(err)) => Err(err), + _ => bail!("failed to deserialize swap"), + } + } + Err(err) => Err(err).context("failed to retrieve swap from DB"), + }) + .collect() + } } pub fn serialize(t: &T) -> anyhow::Result> @@ -170,4 +189,28 @@ mod tests { assert_eq!(recovered, state); } + + #[tokio::test] + async fn can_fetch_all_keys() { + let db_dir = tempfile::tempdir().unwrap(); + let db = Database::open(db_dir.path()).unwrap(); + + let state_1 = Swap::Alice(Alice::SwapComplete); + let swap_id_1 = Uuid::new_v4(); + db.insert_latest_state(swap_id_1, state_1.clone()) + .await + .expect("Failed to save second state"); + + let state_2 = Swap::Bob(Bob::SwapComplete); + let swap_id_2 = Uuid::new_v4(); + db.insert_latest_state(swap_id_2, state_2.clone()) + .await + .expect("Failed to save first state"); + + let swaps = db.all().unwrap(); + + assert_eq!(swaps.len(), 2); + assert!(swaps.contains(&(swap_id_1, state_1))); + assert!(swaps.contains(&(swap_id_2, state_2))); + } } From ac2cfd7f656cfa1f7ce301ac888df294232d33e1 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Tue, 3 Nov 2020 16:44:04 +1100 Subject: [PATCH 08/22] Print swap history on --history flag --- swap/Cargo.toml | 1 + swap/src/main.rs | 12 +++++------- swap/src/storage.rs | 38 +++++++++++++++++++++++++++++++++++++- swap/tests/e2e.rs | 5 +++++ 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 3d66058a..71e88626 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -21,6 +21,7 @@ libp2p-tokio-socks5 = "0.4" log = { version = "0.4", features = ["serde"] } monero = { version = "0.9", features = ["serde_support"] } monero-harness = { path = "../monero-harness" } +prettytable-rs = "0.8" rand = "0.7" reqwest = { version = "0.10", default-features = false, features = ["socks"] } serde = { version = "1", features = ["derive"] } diff --git a/swap/src/main.rs b/swap/src/main.rs index 5f57553d..d7c1fe23 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -16,6 +16,7 @@ use anyhow::Result; use futures::{channel::mpsc, StreamExt}; use libp2p::Multiaddr; use log::LevelFilter; +use prettytable::{row, Table}; use std::{io, io::Write, process, sync::Arc}; use structopt::StructOpt; use swap::{ @@ -30,6 +31,9 @@ use swap::{ use tempfile::tempdir; use tracing::info; +#[macro_use] +extern crate prettytable; + mod cli; mod trace; @@ -81,13 +85,7 @@ async fn main() -> Result<()> { let monero_wallet = Arc::new(monero::Wallet::new(monerod_url)); - let db = Database::open(db_dir.path()).unwrap(); - - swap_as_alice( - bitcoin_wallet, - monero_wallet, - db - listen_addr, + swap_as_alice(bitcoin_wallet, monero_wallet, dblisten_addr, transport, behaviour, ) diff --git a/swap/src/storage.rs b/swap/src/storage.rs index 3a797df9..8a1fc5ed 100644 --- a/swap/src/storage.rs +++ b/swap/src/storage.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, bail, Context, Result}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::path::Path; +use std::{fmt::Display, path::Path}; use uuid::Uuid; use xmr_btc::{alice, bob, monero, serde::monero_private_key}; @@ -53,6 +53,42 @@ impl From for Swap { } } +impl Display for Swap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Swap::Alice(alice) => Display::fmt(alice, f), + Swap::Bob(bob) => Display::fmt(bob, f), + } + } +} + +impl Display for Alice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Alice::Handshaken(_) => f.write_str("Handshake complete"), + Alice::BtcLocked(_) => f.write_str("Bitcoin locked"), + Alice::XmrLocked(_) => f.write_str("Monero locked"), + Alice::BtcRedeemable { .. } => f.write_str("Bitcoin redeemable"), + Alice::BtcPunishable(_) => f.write_str("Bitcoin punishable"), + Alice::BtcRefunded { .. } => f.write_str("Monero refundable"), + Alice::SwapComplete => f.write_str("Swap complete"), + } + } +} + +impl Display for Bob { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Bob::Handshaken(_) => f.write_str("Handshake complete"), + Bob::BtcLocked(_) | Bob::XmrLocked(_) | Bob::BtcRefundable(_) => { + f.write_str("Bitcoin refundable") + } + Bob::BtcRedeemed(_) => f.write_str("Monero redeemable"), + Bob::SwapComplete => f.write_str("Swap complete"), + } + } +} + pub struct Database(sled::Db); impl Database { diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 90590e69..1f293d3c 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -8,6 +8,11 @@ mod e2e_test { use swap::{alice, bob, network::transport::build}; use testcontainers::clients::Cli; + // NOTE: For some reason running these tests overflows the stack. In order to + // mitigate this run them with: + // + // RUST_MIN_STACK=100000000 cargo test + #[tokio::test] async fn swap() { let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" From 33a111d879abd1e3680f7a48f8175e4f96d926fc Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Tue, 3 Nov 2020 16:53:28 +1100 Subject: [PATCH 09/22] Move Swap state out of storage --- swap/src/alice.rs | 22 +++++------ swap/src/bob.rs | 17 +++++---- swap/src/lib.rs | 1 + swap/src/state.rs | 88 ++++++++++++++++++++++++++++++++++++++++++ swap/src/storage.rs | 93 +++------------------------------------------ swap/tests/e2e.rs | 3 +- 6 files changed, 114 insertions(+), 110 deletions(-) create mode 100644 swap/src/state.rs diff --git a/swap/src/alice.rs b/swap/src/alice.rs index 0b89d2b4..b419f5a9 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -32,7 +32,8 @@ use crate::{ transport::SwapTransport, TokioExecutor, }, - storage::{self, Database}, + state, + storage::Database, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; use xmr_btc::{ @@ -177,7 +178,7 @@ pub async fn swap( }; let swap_id = Uuid::new_v4(); - db.insert_latest_state(swap_id, storage::Alice::Handshaken(state3.clone()).into()) + db.insert_latest_state(swap_id, state::Alice::Handshaken(state3.clone()).into()) .await?; info!("Handshake complete, we now have State3 for Alice."); @@ -205,14 +206,14 @@ pub async fn swap( public_spend_key, public_view_key, }) => { - db.insert_latest_state(swap_id, storage::Alice::BtcLocked(state3.clone()).into()) + db.insert_latest_state(swap_id, state::Alice::BtcLocked(state3.clone()).into()) .await?; let (transfer_proof, _) = monero_wallet .transfer(public_spend_key, public_view_key, amount) .await?; - db.insert_latest_state(swap_id, storage::Alice::XmrLocked(state3.clone()).into()) + db.insert_latest_state(swap_id, state::Alice::XmrLocked(state3.clone()).into()) .await?; let mut guard = network.as_ref().lock().await; @@ -223,7 +224,7 @@ pub async fn swap( GeneratorState::Yielded(Action::RedeemBtc(tx)) => { db.insert_latest_state( swap_id, - storage::Alice::BtcRedeemable { + state::Alice::BtcRedeemable { state: state3.clone(), redeem_tx: tx.clone(), } @@ -237,11 +238,8 @@ pub async fn swap( let _ = bitcoin_wallet.broadcast_signed_transaction(tx).await?; } GeneratorState::Yielded(Action::PunishBtc(tx)) => { - db.insert_latest_state( - swap_id, - storage::Alice::BtcPunishable(state3.clone()).into(), - ) - .await?; + db.insert_latest_state(swap_id, state::Alice::BtcPunishable(state3.clone()).into()) + .await?; let _ = bitcoin_wallet.broadcast_signed_transaction(tx).await?; } @@ -251,7 +249,7 @@ pub async fn swap( }) => { db.insert_latest_state( swap_id, - storage::Alice::BtcRefunded { + state::Alice::BtcRefunded { state: state3.clone(), spend_key, view_key, @@ -265,7 +263,7 @@ pub async fn swap( .await?; } GeneratorState::Complete(()) => { - db.insert_latest_state(swap_id, storage::Alice::SwapComplete.into()) + db.insert_latest_state(swap_id, state::Alice::SwapComplete.into()) .await?; return Ok(()); diff --git a/swap/src/bob.rs b/swap/src/bob.rs index f9217dec..39a380b5 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -30,7 +30,8 @@ use crate::{ transport::SwapTransport, TokioExecutor, }, - storage::{self, Database}, + state, + storage::Database, Cmd, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; use xmr_btc::{ @@ -144,7 +145,7 @@ pub async fn swap( }; let swap_id = Uuid::new_v4(); - db.insert_latest_state(swap_id, storage::Bob::Handshaken(state2.clone()).into()) + db.insert_latest_state(swap_id, state::Bob::Handshaken(state2.clone()).into()) .await?; swarm.send_message2(alice.clone(), state2.next_message()); @@ -172,11 +173,11 @@ pub async fn swap( let _ = bitcoin_wallet .broadcast_signed_transaction(signed_tx_lock) .await?; - db.insert_latest_state(swap_id, storage::Bob::BtcLocked(state2.clone()).into()) + db.insert_latest_state(swap_id, state::Bob::BtcLocked(state2.clone()).into()) .await?; } GeneratorState::Yielded(bob::Action::SendBtcRedeemEncsig(tx_redeem_encsig)) => { - db.insert_latest_state(swap_id, storage::Bob::XmrLocked(state2.clone()).into()) + db.insert_latest_state(swap_id, state::Bob::XmrLocked(state2.clone()).into()) .await?; let mut guard = network.as_ref().lock().await; @@ -196,7 +197,7 @@ pub async fn swap( spend_key, view_key, }) => { - db.insert_latest_state(swap_id, storage::Bob::BtcRedeemed(state2.clone()).into()) + db.insert_latest_state(swap_id, state::Bob::BtcRedeemed(state2.clone()).into()) .await?; monero_wallet @@ -204,7 +205,7 @@ pub async fn swap( .await?; } GeneratorState::Yielded(bob::Action::CancelBtc(tx_cancel)) => { - db.insert_latest_state(swap_id, storage::Bob::BtcRefundable(state2.clone()).into()) + db.insert_latest_state(swap_id, state::Bob::BtcRefundable(state2.clone()).into()) .await?; let _ = bitcoin_wallet @@ -212,7 +213,7 @@ pub async fn swap( .await?; } GeneratorState::Yielded(bob::Action::RefundBtc(tx_refund)) => { - db.insert_latest_state(swap_id, storage::Bob::BtcRefundable(state2.clone()).into()) + db.insert_latest_state(swap_id, state::Bob::BtcRefundable(state2.clone()).into()) .await?; let _ = bitcoin_wallet @@ -220,7 +221,7 @@ pub async fn swap( .await?; } GeneratorState::Complete(()) => { - db.insert_latest_state(swap_id, storage::Bob::SwapComplete.into()) + db.insert_latest_state(swap_id, state::Bob::SwapComplete.into()) .await?; return Ok(()); diff --git a/swap/src/lib.rs b/swap/src/lib.rs index 32743dd3..401184ed 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -6,6 +6,7 @@ pub mod bitcoin; pub mod bob; pub mod monero; pub mod network; +pub mod state; pub mod storage; pub mod tor; diff --git a/swap/src/state.rs b/swap/src/state.rs new file mode 100644 index 00000000..701c61e0 --- /dev/null +++ b/swap/src/state.rs @@ -0,0 +1,88 @@ +use serde::{Deserialize, Serialize}; +use std::fmt::Display; +use xmr_btc::{alice, bob, monero, serde::monero_private_key}; + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub enum Swap { + Alice(Alice), + Bob(Bob), +} + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub enum Alice { + Handshaken(alice::State3), + BtcLocked(alice::State3), + XmrLocked(alice::State3), + BtcRedeemable { + state: alice::State3, + redeem_tx: bitcoin::Transaction, + }, + BtcPunishable(alice::State3), + BtcRefunded { + state: alice::State3, + #[serde(with = "monero_private_key")] + spend_key: monero::PrivateKey, + view_key: monero::PrivateViewKey, + }, + SwapComplete, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub enum Bob { + Handshaken(bob::State2), + BtcLocked(bob::State2), + XmrLocked(bob::State2), + BtcRedeemed(bob::State2), + BtcRefundable(bob::State2), + SwapComplete, +} + +impl From for Swap { + fn from(from: Alice) -> Self { + Swap::Alice(from) + } +} + +impl From for Swap { + fn from(from: Bob) -> Self { + Swap::Bob(from) + } +} + +impl Display for Swap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Swap::Alice(alice) => Display::fmt(alice, f), + Swap::Bob(bob) => Display::fmt(bob, f), + } + } +} + +impl Display for Alice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Alice::Handshaken(_) => f.write_str("Handshake complete"), + Alice::BtcLocked(_) => f.write_str("Bitcoin locked"), + Alice::XmrLocked(_) => f.write_str("Monero locked"), + Alice::BtcRedeemable { .. } => f.write_str("Bitcoin redeemable"), + Alice::BtcPunishable(_) => f.write_str("Bitcoin punishable"), + Alice::BtcRefunded { .. } => f.write_str("Monero refundable"), + Alice::SwapComplete => f.write_str("Swap complete"), + } + } +} + +impl Display for Bob { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Bob::Handshaken(_) => f.write_str("Handshake complete"), + Bob::BtcLocked(_) | Bob::XmrLocked(_) | Bob::BtcRefundable(_) => { + f.write_str("Bitcoin refundable") + } + Bob::BtcRedeemed(_) => f.write_str("Monero redeemable"), + Bob::SwapComplete => f.write_str("Swap complete"), + } + } +} diff --git a/swap/src/storage.rs b/swap/src/storage.rs index 8a1fc5ed..c0a11a90 100644 --- a/swap/src/storage.rs +++ b/swap/src/storage.rs @@ -1,93 +1,8 @@ +use crate::state::Swap; use anyhow::{anyhow, bail, Context, Result}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{fmt::Display, path::Path}; +use serde::{de::DeserializeOwned, Serialize}; +use std::path::Path; use uuid::Uuid; -use xmr_btc::{alice, bob, monero, serde::monero_private_key}; - -#[allow(clippy::large_enum_variant)] -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] -pub enum Swap { - Alice(Alice), - Bob(Bob), -} - -#[allow(clippy::large_enum_variant)] -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] -pub enum Alice { - Handshaken(alice::State3), - BtcLocked(alice::State3), - XmrLocked(alice::State3), - BtcRedeemable { - state: alice::State3, - redeem_tx: bitcoin::Transaction, - }, - BtcPunishable(alice::State3), - BtcRefunded { - state: alice::State3, - #[serde(with = "monero_private_key")] - spend_key: monero::PrivateKey, - view_key: monero::PrivateViewKey, - }, - SwapComplete, -} - -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] -pub enum Bob { - Handshaken(bob::State2), - BtcLocked(bob::State2), - XmrLocked(bob::State2), - BtcRedeemed(bob::State2), - BtcRefundable(bob::State2), - SwapComplete, -} - -impl From for Swap { - fn from(from: Alice) -> Self { - Swap::Alice(from) - } -} - -impl From for Swap { - fn from(from: Bob) -> Self { - Swap::Bob(from) - } -} - -impl Display for Swap { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Swap::Alice(alice) => Display::fmt(alice, f), - Swap::Bob(bob) => Display::fmt(bob, f), - } - } -} - -impl Display for Alice { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Alice::Handshaken(_) => f.write_str("Handshake complete"), - Alice::BtcLocked(_) => f.write_str("Bitcoin locked"), - Alice::XmrLocked(_) => f.write_str("Monero locked"), - Alice::BtcRedeemable { .. } => f.write_str("Bitcoin redeemable"), - Alice::BtcPunishable(_) => f.write_str("Bitcoin punishable"), - Alice::BtcRefunded { .. } => f.write_str("Monero refundable"), - Alice::SwapComplete => f.write_str("Swap complete"), - } - } -} - -impl Display for Bob { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Bob::Handshaken(_) => f.write_str("Handshake complete"), - Bob::BtcLocked(_) | Bob::XmrLocked(_) | Bob::BtcRefundable(_) => { - f.write_str("Bitcoin refundable") - } - Bob::BtcRedeemed(_) => f.write_str("Monero redeemable"), - Bob::SwapComplete => f.write_str("Swap complete"), - } - } -} pub struct Database(sled::Db); @@ -168,6 +83,8 @@ where #[cfg(test)] mod tests { + use crate::state::{Alice, Bob}; + use super::*; #[tokio::test] diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 1f293d3c..df72ce36 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -68,8 +68,7 @@ mod e2e_test { let alice_transport = build(alice_behaviour.identity()).unwrap(); let db_dir = tempdir().unwrap(); - let db = Database::open(db_dir.path()).unwrap(); - + let db = Database::open(std::path::Path::new("/home/luckysori/test/xmr_btc_swap")).unwrap(); let alice_swap = alice::swap( alice_btc_wallet.clone(), alice_xmr_wallet.clone(), From b989e94322c6b42228ec24322a72ab1275c474ca Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Tue, 3 Nov 2020 16:54:16 +1100 Subject: [PATCH 10/22] Remove stale TODO --- swap/src/storage.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/swap/src/storage.rs b/swap/src/storage.rs index c0a11a90..4a0664b6 100644 --- a/swap/src/storage.rs +++ b/swap/src/storage.rs @@ -14,8 +14,6 @@ impl Database { Ok(Database(db)) } - // TODO: Add method to update state - pub async fn insert_latest_state(&self, swap_id: Uuid, state: Swap) -> Result<()> { let key = serialize(&swap_id)?; let new_value = serialize(&state).context("Could not serialize new state value")?; From 28225f8643c25effc46671184d94ded75f795fec Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Tue, 3 Nov 2020 17:08:31 +1100 Subject: [PATCH 11/22] Implement swap recover function for Alice This introduces a lot of duplication between the binary and the library, but it's okay because this module should only be a temporary measure until we allow recovery to be handled by the original state machine. Also, fix a bug in `xmr_btc::alice::action_generator` caused by the incorrect assumption that Alice's ability to punish Bob could be determined before the cancel transaction hits the blockchain. --- swap/Cargo.toml | 1 + swap/src/lib.rs | 1 + swap/src/main.rs | 28 ++++--- swap/src/recover.rs | 187 +++++++++++++++++++++++++++++++++++++++++++ swap/src/storage.rs | 10 +-- swap/tests/e2e.rs | 15 ++-- xmr-btc/src/alice.rs | 93 +++++---------------- xmr-btc/src/bob.rs | 4 +- 8 files changed, 242 insertions(+), 97 deletions(-) create mode 100644 swap/src/recover.rs diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 71e88626..e8ab1323 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -14,6 +14,7 @@ base64 = "0.12" bitcoin = { version = "0.23", features = ["rand", "use-serde"] } # TODO: Upgrade other crates in this repo to use this version. bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "3be644cd9512c157d3337a189298b8257ed54d04" } derivative = "2" +ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", rev = "510d48ef6a2b19805f7f5c70c598e5b03f668e7a", features = ["libsecp_compat", "serde", "serialization"] } futures = { version = "0.3", default-features = false } genawaiter = "0.99.1" libp2p = { version = "0.29", default-features = false, features = ["tcp-tokio", "yamux", "mplex", "dns", "noise", "request-response"] } diff --git a/swap/src/lib.rs b/swap/src/lib.rs index 401184ed..9039aa74 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -6,6 +6,7 @@ pub mod bitcoin; pub mod bob; pub mod monero; pub mod network; +pub mod recover; pub mod state; pub mod storage; pub mod tor; diff --git a/swap/src/main.rs b/swap/src/main.rs index d7c1fe23..f5c34f89 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -16,7 +16,6 @@ use anyhow::Result; use futures::{channel::mpsc, StreamExt}; use libp2p::Multiaddr; use log::LevelFilter; -use prettytable::{row, Table}; use std::{io, io::Write, process, sync::Arc}; use structopt::StructOpt; use swap::{ @@ -31,13 +30,11 @@ use swap::{ use tempfile::tempdir; use tracing::info; -#[macro_use] -extern crate prettytable; - mod cli; mod trace; use cli::Options; +use swap::storage::Database; // TODO: Add root seed file instead of generating new seed each run. @@ -47,6 +44,9 @@ async fn main() -> Result<()> { trace::init_tracing(LevelFilter::Debug)?; + let db_dir = tempdir()?; + let db = Database::open(db_dir.path()).unwrap(); + match opt { Options::Alice { bitcoind_url, @@ -85,7 +85,11 @@ async fn main() -> Result<()> { let monero_wallet = Arc::new(monero::Wallet::new(monerod_url)); - swap_as_alice(bitcoin_wallet, monero_wallet, dblisten_addr, + swap_as_alice( + bitcoin_wallet, + monero_wallet, + db, + listen_addr, transport, behaviour, ) @@ -115,12 +119,10 @@ async fn main() -> Result<()> { let monero_wallet = Arc::new(monero::Wallet::new(monerod_url)); - let db = Database::open(db_dir.path()).unwrap(); - swap_as_bob( bitcoin_wallet, monero_wallet, - db + db, satoshis, alice_addr, transport, @@ -159,7 +161,15 @@ async fn swap_as_alice( transport: SwapTransport, behaviour: Alice, ) -> Result<()> { - alice::swap(bitcoin_wallet, monero_wallet, addr, transport, behaviour).await + alice::swap( + bitcoin_wallet, + monero_wallet, + db, + addr, + transport, + behaviour, + ) + .await } async fn swap_as_bob( diff --git a/swap/src/recover.rs b/swap/src/recover.rs new file mode 100644 index 00000000..87cb11ee --- /dev/null +++ b/swap/src/recover.rs @@ -0,0 +1,187 @@ +use crate::{ + monero::CreateWalletForOutput, + state::{Alice, Bob, Swap}, +}; +use anyhow::Result; +use futures::{ + future::{select, Either}, + pin_mut, +}; +use xmr_btc::bitcoin::{ + poll_until_block_height_is_gte, BroadcastSignedTransaction, TransactionBlockHeight, TxCancel, + TxPunish, TxRefund, WatchForRawTransaction, +}; + +pub async fn recover( + bitcoin_wallet: crate::bitcoin::Wallet, + monero_wallet: crate::monero::Wallet, + state: Swap, +) -> Result<()> { + match state { + Swap::Alice(state) => alice_recover(bitcoin_wallet, monero_wallet, state).await, + Swap::Bob(state) => bob_recover(bitcoin_wallet, monero_wallet, state).await, + } +} + +pub async fn alice_recover( + bitcoin_wallet: crate::bitcoin::Wallet, + monero_wallet: crate::monero::Wallet, + state: Alice, +) -> Result<()> { + match state { + Alice::Handshaken(_) | Alice::BtcLocked(_) | Alice::SwapComplete => Ok(()), + Alice::XmrLocked(state) => { + let tx_cancel = TxCancel::new( + &state.tx_lock, + state.refund_timelock, + state.a.public(), + state.B.clone(), + ); + + // Ensure that TxCancel is on the blockchain + if bitcoin_wallet + .0 + .get_raw_transaction(tx_cancel.txid()) + .await + .is_err() + { + let tx_lock_height = bitcoin_wallet + .transaction_block_height(state.tx_lock.txid()) + .await; + poll_until_block_height_is_gte( + &bitcoin_wallet, + tx_lock_height + state.refund_timelock, + ) + .await; + + let sig_a = state.a.sign(tx_cancel.digest()); + let sig_b = state.tx_cancel_sig_bob.clone(); + + let tx_cancel = tx_cancel + .clone() + .add_signatures( + &state.tx_lock, + (state.a.public(), sig_a), + (state.B.clone(), sig_b), + ) + .expect("sig_{a,b} to be valid signatures for tx_cancel"); + + // TODO: We should not fail if the transaction is already on the blockchain + bitcoin_wallet + .broadcast_signed_transaction(tx_cancel) + .await?; + } + + let tx_cancel_height = bitcoin_wallet + .transaction_block_height(tx_cancel.txid()) + .await; + let poll_until_bob_can_be_punished = poll_until_block_height_is_gte( + &bitcoin_wallet, + tx_cancel_height + state.punish_timelock, + ); + pin_mut!(poll_until_bob_can_be_punished); + + let tx_refund = TxRefund::new(&tx_cancel, &state.refund_address); + + match select( + bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid()), + poll_until_bob_can_be_punished, + ) + .await + { + Either::Left((tx_refund_published, ..)) => { + let tx_refund_sig = tx_refund + .extract_signature_by_key(tx_refund_published, state.a.public())?; + let tx_refund_encsig = state + .a + .encsign(state.S_b_bitcoin.clone(), tx_refund.digest()); + + let s_b = xmr_btc::bitcoin::recover( + state.S_b_bitcoin, + tx_refund_sig, + tx_refund_encsig, + )?; + let s_b = monero::PrivateKey::from_scalar( + xmr_btc::monero::Scalar::from_bytes_mod_order(s_b.to_bytes()), + ); + + let s_a = monero::PrivateKey { + scalar: state.s_a.into_ed25519(), + }; + + monero_wallet + .create_and_load_wallet_for_output(s_a + s_b, state.v) + .await?; + } + Either::Right(_) => { + let tx_punish = + TxPunish::new(&tx_cancel, &state.punish_address, state.punish_timelock); + + let sig_a = state.a.sign(tx_punish.digest()); + let sig_b = state.tx_cancel_sig_bob.clone(); + + let sig_tx_punish = tx_punish.add_signatures( + &tx_cancel, + (state.a.public(), sig_a), + (state.B.clone(), sig_b), + )?; + + bitcoin_wallet + .broadcast_signed_transaction(sig_tx_punish) + .await?; + } + }; + + Ok(()) + } + Alice::BtcRedeemable { redeem_tx, .. } => { + bitcoin_wallet + .broadcast_signed_transaction(redeem_tx) + .await?; + Ok(()) + } + Alice::BtcPunishable(state) => { + let tx_cancel = TxCancel::new( + &state.tx_lock, + state.refund_timelock, + state.a.public(), + state.B.clone(), + ); + + let tx_punish = TxPunish::new(&tx_cancel, &state.punish_address, state.punish_timelock); + + let sig_a = state.a.sign(tx_punish.digest()); + let sig_b = state.tx_cancel_sig_bob.clone(); + + let sig_tx_punish = tx_punish.add_signatures( + &tx_cancel, + (state.a.public(), sig_a), + (state.B.clone(), sig_b), + )?; + + bitcoin_wallet + .broadcast_signed_transaction(sig_tx_punish) + .await?; + + Ok(()) + } + Alice::BtcRefunded { + view_key, + spend_key, + .. + } => { + monero_wallet + .create_and_load_wallet_for_output(spend_key, view_key) + .await?; + Ok(()) + } + } +} + +pub async fn bob_recover( + _bitcoin_wallet: crate::bitcoin::Wallet, + _monero_wallet: crate::monero::Wallet, + _state: Bob, +) -> Result<()> { + todo!() +} diff --git a/swap/src/storage.rs b/swap/src/storage.rs index 4a0664b6..47c6ce11 100644 --- a/swap/src/storage.rs +++ b/swap/src/storage.rs @@ -33,7 +33,7 @@ impl Database { .context("Could not flush db") } - pub fn get_latest_state(&self, swap_id: Uuid) -> anyhow::Result { + pub fn get_state(&self, swap_id: Uuid) -> anyhow::Result { let key = serialize(&swap_id)?; let encoded = self @@ -103,11 +103,11 @@ mod tests { .expect("Failed to save first state"); let recovered_1 = db - .get_latest_state(swap_id_1) + .get_state(swap_id_1) .expect("Failed to recover first state"); let recovered_2 = db - .get_latest_state(swap_id_2) + .get_state(swap_id_2) .expect("Failed to recover second state"); assert_eq!(recovered_1, state_1); @@ -126,7 +126,7 @@ mod tests { .await .expect("Failed to save state the first time"); let recovered = db - .get_latest_state(swap_id) + .get_state(swap_id) .expect("Failed to recover state the first time"); // We insert and recover twice to ensure database implementation allows the @@ -135,7 +135,7 @@ mod tests { .await .expect("Failed to save state the second time"); let recovered = db - .get_latest_state(swap_id) + .get_state(swap_id) .expect("Failed to recover state the second time"); assert_eq!(recovered, state); diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index df72ce36..bebb052b 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -5,7 +5,8 @@ mod e2e_test { use libp2p::Multiaddr; use monero_harness::Monero; use std::sync::Arc; - use swap::{alice, bob, network::transport::build}; + use swap::{alice, bob, network::transport::build, storage::Database}; + use tempfile::tempdir; use testcontainers::clients::Cli; // NOTE: For some reason running these tests overflows the stack. In order to @@ -48,12 +49,10 @@ mod e2e_test { .await .unwrap(); - let (monero, _container) = Monero::new(&cli, Some("swap_".to_string()), vec![ - "alice".to_string(), - "bob".to_string(), - ]) - .await - .unwrap(); + let (monero, _container) = + Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()]) + .await + .unwrap(); monero .init(vec![("alice", xmr_alice), ("bob", xmr_bob)]) .await @@ -68,7 +67,7 @@ mod e2e_test { let alice_transport = build(alice_behaviour.identity()).unwrap(); let db_dir = tempdir().unwrap(); - let db = Database::open(std::path::Path::new("/home/luckysori/test/xmr_btc_swap")).unwrap(); + let db = Database::open(db_dir.path()).unwrap(); let alice_swap = alice::swap( alice_btc_wallet.clone(), alice_xmr_wallet.clone(), diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index b0382e63..fa35a140 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -97,7 +97,7 @@ where #[derive(Debug)] enum SwapFailed { BeforeBtcLock(Reason), - AfterXmrLock { tx_lock_height: u32, reason: Reason }, + AfterXmrLock(Reason), } /// Reason why the swap has failed. @@ -114,9 +114,7 @@ where #[derive(Debug)] enum RefundFailed { - BtcPunishable { - tx_cancel_was_published: bool, - }, + BtcPunishable, /// Could not find Alice's signature on the refund transaction witness /// stack. BtcRefundSignature, @@ -167,12 +165,7 @@ where .await { Either::Left((encsig, _)) => encsig, - Either::Right(_) => { - return Err(SwapFailed::AfterXmrLock { - reason: Reason::BtcExpired, - tx_lock_height, - }) - } + Either::Right(_) => return Err(SwapFailed::AfterXmrLock(Reason::BtcExpired)), }; tracing::debug!("select returned redeem encsig from message"); @@ -191,10 +184,7 @@ where &tx_redeem.digest(), &tx_redeem_encsig, ) - .map_err(|_| SwapFailed::AfterXmrLock { - reason: Reason::InvalidEncryptedSignature, - tx_lock_height, - })?; + .map_err(|_| SwapFailed::AfterXmrLock(Reason::InvalidEncryptedSignature))?; let sig_a = a.sign(tx_redeem.digest()); let sig_b = @@ -217,12 +207,7 @@ where .await { Either::Left(_) => {} - Either::Right(_) => { - return Err(SwapFailed::AfterXmrLock { - reason: Reason::BtcExpired, - tx_lock_height, - }) - } + Either::Right(_) => return Err(SwapFailed::AfterXmrLock(Reason::BtcExpired)), }; Ok(()) @@ -233,19 +218,8 @@ where error!("swap failed: {:?}", err); } - if let Err(SwapFailed::AfterXmrLock { - reason: Reason::BtcExpired, - tx_lock_height, - }) = swap_result - { + if let Err(SwapFailed::AfterXmrLock(Reason::BtcExpired)) = swap_result { let refund_result: Result<(), RefundFailed> = async { - let poll_until_bob_can_be_punished = poll_until_block_height_is_gte( - bitcoin_client.as_ref(), - tx_lock_height + refund_timelock + punish_timelock, - ) - .shared(); - pin_mut!(poll_until_bob_can_be_punished); - let tx_cancel = bitcoin::TxCancel::new(&tx_lock, refund_timelock, a.public(), B.clone()); let signed_tx_cancel = { @@ -260,19 +234,19 @@ where co.yield_(Action::CancelBtc(signed_tx_cancel)).await; - match select( - bitcoin_client.watch_for_raw_transaction(tx_cancel.txid()), - poll_until_bob_can_be_punished.clone(), + bitcoin_client + .watch_for_raw_transaction(tx_cancel.txid()) + .await; + + let tx_cancel_height = bitcoin_client + .transaction_block_height(tx_cancel.txid()) + .await; + let poll_until_bob_can_be_punished = poll_until_block_height_is_gte( + bitcoin_client.as_ref(), + tx_cancel_height + punish_timelock, ) - .await - { - Either::Left(_) => {} - Either::Right(_) => { - return Err(RefundFailed::BtcPunishable { - tx_cancel_was_published: false, - }) - } - }; + .shared(); + pin_mut!(poll_until_bob_can_be_punished); let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &refund_address); let tx_refund_published = match select( @@ -282,11 +256,7 @@ where .await { Either::Left((tx, _)) => tx, - Either::Right(_) => { - return Err(RefundFailed::BtcPunishable { - tx_cancel_was_published: true, - }); - } + Either::Right(_) => return Err(RefundFailed::BtcPunishable), }; let s_a = monero::PrivateKey { @@ -321,32 +291,9 @@ where // transaction with his refund transaction. Alice would then need to carry on // with the refund on Monero. Doing so may be too verbose with the current, // linear approach. A different design may be required - if let Err(RefundFailed::BtcPunishable { - tx_cancel_was_published, - }) = refund_result - { + if let Err(RefundFailed::BtcPunishable) = refund_result { let tx_cancel = bitcoin::TxCancel::new(&tx_lock, refund_timelock, a.public(), B.clone()); - - if !tx_cancel_was_published { - let tx_cancel_txid = tx_cancel.txid(); - let signed_tx_cancel = { - let sig_a = a.sign(tx_cancel.digest()); - let sig_b = tx_cancel_sig_bob; - - tx_cancel - .clone() - .add_signatures(&tx_lock, (a.public(), sig_a), (B.clone(), sig_b)) - .expect("sig_{a,b} to be valid signatures for tx_cancel") - }; - - co.yield_(Action::CancelBtc(signed_tx_cancel)).await; - - let _ = bitcoin_client - .watch_for_raw_transaction(tx_cancel_txid) - .await; - } - let tx_punish = bitcoin::TxPunish::new(&tx_cancel, &punish_address, punish_timelock); let tx_punish_txid = tx_punish.txid(); diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index 26c26374..e9b98ac8 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -507,10 +507,10 @@ pub struct State2 { btc: bitcoin::Amount, pub xmr: monero::Amount, pub refund_timelock: u32, - punish_timelock: u32, + pub punish_timelock: u32, pub refund_address: bitcoin::Address, pub redeem_address: bitcoin::Address, - punish_address: bitcoin::Address, + pub punish_address: bitcoin::Address, pub tx_lock: bitcoin::TxLock, pub tx_cancel_sig_a: Signature, pub tx_refund_encsig: EncryptedSignature, From 6db40dce2a99a500f1d54c511f0e5e9607a25afc Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Thu, 5 Nov 2020 15:48:31 +1100 Subject: [PATCH 12/22] Implement swap recover function for Bob --- swap/Cargo.toml | 1 + swap/src/recover.rs | 117 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 105 insertions(+), 13 deletions(-) diff --git a/swap/Cargo.toml b/swap/Cargo.toml index e8ab1323..e1371f91 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -29,6 +29,7 @@ serde = { version = "1", features = ["derive"] } serde_cbor = "0.11" serde_derive = "1.0" serde_json = "1" +sha2 = "0.9" sled = "0.34" structopt = "0.3" tempfile = "3" diff --git a/swap/src/recover.rs b/swap/src/recover.rs index 87cb11ee..f3addbf0 100644 --- a/swap/src/recover.rs +++ b/swap/src/recover.rs @@ -3,13 +3,15 @@ use crate::{ state::{Alice, Bob, Swap}, }; use anyhow::Result; +use ecdsa_fun::{adaptor::Adaptor, nonce::Deterministic}; use futures::{ future::{select, Either}, pin_mut, }; +use sha2::Sha256; use xmr_btc::bitcoin::{ poll_until_block_height_is_gte, BroadcastSignedTransaction, TransactionBlockHeight, TxCancel, - TxPunish, TxRefund, WatchForRawTransaction, + TxPunish, TxRedeem, TxRefund, WatchForRawTransaction, }; pub async fn recover( @@ -29,7 +31,7 @@ pub async fn alice_recover( state: Alice, ) -> Result<()> { match state { - Alice::Handshaken(_) | Alice::BtcLocked(_) | Alice::SwapComplete => Ok(()), + Alice::Handshaken(_) | Alice::BtcLocked(_) | Alice::SwapComplete => {} Alice::XmrLocked(state) => { let tx_cancel = TxCancel::new( &state.tx_lock, @@ -131,14 +133,11 @@ pub async fn alice_recover( .await?; } }; - - Ok(()) } Alice::BtcRedeemable { redeem_tx, .. } => { bitcoin_wallet .broadcast_signed_transaction(redeem_tx) .await?; - Ok(()) } Alice::BtcPunishable(state) => { let tx_cancel = TxCancel::new( @@ -162,8 +161,6 @@ pub async fn alice_recover( bitcoin_wallet .broadcast_signed_transaction(sig_tx_punish) .await?; - - Ok(()) } Alice::BtcRefunded { view_key, @@ -173,15 +170,109 @@ pub async fn alice_recover( monero_wallet .create_and_load_wallet_for_output(spend_key, view_key) .await?; - Ok(()) } - } + }; + + Ok(()) } pub async fn bob_recover( - _bitcoin_wallet: crate::bitcoin::Wallet, - _monero_wallet: crate::monero::Wallet, - _state: Bob, + bitcoin_wallet: crate::bitcoin::Wallet, + monero_wallet: crate::monero::Wallet, + state: Bob, ) -> Result<()> { - todo!() + match state { + Bob::Handshaken(_) | Bob::SwapComplete => {} + Bob::BtcLocked(state) | Bob::XmrLocked(state) | Bob::BtcRefundable(state) => { + let tx_cancel = TxCancel::new( + &state.tx_lock, + state.refund_timelock, + state.A.clone(), + state.b.public(), + ); + + // Ensure that TxCancel is on the blockchain + if bitcoin_wallet + .0 + .get_raw_transaction(tx_cancel.txid()) + .await + .is_err() + { + let tx_lock_height = bitcoin_wallet + .transaction_block_height(state.tx_lock.txid()) + .await; + poll_until_block_height_is_gte( + &bitcoin_wallet, + tx_lock_height + state.refund_timelock, + ) + .await; + + let sig_a = state.tx_cancel_sig_a.clone(); + let sig_b = state.b.sign(tx_cancel.digest()); + + let tx_cancel = tx_cancel + .clone() + .add_signatures( + &state.tx_lock, + (state.A.clone(), sig_a), + (state.b.public(), sig_b), + ) + .expect("sig_{a,b} to be valid signatures for tx_cancel"); + + // TODO: We should not fail if the transaction is already on the blockchain + bitcoin_wallet + .broadcast_signed_transaction(tx_cancel) + .await?; + } + + let tx_refund = TxRefund::new(&tx_cancel, &state.refund_address); + let signed_tx_refund = { + let adaptor = Adaptor::>::default(); + let sig_a = adaptor + .decrypt_signature(&state.s_b.into_secp256k1(), state.tx_refund_encsig.clone()); + let sig_b = state.b.sign(tx_refund.digest()); + + tx_refund + .add_signatures( + &tx_cancel, + (state.A.clone(), sig_a), + (state.b.public(), sig_b), + ) + .expect("sig_{a,b} to be valid signatures for tx_refund") + }; + + bitcoin_wallet + .broadcast_signed_transaction(signed_tx_refund) + .await?; + } + Bob::BtcRedeemed(state) => { + let tx_redeem = TxRedeem::new(&state.tx_lock, &state.redeem_address); + let tx_redeem_published = bitcoin_wallet + .0 + .get_raw_transaction(tx_redeem.txid()) + .await?; + + let tx_redeem_encsig = state + .b + .encsign(state.S_a_bitcoin.clone(), tx_redeem.digest()); + let tx_redeem_sig = + tx_redeem.extract_signature_by_key(tx_redeem_published, state.b.public())?; + + let s_a = + xmr_btc::bitcoin::recover(state.S_a_bitcoin, tx_redeem_sig, tx_redeem_encsig)?; + let s_a = monero::PrivateKey::from_scalar( + xmr_btc::monero::Scalar::from_bytes_mod_order(s_a.to_bytes()), + ); + + let s_b = monero::PrivateKey { + scalar: state.s_b.into_ed25519(), + }; + + monero_wallet + .create_and_load_wallet_for_output(s_a + s_b, state.v) + .await?; + } + }; + + Ok(()) } From 0f1a77fa21d8b57e9bb90048e426997411837c23 Mon Sep 17 00:00:00 2001 From: rishflab Date: Fri, 6 Nov 2020 10:13:10 +1100 Subject: [PATCH 13/22] Fix Alice's btc redeem recovery path --- swap/src/recover.rs | 85 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/swap/src/recover.rs b/swap/src/recover.rs index f3addbf0..2abf1334 100644 --- a/swap/src/recover.rs +++ b/swap/src/recover.rs @@ -134,10 +134,87 @@ pub async fn alice_recover( } }; } - Alice::BtcRedeemable { redeem_tx, .. } => { - bitcoin_wallet - .broadcast_signed_transaction(redeem_tx) - .await?; + Alice::BtcRedeemable { redeem_tx, state } => { + let tx_lock_height = bitcoin_wallet + .transaction_block_height(state.tx_lock.txid()) + .await; + + let block_height = bitcoin_wallet.0.block_height().await?; + let refund_absolute_expiry = tx_lock_height + state.refund_timelock; + + // bob cannot cancel + if block_height > refund_absolute_expiry { + bitcoin_wallet + .broadcast_signed_transaction(redeem_tx) + .await?; + } else { + let tx_cancel = TxCancel::new( + &state.tx_lock, + state.refund_timelock, + state.a.public(), + state.B.clone(), + ); + + let tx_cancel_height = bitcoin_wallet + .transaction_block_height(tx_cancel.txid()) + .await; + let poll_until_bob_can_be_punished = poll_until_block_height_is_gte( + &bitcoin_wallet, + tx_cancel_height + state.punish_timelock, + ); + pin_mut!(poll_until_bob_can_be_punished); + + let tx_refund = TxRefund::new(&tx_cancel, &state.refund_address); + + match select( + bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid()), + poll_until_bob_can_be_punished, + ) + .await + { + Either::Left((tx_refund_published, ..)) => { + let tx_refund_sig = tx_refund + .extract_signature_by_key(tx_refund_published, state.a.public())?; + let tx_refund_encsig = state + .a + .encsign(state.S_b_bitcoin.clone(), tx_refund.digest()); + + let s_b = xmr_btc::bitcoin::recover( + state.S_b_bitcoin, + tx_refund_sig, + tx_refund_encsig, + )?; + let s_b = monero::PrivateKey::from_scalar( + xmr_btc::monero::Scalar::from_bytes_mod_order(s_b.to_bytes()), + ); + + let s_a = monero::PrivateKey { + scalar: state.s_a.into_ed25519(), + }; + + monero_wallet + .create_and_load_wallet_for_output(s_a + s_b, state.v) + .await?; + } + Either::Right(_) => { + let tx_punish = + TxPunish::new(&tx_cancel, &state.punish_address, state.punish_timelock); + + let sig_a = state.a.sign(tx_punish.digest()); + let sig_b = state.tx_cancel_sig_bob.clone(); + + let sig_tx_punish = tx_punish.add_signatures( + &tx_cancel, + (state.a.public(), sig_a), + (state.B.clone(), sig_b), + )?; + + bitcoin_wallet + .broadcast_signed_transaction(sig_tx_punish) + .await?; + } + }; + } } Alice::BtcPunishable(state) => { let tx_cancel = TxCancel::new( From 09773dd15b993aa3cdbbb65e4119112b88e8de57 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Thu, 5 Nov 2020 16:55:30 +1100 Subject: [PATCH 14/22] Re-introduce history command --- .gitignore | 4 +++- swap/src/cli.rs | 1 + swap/src/main.rs | 21 ++++++++++++++++++--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index d89bb47f..40a04136 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ - # 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 @@ -154,4 +153,7 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk +# sled DB directory generated during local development +.swap-db/ + # End of https://www.toptal.com/developers/gitignore/api/rust,clion+all,emacs diff --git a/swap/src/cli.rs b/swap/src/cli.rs index aff4d67c..4627afab 100644 --- a/swap/src/cli.rs +++ b/swap/src/cli.rs @@ -33,4 +33,5 @@ pub enum Options { #[structopt(long = "tor")] tor: bool, }, + History, } diff --git a/swap/src/main.rs b/swap/src/main.rs index f5c34f89..77b6a11f 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -16,6 +16,7 @@ use anyhow::Result; use futures::{channel::mpsc, StreamExt}; use libp2p::Multiaddr; use log::LevelFilter; +use prettytable::{row, Table}; use std::{io, io::Write, process, sync::Arc}; use structopt::StructOpt; use swap::{ @@ -27,9 +28,11 @@ use swap::{ network::transport::{build, build_tor, SwapTransport}, Cmd, Rsp, SwapAmounts, }; -use tempfile::tempdir; use tracing::info; +#[macro_use] +extern crate prettytable; + mod cli; mod trace; @@ -44,8 +47,8 @@ async fn main() -> Result<()> { trace::init_tracing(LevelFilter::Debug)?; - let db_dir = tempdir()?; - let db = Database::open(db_dir.path()).unwrap(); + // This currently creates the directory if it's not there in the first place + let db = Database::open(std::path::Path::new("./.swap-db/")).unwrap(); match opt { Options::Alice { @@ -130,6 +133,18 @@ async fn main() -> Result<()> { ) .await?; } + Options::History => { + let mut table = Table::new(); + + table.add_row(row!["SWAP ID", "STATE"]); + + for (swap_id, state) in db.all()? { + table.add_row(row![swap_id, state]); + } + + // Print the table to stdout + table.printstd(); + } } Ok(()) From f0d90130addfb6cbafa47eff9a7a5815b0e007a2 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Fri, 6 Nov 2020 11:10:22 +1100 Subject: [PATCH 15/22] Add recovery command --- swap/src/cli.rs | 11 +++++++++++ swap/src/main.rs | 21 +++++++++++++++++---- swap/src/recover.rs | 45 ++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 68 insertions(+), 9 deletions(-) diff --git a/swap/src/cli.rs b/swap/src/cli.rs index 4627afab..15d1355e 100644 --- a/swap/src/cli.rs +++ b/swap/src/cli.rs @@ -1,5 +1,6 @@ use libp2p::core::Multiaddr; use url::Url; +use uuid::Uuid; #[derive(structopt::StructOpt, Debug)] #[structopt(name = "xmr-btc-swap", about = "Trustless XMR BTC swaps")] @@ -34,4 +35,14 @@ pub enum Options { tor: bool, }, History, + Recover { + #[structopt(required = true)] + swap_id: Uuid, + + #[structopt(default_value = "http://127.0.0.1:8332", long = "bitcoind")] + bitcoind_url: Url, + + #[structopt(default_value = "http://127.0.0.1:18083", long = "monerod")] + monerod_url: Url, + }, } diff --git a/swap/src/main.rs b/swap/src/main.rs index 77b6a11f..afdf110c 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -20,12 +20,12 @@ use prettytable::{row, Table}; use std::{io, io::Write, process, sync::Arc}; use structopt::StructOpt; use swap::{ - alice, - alice::Alice, - bitcoin, bob, - bob::Bob, + alice::{self, Alice}, + bitcoin, + bob::{self, Bob}, monero, network::transport::{build, build_tor, SwapTransport}, + recover::recover, Cmd, Rsp, SwapAmounts, }; use tracing::info; @@ -145,6 +145,19 @@ async fn main() -> Result<()> { // Print the table to stdout table.printstd(); } + Options::Recover { + swap_id, + bitcoind_url, + monerod_url, + } => { + let state = db.get_state(swap_id)?; + let bitcoin_wallet = bitcoin::Wallet::new("bob", bitcoind_url) + .await + .expect("failed to create bitcoin wallet"); + let monero_wallet = monero::Wallet::new(monerod_url); + + recover(bitcoin_wallet, monero_wallet, state).await?; + } } Ok(()) diff --git a/swap/src/recover.rs b/swap/src/recover.rs index 2abf1334..a540ba35 100644 --- a/swap/src/recover.rs +++ b/swap/src/recover.rs @@ -9,6 +9,7 @@ use futures::{ pin_mut, }; use sha2::Sha256; +use tracing::info; use xmr_btc::bitcoin::{ poll_until_block_height_is_gte, BroadcastSignedTransaction, TransactionBlockHeight, TxCancel, TxPunish, TxRedeem, TxRefund, WatchForRawTransaction, @@ -31,8 +32,12 @@ pub async fn alice_recover( state: Alice, ) -> Result<()> { match state { - Alice::Handshaken(_) | Alice::BtcLocked(_) | Alice::SwapComplete => {} + Alice::Handshaken(_) | Alice::BtcLocked(_) | Alice::SwapComplete => { + info!("Nothing to do"); + } Alice::XmrLocked(state) => { + info!("Monero still locked up"); + let tx_cancel = TxCancel::new( &state.tx_lock, state.refund_timelock, @@ -40,13 +45,15 @@ pub async fn alice_recover( state.B.clone(), ); - // Ensure that TxCancel is on the blockchain + info!("Checking if the Bitcoin cancel transaction has been published"); if bitcoin_wallet .0 .get_raw_transaction(tx_cancel.txid()) .await .is_err() { + info!("Bitcoin cancel transaction not yet published"); + let tx_lock_height = bitcoin_wallet .transaction_block_height(state.tx_lock.txid()) .await; @@ -72,6 +79,7 @@ pub async fn alice_recover( bitcoin_wallet .broadcast_signed_transaction(tx_cancel) .await?; + info!("Successfully published Bitcoin cancel transaction"); } let tx_cancel_height = bitcoin_wallet @@ -85,6 +93,7 @@ pub async fn alice_recover( let tx_refund = TxRefund::new(&tx_cancel, &state.refund_address); + info!("Waiting for either Bitcoin refund or punish timelock"); match select( bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid()), poll_until_bob_can_be_punished, @@ -92,6 +101,8 @@ pub async fn alice_recover( .await { Either::Left((tx_refund_published, ..)) => { + info!("Found Bitcoin refund transaction"); + let tx_refund_sig = tx_refund .extract_signature_by_key(tx_refund_published, state.a.public())?; let tx_refund_encsig = state @@ -114,13 +125,16 @@ pub async fn alice_recover( monero_wallet .create_and_load_wallet_for_output(s_a + s_b, state.v) .await?; + info!("Successfully refunded monero"); } Either::Right(_) => { + info!("Punish timelock reached, attempting to punish Bob"); + let tx_punish = TxPunish::new(&tx_cancel, &state.punish_address, state.punish_timelock); let sig_a = state.a.sign(tx_punish.digest()); - let sig_b = state.tx_cancel_sig_bob.clone(); + let sig_b = state.tx_punish_sig_bob.clone(); let sig_tx_punish = tx_punish.add_signatures( &tx_cancel, @@ -131,6 +145,7 @@ pub async fn alice_recover( bitcoin_wallet .broadcast_signed_transaction(sig_tx_punish) .await?; + info!("Successfully punished Bob's inactivity by taking bitcoin"); } }; } @@ -212,11 +227,14 @@ pub async fn alice_recover( bitcoin_wallet .broadcast_signed_transaction(sig_tx_punish) .await?; + info!("Successfully redeemed bitcoin"); } }; } } Alice::BtcPunishable(state) => { + info!("Punish timelock reached, attempting to punish Bob"); + let tx_cancel = TxCancel::new( &state.tx_lock, state.refund_timelock, @@ -238,15 +256,19 @@ pub async fn alice_recover( bitcoin_wallet .broadcast_signed_transaction(sig_tx_punish) .await?; + info!("Successfully punished Bob's inactivity by taking bitcoin"); } Alice::BtcRefunded { view_key, spend_key, .. } => { + info!("Bitcoin was refunded, attempting to refund monero"); + monero_wallet .create_and_load_wallet_for_output(spend_key, view_key) .await?; + info!("Successfully refunded monero"); } }; @@ -259,8 +281,12 @@ pub async fn bob_recover( state: Bob, ) -> Result<()> { match state { - Bob::Handshaken(_) | Bob::SwapComplete => {} + Bob::Handshaken(_) | Bob::SwapComplete => { + info!("Nothing to do"); + } Bob::BtcLocked(state) | Bob::XmrLocked(state) | Bob::BtcRefundable(state) => { + info!("Bitcoin may still be locked up, attempting to refund"); + let tx_cancel = TxCancel::new( &state.tx_lock, state.refund_timelock, @@ -268,13 +294,15 @@ pub async fn bob_recover( state.b.public(), ); - // Ensure that TxCancel is on the blockchain + info!("Checking if the Bitcoin cancel transaction has been published"); if bitcoin_wallet .0 .get_raw_transaction(tx_cancel.txid()) .await .is_err() { + info!("Bitcoin cancel transaction not yet published"); + let tx_lock_height = bitcoin_wallet .transaction_block_height(state.tx_lock.txid()) .await; @@ -300,6 +328,7 @@ pub async fn bob_recover( bitcoin_wallet .broadcast_signed_transaction(tx_cancel) .await?; + info!("Successfully published Bitcoin cancel transaction"); } let tx_refund = TxRefund::new(&tx_cancel, &state.refund_address); @@ -318,11 +347,16 @@ pub async fn bob_recover( .expect("sig_{a,b} to be valid signatures for tx_refund") }; + // TODO: Check if Bitcoin has already been punished and provide a useful error + // message/log to the user if so bitcoin_wallet .broadcast_signed_transaction(signed_tx_refund) .await?; + info!("Successfully refunded bitcoin"); } Bob::BtcRedeemed(state) => { + info!("Bitcoin was redeemed, attempting to redeem monero"); + let tx_redeem = TxRedeem::new(&state.tx_lock, &state.redeem_address); let tx_redeem_published = bitcoin_wallet .0 @@ -348,6 +382,7 @@ pub async fn bob_recover( monero_wallet .create_and_load_wallet_for_output(s_a + s_b, state.v) .await?; + info!("Successfully redeemed monero") } }; From 11a7963081877347c17acf1925402f1f056f8105 Mon Sep 17 00:00:00 2001 From: rishflab Date: Fri, 6 Nov 2020 11:31:08 +1100 Subject: [PATCH 16/22] Fix bugs --- swap/src/recover.rs | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/swap/src/recover.rs b/swap/src/recover.rs index a540ba35..3d06b6bd 100644 --- a/swap/src/recover.rs +++ b/swap/src/recover.rs @@ -158,7 +158,7 @@ pub async fn alice_recover( let refund_absolute_expiry = tx_lock_height + state.refund_timelock; // bob cannot cancel - if block_height > refund_absolute_expiry { + if block_height < refund_absolute_expiry { bitcoin_wallet .broadcast_signed_transaction(redeem_tx) .await?; @@ -170,6 +170,40 @@ pub async fn alice_recover( state.B.clone(), ); + // Ensure that TxCancel is on the blockchain + if bitcoin_wallet + .0 + .get_raw_transaction(tx_cancel.txid()) + .await + .is_err() + { + let tx_lock_height = bitcoin_wallet + .transaction_block_height(state.tx_lock.txid()) + .await; + poll_until_block_height_is_gte( + &bitcoin_wallet, + tx_lock_height + state.refund_timelock, + ) + .await; + + let sig_a = state.a.sign(tx_cancel.digest()); + let sig_b = state.tx_cancel_sig_bob.clone(); + + let tx_cancel = tx_cancel + .clone() + .add_signatures( + &state.tx_lock, + (state.a.public(), sig_a), + (state.B.clone(), sig_b), + ) + .expect("sig_{a,b} to be valid signatures for tx_cancel"); + + // TODO: We should not fail if the transaction is already on the blockchain + bitcoin_wallet + .broadcast_signed_transaction(tx_cancel) + .await?; + } + let tx_cancel_height = bitcoin_wallet .transaction_block_height(tx_cancel.txid()) .await; @@ -245,7 +279,7 @@ pub async fn alice_recover( let tx_punish = TxPunish::new(&tx_cancel, &state.punish_address, state.punish_timelock); let sig_a = state.a.sign(tx_punish.digest()); - let sig_b = state.tx_cancel_sig_bob.clone(); + let sig_b = state.tx_punish_sig_bob.clone(); let sig_tx_punish = tx_punish.add_signatures( &tx_cancel, From 5dede0f3617ed18c06c400b76f925e7eab622513 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Fri, 6 Nov 2020 13:17:33 +1100 Subject: [PATCH 17/22] Add more logging to recovery Also, remove redundant timelock check. --- swap/src/recover.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/swap/src/recover.rs b/swap/src/recover.rs index 3d06b6bd..37722463 100644 --- a/swap/src/recover.rs +++ b/swap/src/recover.rs @@ -150,6 +150,8 @@ pub async fn alice_recover( }; } Alice::BtcRedeemable { redeem_tx, state } => { + info!("Have the means to redeem the Bitcoin"); + let tx_lock_height = bitcoin_wallet .transaction_block_height(state.tx_lock.txid()) .await; @@ -157,12 +159,17 @@ pub async fn alice_recover( let block_height = bitcoin_wallet.0.block_height().await?; let refund_absolute_expiry = tx_lock_height + state.refund_timelock; - // bob cannot cancel + info!("Checking refund timelock"); if block_height < refund_absolute_expiry { + info!("Safe to redeem"); + bitcoin_wallet .broadcast_signed_transaction(redeem_tx) .await?; + info!("Successfully redeemed bitcoin"); } else { + info!("Refund timelock reached"); + let tx_cancel = TxCancel::new( &state.tx_lock, state.refund_timelock, @@ -170,22 +177,13 @@ pub async fn alice_recover( state.B.clone(), ); - // Ensure that TxCancel is on the blockchain + info!("Checking if the Bitcoin cancel transaction has been published"); if bitcoin_wallet .0 .get_raw_transaction(tx_cancel.txid()) .await .is_err() { - let tx_lock_height = bitcoin_wallet - .transaction_block_height(state.tx_lock.txid()) - .await; - poll_until_block_height_is_gte( - &bitcoin_wallet, - tx_lock_height + state.refund_timelock, - ) - .await; - let sig_a = state.a.sign(tx_cancel.digest()); let sig_b = state.tx_cancel_sig_bob.clone(); @@ -202,6 +200,7 @@ pub async fn alice_recover( bitcoin_wallet .broadcast_signed_transaction(tx_cancel) .await?; + info!("Successfully published Bitcoin cancel transaction"); } let tx_cancel_height = bitcoin_wallet @@ -215,6 +214,7 @@ pub async fn alice_recover( let tx_refund = TxRefund::new(&tx_cancel, &state.refund_address); + info!("Waiting for either Bitcoin refund or punish timelock"); match select( bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid()), poll_until_bob_can_be_punished, @@ -222,6 +222,8 @@ pub async fn alice_recover( .await { Either::Left((tx_refund_published, ..)) => { + info!("Found Bitcoin refund transaction"); + let tx_refund_sig = tx_refund .extract_signature_by_key(tx_refund_published, state.a.public())?; let tx_refund_encsig = state @@ -244,8 +246,11 @@ pub async fn alice_recover( monero_wallet .create_and_load_wallet_for_output(s_a + s_b, state.v) .await?; + info!("Successfully refunded monero"); } Either::Right(_) => { + info!("Punish timelock reached, attempting to punish Bob"); + let tx_punish = TxPunish::new(&tx_cancel, &state.punish_address, state.punish_timelock); @@ -261,7 +266,7 @@ pub async fn alice_recover( bitcoin_wallet .broadcast_signed_transaction(sig_tx_punish) .await?; - info!("Successfully redeemed bitcoin"); + info!("Successfully punished Bob's inactivity by taking bitcoin"); } }; } From 33b36c98fa67366d3bc012b7a5fce9d70a2f5654 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Fri, 6 Nov 2020 15:39:42 +1100 Subject: [PATCH 18/22] Configure binary tracing level for xmr_btc and monero_harness --- swap/src/trace.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/swap/src/trace.rs b/swap/src/trace.rs index 14854cd4..c8f82e89 100644 --- a/swap/src/trace.rs +++ b/swap/src/trace.rs @@ -14,7 +14,10 @@ pub fn init_tracing(level: log::LevelFilter) -> anyhow::Result<()> { let is_terminal = atty::is(Stream::Stdout); let subscriber = FmtSubscriber::builder() - .with_env_filter(format!("swap={}", level)) + .with_env_filter(format!( + "swap={},xmr_btc={},monero_harness={}", + level, level, level + )) .with_ansi(is_terminal) .finish(); From 5930af35875223de7665bfdf363a9d1deef80c22 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Fri, 6 Nov 2020 15:40:56 +1100 Subject: [PATCH 19/22] Move some things around in recover module --- swap/src/recover.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/swap/src/recover.rs b/swap/src/recover.rs index 37722463..21f34355 100644 --- a/swap/src/recover.rs +++ b/swap/src/recover.rs @@ -79,9 +79,10 @@ pub async fn alice_recover( bitcoin_wallet .broadcast_signed_transaction(tx_cancel) .await?; - info!("Successfully published Bitcoin cancel transaction"); } + info!("Confirmed that Bitcoin cancel transaction is on the blockchain"); + let tx_cancel_height = bitcoin_wallet .transaction_block_height(tx_cancel.txid()) .await; @@ -103,6 +104,10 @@ pub async fn alice_recover( Either::Left((tx_refund_published, ..)) => { info!("Found Bitcoin refund transaction"); + let s_a = monero::PrivateKey { + scalar: state.s_a.into_ed25519(), + }; + let tx_refund_sig = tx_refund .extract_signature_by_key(tx_refund_published, state.a.public())?; let tx_refund_encsig = state @@ -118,10 +123,6 @@ pub async fn alice_recover( xmr_btc::monero::Scalar::from_bytes_mod_order(s_b.to_bytes()), ); - let s_a = monero::PrivateKey { - scalar: state.s_a.into_ed25519(), - }; - monero_wallet .create_and_load_wallet_for_output(s_a + s_b, state.v) .await?; @@ -200,9 +201,10 @@ pub async fn alice_recover( bitcoin_wallet .broadcast_signed_transaction(tx_cancel) .await?; - info!("Successfully published Bitcoin cancel transaction"); } + info!("Confirmed that Bitcoin cancel transaction is on the blockchain"); + let tx_cancel_height = bitcoin_wallet .transaction_block_height(tx_cancel.txid()) .await; @@ -224,6 +226,10 @@ pub async fn alice_recover( Either::Left((tx_refund_published, ..)) => { info!("Found Bitcoin refund transaction"); + let s_a = monero::PrivateKey { + scalar: state.s_a.into_ed25519(), + }; + let tx_refund_sig = tx_refund .extract_signature_by_key(tx_refund_published, state.a.public())?; let tx_refund_encsig = state @@ -239,10 +245,6 @@ pub async fn alice_recover( xmr_btc::monero::Scalar::from_bytes_mod_order(s_b.to_bytes()), ); - let s_a = monero::PrivateKey { - scalar: state.s_a.into_ed25519(), - }; - monero_wallet .create_and_load_wallet_for_output(s_a + s_b, state.v) .await?; From bea99185ed7f70788c2038f13589202b40e02934 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Fri, 6 Nov 2020 15:41:55 +1100 Subject: [PATCH 20/22] Use correct default values for monerod_url* *`monerod_url` is an incorrect name as it's actually a `monero_wallet_rpc_url`. This is fixed in another branch. --- swap/src/cli.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/swap/src/cli.rs b/swap/src/cli.rs index 15d1355e..2d13fcb8 100644 --- a/swap/src/cli.rs +++ b/swap/src/cli.rs @@ -9,7 +9,7 @@ pub enum Options { #[structopt(default_value = "http://127.0.0.1:8332", long = "bitcoind")] bitcoind_url: Url, - #[structopt(default_value = "http://127.0.0.1:18083", long = "monerod")] + #[structopt(default_value = "http://127.0.0.1:18083/json_rpc", long = "monerod")] monerod_url: Url, #[structopt(default_value = "/ip4/127.0.0.1/tcp/9876", long = "listen-addr")] @@ -28,7 +28,7 @@ pub enum Options { #[structopt(default_value = "http://127.0.0.1:8332", long = "bitcoind")] bitcoind_url: Url, - #[structopt(default_value = "http://127.0.0.1:18083", long = "monerod")] + #[structopt(default_value = "http://127.0.0.1:18083/json_rpc", long = "monerod")] monerod_url: Url, #[structopt(long = "tor")] @@ -42,7 +42,7 @@ pub enum Options { #[structopt(default_value = "http://127.0.0.1:8332", long = "bitcoind")] bitcoind_url: Url, - #[structopt(default_value = "http://127.0.0.1:18083", long = "monerod")] + #[structopt(default_value = "http://127.0.0.1:18083/json_rpc", long = "monerod")] monerod_url: Url, }, } From f5ff50157e3eb950ba98501ca7ed24703b5eebce Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Fri, 6 Nov 2020 18:05:49 +1100 Subject: [PATCH 21/22] Some more fixes and comments after testing Alice's recovery --- swap/src/bob.rs | 8 +- swap/src/recover.rs | 77 ++++++++++++--- swap/tests/e2e.rs | 235 ++++++++++++++++++++++---------------------- 3 files changed, 189 insertions(+), 131 deletions(-) diff --git a/swap/src/bob.rs b/swap/src/bob.rs index 39a380b5..3b5c5936 100644 --- a/swap/src/bob.rs +++ b/swap/src/bob.rs @@ -167,6 +167,9 @@ pub async fn swap( info!("Resumed execution of generator, got: {:?}", state); + // TODO: Protect against transient errors + // TODO: Ignore transaction-already-in-block-chain errors + match state { GeneratorState::Yielded(bob::Action::LockBtc(tx_lock)) => { let signed_tx_lock = bitcoin_wallet.sign_tx_lock(tx_lock).await?; @@ -184,8 +187,9 @@ pub async fn swap( guard.0.send_message3(alice.clone(), tx_redeem_encsig); info!("Sent Bitcoin redeem encsig"); - // TODO: Does Bob need to wait for Alice to send an empty response, or can we - // just continue? + // FIXME: Having to wait for Alice's response here is a big problem, because + // we're stuck if she doesn't send her response back. I believe this is + // currently necessary, so we may have to rework this and/or how we use libp2p match guard.0.next().shared().await { OutEvent::Message3 => { debug!("Got Message3 empty response"); diff --git a/swap/src/recover.rs b/swap/src/recover.rs index 21f34355..4cbfe54c 100644 --- a/swap/src/recover.rs +++ b/swap/src/recover.rs @@ -1,3 +1,15 @@ +//! This module is used to attempt to recover an unfinished swap. +//! +//! Recovery is only supported for certain states and the strategy followed is +//! to perform the simplest steps that require no further action from the +//! counterparty. +//! +//! The quality of this module is bad because there is a lot of code +//! duplication, both within the module and with respect to +//! `xmr_btc/src/{alice,bob}.rs`. In my opinion, a better approach to support +//! swap recovery would be through the `action_generator`s themselves, but this +//! was deemed too complicated for the time being. + use crate::{ monero::CreateWalletForOutput, state::{Alice, Bob, Swap}, @@ -257,7 +269,7 @@ pub async fn alice_recover( TxPunish::new(&tx_cancel, &state.punish_address, state.punish_timelock); let sig_a = state.a.sign(tx_punish.digest()); - let sig_b = state.tx_cancel_sig_bob.clone(); + let sig_b = state.tx_punish_sig_bob.clone(); let sig_tx_punish = tx_punish.add_signatures( &tx_cancel, @@ -282,22 +294,61 @@ pub async fn alice_recover( state.a.public(), state.B.clone(), ); + let tx_refund = TxRefund::new(&tx_cancel, &state.refund_address); - let tx_punish = TxPunish::new(&tx_cancel, &state.punish_address, state.punish_timelock); + info!("Checking if Bitcoin has already been refunded"); - let sig_a = state.a.sign(tx_punish.digest()); - let sig_b = state.tx_punish_sig_bob.clone(); + // TODO: Protect against transient errors so that we can correctly decide if the + // bitcoin has been refunded + match bitcoin_wallet.0.get_raw_transaction(tx_refund.txid()).await { + Ok(tx_refund_published) => { + info!("Bitcoin already refunded"); - let sig_tx_punish = tx_punish.add_signatures( - &tx_cancel, - (state.a.public(), sig_a), - (state.B.clone(), sig_b), - )?; + let s_a = monero::PrivateKey { + scalar: state.s_a.into_ed25519(), + }; - bitcoin_wallet - .broadcast_signed_transaction(sig_tx_punish) - .await?; - info!("Successfully punished Bob's inactivity by taking bitcoin"); + let tx_refund_sig = tx_refund + .extract_signature_by_key(tx_refund_published, state.a.public())?; + let tx_refund_encsig = state + .a + .encsign(state.S_b_bitcoin.clone(), tx_refund.digest()); + + let s_b = xmr_btc::bitcoin::recover( + state.S_b_bitcoin, + tx_refund_sig, + tx_refund_encsig, + )?; + let s_b = monero::PrivateKey::from_scalar( + xmr_btc::monero::Scalar::from_bytes_mod_order(s_b.to_bytes()), + ); + + monero_wallet + .create_and_load_wallet_for_output(s_a + s_b, state.v) + .await?; + info!("Successfully refunded monero"); + } + Err(_) => { + info!("Bitcoin not yet refunded"); + + let tx_punish = + TxPunish::new(&tx_cancel, &state.punish_address, state.punish_timelock); + + let sig_a = state.a.sign(tx_punish.digest()); + let sig_b = state.tx_punish_sig_bob.clone(); + + let sig_tx_punish = tx_punish.add_signatures( + &tx_cancel, + (state.a.public(), sig_a), + (state.B.clone(), sig_b), + )?; + + bitcoin_wallet + .broadcast_signed_transaction(sig_tx_punish) + .await?; + info!("Successfully punished Bob's inactivity by taking bitcoin"); + } + } } Alice::BtcRefunded { view_key, diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index bebb052b..d17ca0db 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -1,120 +1,123 @@ -#[cfg(not(feature = "tor"))] -mod e2e_test { - use bitcoin_harness::Bitcoind; - use futures::{channel::mpsc, future::try_join}; - use libp2p::Multiaddr; - use monero_harness::Monero; - use std::sync::Arc; - use swap::{alice, bob, network::transport::build, storage::Database}; - use tempfile::tempdir; - use testcontainers::clients::Cli; - - // NOTE: For some reason running these tests overflows the stack. In order to - // mitigate this run them with: - // - // RUST_MIN_STACK=100000000 cargo test - - #[tokio::test] - async fn swap() { - let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" - .parse() - .expect("failed to parse Alice's address"); - - let cli = Cli::default(); - let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); - let _ = bitcoind.init(5).await; - - let btc = bitcoin::Amount::from_sat(1_000_000); - let btc_alice = bitcoin::Amount::ZERO; - let btc_bob = btc * 10; - - // this xmr value matches the logic of alice::calculate_amounts i.e. btc * - // 10_000 * 100 - let xmr = 1_000_000_000_000; - let xmr_alice = xmr * 10; - let xmr_bob = 0; - - let alice_btc_wallet = Arc::new( - swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone()) - .await - .unwrap(), - ); - let bob_btc_wallet = Arc::new( - swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone()) - .await - .unwrap(), - ); - bitcoind - .mint(bob_btc_wallet.0.new_address().await.unwrap(), btc_bob) +use bitcoin_harness::Bitcoind; +use futures::{channel::mpsc, future::try_join}; +use libp2p::Multiaddr; +use monero_harness::Monero; +use std::sync::Arc; +use swap::{alice, bob, network::transport::build, storage::Database}; +use tempfile::tempdir; +use testcontainers::clients::Cli; + +// NOTE: For some reason running these tests overflows the stack. In order to +// mitigate this run them with: +// +// RUST_MIN_STACK=100000000 cargo test + +#[tokio::test] +async fn swap() { + use tracing_subscriber::util::SubscriberInitExt as _; + let _guard = tracing_subscriber::fmt() + .with_env_filter("swap=info,xmr_btc=info") + .with_ansi(false) + .set_default(); + + let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" + .parse() + .expect("failed to parse Alice's address"); + + let cli = Cli::default(); + let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); + dbg!(&bitcoind.node_url); + let _ = bitcoind.init(5).await; + + let btc = bitcoin::Amount::from_sat(1_000_000); + let btc_alice = bitcoin::Amount::ZERO; + let btc_bob = btc * 10; + + // this xmr value matches the logic of alice::calculate_amounts i.e. btc * + // 10_000 * 100 + let xmr = 1_000_000_000_000; + let xmr_alice = xmr * 10; + let xmr_bob = 0; + + let alice_btc_wallet = Arc::new( + swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone()) .await - .unwrap(); - - let (monero, _container) = - Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()]) - .await - .unwrap(); - monero - .init(vec![("alice", xmr_alice), ("bob", xmr_bob)]) + .unwrap(), + ); + let bob_btc_wallet = Arc::new( + swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone()) + .await + .unwrap(), + ); + bitcoind + .mint(bob_btc_wallet.0.new_address().await.unwrap(), btc_bob) + .await + .unwrap(); + + let (monero, _container) = + Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()]) .await .unwrap(); - - let alice_xmr_wallet = Arc::new(swap::monero::Wallet( - monero.wallet("alice").unwrap().client(), - )); - let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet("bob").unwrap().client())); - - let alice_behaviour = alice::Alice::default(); - let alice_transport = build(alice_behaviour.identity()).unwrap(); - - let db_dir = tempdir().unwrap(); - let db = Database::open(db_dir.path()).unwrap(); - let alice_swap = alice::swap( - alice_btc_wallet.clone(), - alice_xmr_wallet.clone(), - db, - alice_multiaddr.clone(), - alice_transport, - alice_behaviour, - ); - - let db_dir = tempdir().unwrap(); - let db = Database::open(db_dir.path()).unwrap(); - let (cmd_tx, mut _cmd_rx) = mpsc::channel(1); - let (mut rsp_tx, rsp_rx) = mpsc::channel(1); - let bob_behaviour = bob::Bob::default(); - let bob_transport = build(bob_behaviour.identity()).unwrap(); - let bob_swap = bob::swap( - bob_btc_wallet.clone(), - bob_xmr_wallet.clone(), - db, - btc.as_sat(), - alice_multiaddr, - cmd_tx, - rsp_rx, - bob_transport, - bob_behaviour, - ); - - // automate the verification step by accepting any amounts sent over by Alice - rsp_tx.try_send(swap::Rsp::VerifiedAmounts).unwrap(); - - try_join(alice_swap, bob_swap).await.unwrap(); - - let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap(); - let btc_bob_final = bob_btc_wallet.as_ref().balance().await.unwrap(); - - let xmr_alice_final = alice_xmr_wallet.as_ref().get_balance().await.unwrap(); - - bob_xmr_wallet.as_ref().0.refresh().await.unwrap(); - let xmr_bob_final = bob_xmr_wallet.as_ref().get_balance().await.unwrap(); - - assert_eq!( - btc_alice_final, - btc_alice + btc - bitcoin::Amount::from_sat(xmr_btc::bitcoin::TX_FEE) - ); - assert!(btc_bob_final <= btc_bob - btc); - - assert!(xmr_alice_final.as_piconero() <= xmr_alice - xmr); - assert_eq!(xmr_bob_final.as_piconero(), xmr_bob + xmr); - } + monero + .init(vec![("alice", xmr_alice), ("bob", xmr_bob)]) + .await + .unwrap(); + + let alice_xmr_wallet = Arc::new(swap::monero::Wallet( + monero.wallet("alice").unwrap().client(), + )); + let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet("bob").unwrap().client())); + + let alice_behaviour = alice::Alice::default(); + let alice_transport = build(alice_behaviour.identity()).unwrap(); + + let db = Database::open(std::path::Path::new("../.swap-db/")).unwrap(); + let alice_swap = alice::swap( + alice_btc_wallet.clone(), + alice_xmr_wallet.clone(), + db, + alice_multiaddr.clone(), + alice_transport, + alice_behaviour, + ); + + let db_dir = tempdir().unwrap(); + let db = Database::open(db_dir.path()).unwrap(); + let (cmd_tx, mut _cmd_rx) = mpsc::channel(1); + let (mut rsp_tx, rsp_rx) = mpsc::channel(1); + let bob_behaviour = bob::Bob::default(); + let bob_transport = build(bob_behaviour.identity()).unwrap(); + let bob_swap = bob::swap( + bob_btc_wallet.clone(), + bob_xmr_wallet.clone(), + db, + btc.as_sat(), + alice_multiaddr, + cmd_tx, + rsp_rx, + bob_transport, + bob_behaviour, + ); + + // automate the verification step by accepting any amounts sent over by Alice + rsp_tx.try_send(swap::Rsp::VerifiedAmounts).unwrap(); + + try_join(alice_swap, bob_swap).await.unwrap(); + + let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap(); + let btc_bob_final = bob_btc_wallet.as_ref().balance().await.unwrap(); + + let xmr_alice_final = alice_xmr_wallet.as_ref().get_balance().await.unwrap(); + + bob_xmr_wallet.as_ref().0.refresh().await.unwrap(); + let xmr_bob_final = bob_xmr_wallet.as_ref().get_balance().await.unwrap(); + + assert_eq!( + btc_alice_final, + btc_alice + btc - bitcoin::Amount::from_sat(xmr_btc::bitcoin::TX_FEE) + ); + assert!(btc_bob_final <= btc_bob - btc); + + assert!(xmr_alice_final.as_piconero() <= xmr_alice - xmr); + assert_eq!(xmr_bob_final.as_piconero(), xmr_bob + xmr); } From 483c819e1751e6a34877969b0300c49266cb82a8 Mon Sep 17 00:00:00 2001 From: Lucas Soriano del Pino Date: Sat, 7 Nov 2020 06:57:35 +1100 Subject: [PATCH 22/22] Correctly re-export types from bitcoin and monero modules --- swap/src/alice.rs | 2 +- swap/src/bitcoin.rs | 8 +-- swap/src/lib.rs | 4 +- swap/src/monero.rs | 8 +-- swap/src/recover.rs | 89 +++++++++++++------------- swap/tests/e2e.rs | 3 +- xmr-btc/src/bitcoin.rs | 11 +--- xmr-btc/src/monero.rs | 5 +- xmr-btc/tests/harness/wallet/monero.rs | 5 +- xmr-btc/tests/on_chain.rs | 9 ++- 10 files changed, 67 insertions(+), 77 deletions(-) diff --git a/swap/src/alice.rs b/swap/src/alice.rs index b419f5a9..52ae7cc4 100644 --- a/swap/src/alice.rs +++ b/swap/src/alice.rs @@ -75,7 +75,7 @@ pub async fn swap( // to `ConstantBackoff`. #[async_trait] impl ReceiveBitcoinRedeemEncsig for Network { - async fn receive_bitcoin_redeem_encsig(&mut self) -> xmr_btc::bitcoin::EncryptedSignature { + async fn receive_bitcoin_redeem_encsig(&mut self) -> bitcoin::EncryptedSignature { #[derive(Debug)] struct UnexpectedMessage; diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index 2d88ae2c..15c1e76b 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -8,13 +8,13 @@ use bitcoin_harness::bitcoind_rpc::PsbtBase64; use reqwest::Url; use tokio::time; use xmr_btc::bitcoin::{ - Amount, BlockHeight, BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, - TransactionBlockHeight, TxLock, Txid, WatchForRawTransaction, + BlockHeight, BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, TransactionBlockHeight, + WatchForRawTransaction, }; -pub const TX_LOCK_MINE_TIMEOUT: u64 = 3600; +pub use xmr_btc::bitcoin::*; -// This is cut'n'paste from xmr_btc/tests/harness/wallet/bitcoin.rs +pub const TX_LOCK_MINE_TIMEOUT: u64 = 3600; #[derive(Debug)] pub struct Wallet(pub bitcoin_harness::Wallet); diff --git a/swap/src/lib.rs b/swap/src/lib.rs index 9039aa74..cdc8673f 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -34,10 +34,10 @@ pub enum Rsp { pub struct SwapAmounts { /// Amount of BTC to swap. #[serde(with = "::bitcoin::util::amount::serde::as_sat")] - pub btc: ::bitcoin::Amount, + pub btc: bitcoin::Amount, /// Amount of XMR to swap. #[serde(with = "xmr_btc::serde::monero_amount")] - pub xmr: xmr_btc::monero::Amount, + pub xmr: monero::Amount, } // TODO: Display in XMR and BTC (not picos and sats). diff --git a/swap/src/monero.rs b/swap/src/monero.rs index eb9f9bbc..7d252b69 100644 --- a/swap/src/monero.rs +++ b/swap/src/monero.rs @@ -1,15 +1,11 @@ use anyhow::Result; use async_trait::async_trait; use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _}; -use monero::{Address, Network, PrivateKey}; use monero_harness::rpc::wallet; use std::{str::FromStr, time::Duration}; - use url::Url; -pub use xmr_btc::monero::{ - Amount, CreateWalletForOutput, InsufficientFunds, PrivateViewKey, PublicKey, PublicViewKey, - Transfer, TransferProof, TxHash, WatchForTransfer, *, -}; + +pub use xmr_btc::monero::*; pub struct Wallet(pub wallet::Client); diff --git a/swap/src/recover.rs b/swap/src/recover.rs index 4cbfe54c..b71b31fd 100644 --- a/swap/src/recover.rs +++ b/swap/src/recover.rs @@ -11,6 +11,7 @@ //! was deemed too complicated for the time being. use crate::{ + bitcoin, monero, monero::CreateWalletForOutput, state::{Alice, Bob, Swap}, }; @@ -23,13 +24,13 @@ use futures::{ use sha2::Sha256; use tracing::info; use xmr_btc::bitcoin::{ - poll_until_block_height_is_gte, BroadcastSignedTransaction, TransactionBlockHeight, TxCancel, - TxPunish, TxRedeem, TxRefund, WatchForRawTransaction, + poll_until_block_height_is_gte, BroadcastSignedTransaction, TransactionBlockHeight, + WatchForRawTransaction, }; pub async fn recover( - bitcoin_wallet: crate::bitcoin::Wallet, - monero_wallet: crate::monero::Wallet, + bitcoin_wallet: bitcoin::Wallet, + monero_wallet: monero::Wallet, state: Swap, ) -> Result<()> { match state { @@ -39,8 +40,8 @@ pub async fn recover( } pub async fn alice_recover( - bitcoin_wallet: crate::bitcoin::Wallet, - monero_wallet: crate::monero::Wallet, + bitcoin_wallet: bitcoin::Wallet, + monero_wallet: monero::Wallet, state: Alice, ) -> Result<()> { match state { @@ -50,7 +51,7 @@ pub async fn alice_recover( Alice::XmrLocked(state) => { info!("Monero still locked up"); - let tx_cancel = TxCancel::new( + let tx_cancel = bitcoin::TxCancel::new( &state.tx_lock, state.refund_timelock, state.a.public(), @@ -104,7 +105,7 @@ pub async fn alice_recover( ); pin_mut!(poll_until_bob_can_be_punished); - let tx_refund = TxRefund::new(&tx_cancel, &state.refund_address); + let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state.refund_address); info!("Waiting for either Bitcoin refund or punish timelock"); match select( @@ -126,13 +127,9 @@ pub async fn alice_recover( .a .encsign(state.S_b_bitcoin.clone(), tx_refund.digest()); - let s_b = xmr_btc::bitcoin::recover( - state.S_b_bitcoin, - tx_refund_sig, - tx_refund_encsig, - )?; + let s_b = bitcoin::recover(state.S_b_bitcoin, tx_refund_sig, tx_refund_encsig)?; let s_b = monero::PrivateKey::from_scalar( - xmr_btc::monero::Scalar::from_bytes_mod_order(s_b.to_bytes()), + monero::Scalar::from_bytes_mod_order(s_b.to_bytes()), ); monero_wallet @@ -143,8 +140,11 @@ pub async fn alice_recover( Either::Right(_) => { info!("Punish timelock reached, attempting to punish Bob"); - let tx_punish = - TxPunish::new(&tx_cancel, &state.punish_address, state.punish_timelock); + let tx_punish = bitcoin::TxPunish::new( + &tx_cancel, + &state.punish_address, + state.punish_timelock, + ); let sig_a = state.a.sign(tx_punish.digest()); let sig_b = state.tx_punish_sig_bob.clone(); @@ -183,7 +183,7 @@ pub async fn alice_recover( } else { info!("Refund timelock reached"); - let tx_cancel = TxCancel::new( + let tx_cancel = bitcoin::TxCancel::new( &state.tx_lock, state.refund_timelock, state.a.public(), @@ -226,7 +226,7 @@ pub async fn alice_recover( ); pin_mut!(poll_until_bob_can_be_punished); - let tx_refund = TxRefund::new(&tx_cancel, &state.refund_address); + let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state.refund_address); info!("Waiting for either Bitcoin refund or punish timelock"); match select( @@ -248,13 +248,10 @@ pub async fn alice_recover( .a .encsign(state.S_b_bitcoin.clone(), tx_refund.digest()); - let s_b = xmr_btc::bitcoin::recover( - state.S_b_bitcoin, - tx_refund_sig, - tx_refund_encsig, - )?; + let s_b = + bitcoin::recover(state.S_b_bitcoin, tx_refund_sig, tx_refund_encsig)?; let s_b = monero::PrivateKey::from_scalar( - xmr_btc::monero::Scalar::from_bytes_mod_order(s_b.to_bytes()), + monero::Scalar::from_bytes_mod_order(s_b.to_bytes()), ); monero_wallet @@ -265,8 +262,11 @@ pub async fn alice_recover( Either::Right(_) => { info!("Punish timelock reached, attempting to punish Bob"); - let tx_punish = - TxPunish::new(&tx_cancel, &state.punish_address, state.punish_timelock); + let tx_punish = bitcoin::TxPunish::new( + &tx_cancel, + &state.punish_address, + state.punish_timelock, + ); let sig_a = state.a.sign(tx_punish.digest()); let sig_b = state.tx_punish_sig_bob.clone(); @@ -288,13 +288,13 @@ pub async fn alice_recover( Alice::BtcPunishable(state) => { info!("Punish timelock reached, attempting to punish Bob"); - let tx_cancel = TxCancel::new( + let tx_cancel = bitcoin::TxCancel::new( &state.tx_lock, state.refund_timelock, state.a.public(), state.B.clone(), ); - let tx_refund = TxRefund::new(&tx_cancel, &state.refund_address); + let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state.refund_address); info!("Checking if Bitcoin has already been refunded"); @@ -314,13 +314,9 @@ pub async fn alice_recover( .a .encsign(state.S_b_bitcoin.clone(), tx_refund.digest()); - let s_b = xmr_btc::bitcoin::recover( - state.S_b_bitcoin, - tx_refund_sig, - tx_refund_encsig, - )?; + let s_b = bitcoin::recover(state.S_b_bitcoin, tx_refund_sig, tx_refund_encsig)?; let s_b = monero::PrivateKey::from_scalar( - xmr_btc::monero::Scalar::from_bytes_mod_order(s_b.to_bytes()), + monero::Scalar::from_bytes_mod_order(s_b.to_bytes()), ); monero_wallet @@ -331,8 +327,11 @@ pub async fn alice_recover( Err(_) => { info!("Bitcoin not yet refunded"); - let tx_punish = - TxPunish::new(&tx_cancel, &state.punish_address, state.punish_timelock); + let tx_punish = bitcoin::TxPunish::new( + &tx_cancel, + &state.punish_address, + state.punish_timelock, + ); let sig_a = state.a.sign(tx_punish.digest()); let sig_b = state.tx_punish_sig_bob.clone(); @@ -379,7 +378,7 @@ pub async fn bob_recover( Bob::BtcLocked(state) | Bob::XmrLocked(state) | Bob::BtcRefundable(state) => { info!("Bitcoin may still be locked up, attempting to refund"); - let tx_cancel = TxCancel::new( + let tx_cancel = bitcoin::TxCancel::new( &state.tx_lock, state.refund_timelock, state.A.clone(), @@ -420,10 +419,11 @@ pub async fn bob_recover( bitcoin_wallet .broadcast_signed_transaction(tx_cancel) .await?; - info!("Successfully published Bitcoin cancel transaction"); } - let tx_refund = TxRefund::new(&tx_cancel, &state.refund_address); + info!("Confirmed that Bitcoin cancel transaction is on the blockchain"); + + let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &state.refund_address); let signed_tx_refund = { let adaptor = Adaptor::>::default(); let sig_a = adaptor @@ -449,7 +449,7 @@ pub async fn bob_recover( Bob::BtcRedeemed(state) => { info!("Bitcoin was redeemed, attempting to redeem monero"); - let tx_redeem = TxRedeem::new(&state.tx_lock, &state.redeem_address); + let tx_redeem = bitcoin::TxRedeem::new(&state.tx_lock, &state.redeem_address); let tx_redeem_published = bitcoin_wallet .0 .get_raw_transaction(tx_redeem.txid()) @@ -461,11 +461,10 @@ pub async fn bob_recover( let tx_redeem_sig = tx_redeem.extract_signature_by_key(tx_redeem_published, state.b.public())?; - let s_a = - xmr_btc::bitcoin::recover(state.S_a_bitcoin, tx_redeem_sig, tx_redeem_encsig)?; - let s_a = monero::PrivateKey::from_scalar( - xmr_btc::monero::Scalar::from_bytes_mod_order(s_a.to_bytes()), - ); + let s_a = bitcoin::recover(state.S_a_bitcoin, tx_redeem_sig, tx_redeem_encsig)?; + let s_a = monero::PrivateKey::from_scalar(monero::Scalar::from_bytes_mod_order( + s_a.to_bytes(), + )); let s_b = monero::PrivateKey { scalar: state.s_b.into_ed25519(), diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index d17ca0db..358657ca 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use swap::{alice, bob, network::transport::build, storage::Database}; use tempfile::tempdir; use testcontainers::clients::Cli; +use xmr_btc::bitcoin; // NOTE: For some reason running these tests overflows the stack. In order to // mitigate this run them with: @@ -114,7 +115,7 @@ async fn swap() { assert_eq!( btc_alice_final, - btc_alice + btc - bitcoin::Amount::from_sat(xmr_btc::bitcoin::TX_FEE) + btc_alice + btc - bitcoin::Amount::from_sat(bitcoin::TX_FEE) ); assert!(btc_bob_final <= btc_bob - btc); diff --git a/xmr-btc/src/bitcoin.rs b/xmr-btc/src/bitcoin.rs index e5e6ca6b..a095d64f 100644 --- a/xmr-btc/src/bitcoin.rs +++ b/xmr-btc/src/bitcoin.rs @@ -2,12 +2,7 @@ pub mod transactions; use anyhow::{anyhow, bail, Result}; use async_trait::async_trait; -use bitcoin::{ - hashes::{hex::ToHex, Hash}, - secp256k1, - util::psbt::PartiallySignedTransaction, - SigHash, -}; +use bitcoin::hashes::{hex::ToHex, Hash}; use ecdsa_fun::{adaptor::Adaptor, fun::Point, nonce::Deterministic, ECDSA}; use miniscript::{Descriptor, Segwitv0}; use rand::{CryptoRng, RngCore}; @@ -15,9 +10,9 @@ use serde::{Deserialize, Serialize}; use sha2::Sha256; use std::str::FromStr; -pub use crate::bitcoin::transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxRefund}; -pub use bitcoin::{Address, Amount, OutPoint, Transaction, Txid}; +pub use bitcoin::{util::psbt::PartiallySignedTransaction, *}; pub use ecdsa_fun::{adaptor::EncryptedSignature, fun::Scalar, Signature}; +pub use transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxRefund}; pub const TX_FEE: u64 = 10_000; diff --git a/xmr-btc/src/monero.rs b/xmr-btc/src/monero.rs index ca55b904..643c4d32 100644 --- a/xmr-btc/src/monero.rs +++ b/xmr-btc/src/monero.rs @@ -1,12 +1,13 @@ use crate::serde::monero_private_key; use anyhow::Result; use async_trait::async_trait; -pub use curve25519_dalek::scalar::Scalar; -pub use monero::{Address, PrivateKey, PublicKey}; use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use std::ops::{Add, Sub}; +pub use curve25519_dalek::scalar::Scalar; +pub use monero::*; + pub const MIN_CONFIRMATIONS: u32 = 10; pub fn random_private_key(rng: &mut R) -> PrivateKey { diff --git a/xmr-btc/tests/harness/wallet/monero.rs b/xmr-btc/tests/harness/wallet/monero.rs index 3cdc8a46..fbfd4270 100644 --- a/xmr-btc/tests/harness/wallet/monero.rs +++ b/xmr-btc/tests/harness/wallet/monero.rs @@ -1,12 +1,11 @@ use anyhow::Result; use async_trait::async_trait; use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _}; -use monero::{Address, Network, PrivateKey}; use monero_harness::rpc::wallet; use std::{str::FromStr, time::Duration}; use xmr_btc::monero::{ - Amount, CreateWalletForOutput, InsufficientFunds, PrivateViewKey, PublicKey, PublicViewKey, - Transfer, TransferProof, TxHash, WatchForTransfer, + Address, Amount, CreateWalletForOutput, InsufficientFunds, Network, PrivateKey, PrivateViewKey, + PublicKey, PublicViewKey, Transfer, TransferProof, TxHash, WatchForTransfer, }; pub struct Wallet(pub wallet::Client); diff --git a/xmr-btc/tests/on_chain.rs b/xmr-btc/tests/on_chain.rs index aa8adc61..459b368e 100644 --- a/xmr-btc/tests/on_chain.rs +++ b/xmr-btc/tests/on_chain.rs @@ -20,7 +20,7 @@ use tokio::sync::Mutex; use tracing::info; use xmr_btc::{ alice::{self, ReceiveBitcoinRedeemEncsig}, - bitcoin::{BroadcastSignedTransaction, EncryptedSignature, SignTxLock}, + bitcoin::{self, BroadcastSignedTransaction, EncryptedSignature, SignTxLock}, bob::{self, ReceiveTransferProof}, monero::{CreateWalletForOutput, Transfer, TransferProof}, }; @@ -309,8 +309,7 @@ async fn on_chain_happy_path() { assert_eq!( alice_final_btc_balance, - initial_balances.alice_btc + swap_amounts.btc - - bitcoin::Amount::from_sat(xmr_btc::bitcoin::TX_FEE) + initial_balances.alice_btc + swap_amounts.btc - bitcoin::Amount::from_sat(bitcoin::TX_FEE) ); assert_eq!( bob_final_btc_balance, @@ -411,7 +410,7 @@ async fn on_chain_both_refund_if_alice_never_redeems() { bob_final_btc_balance, // The 2 * TX_FEE corresponds to tx_refund and tx_cancel. initial_balances.bob_btc - - bitcoin::Amount::from_sat(2 * xmr_btc::bitcoin::TX_FEE) + - bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE) - lock_tx_bitcoin_fee ); @@ -508,7 +507,7 @@ async fn on_chain_alice_punishes_if_bob_never_acts_after_fund() { assert_eq!( alice_final_btc_balance, initial_balances.alice_btc + swap_amounts.btc - - bitcoin::Amount::from_sat(2 * xmr_btc::bitcoin::TX_FEE) + - bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE) ); assert_eq!( bob_final_btc_balance,