xmr-btc-swap/swap/src/bin/swap.rs

288 lines
8.2 KiB
Rust
Raw Normal View History

#![warn(
unused_extern_crates,
missing_debug_implementations,
missing_copy_implementations,
rust_2018_idioms,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::fallible_impl_from,
clippy::cast_precision_loss,
clippy::cast_possible_wrap,
clippy::dbg_macro
)]
#![forbid(unsafe_code)]
use anyhow::Result;
use futures::{channel::mpsc, StreamExt};
2020-11-03 00:05:39 +00:00
use libp2p::Multiaddr;
2020-11-05 05:55:30 +00:00
use prettytable::{row, Table};
use rand::rngs::OsRng;
use std::{io, io::Write, process, sync::Arc};
use structopt::StructOpt;
use swap::{
2020-11-30 02:25:11 +00:00
alice, bitcoin, bob,
cli::Options,
monero,
network::transport::{build, build_tor, SwapTransport},
2020-11-06 00:10:22 +00:00
recover::recover,
storage::Database,
Cmd, Rsp, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
};
use tracing::info;
use xmr_btc::{alice::State0, cross_curve_dleq};
2020-11-05 05:55:30 +00:00
#[macro_use]
extern crate prettytable;
// TODO: Add root seed file instead of generating new seed each run.
#[tokio::main]
async fn main() -> Result<()> {
let opt = Options::from_args();
2020-11-05 05:55:30 +00:00
// 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 {
bitcoind_url,
monerod_url,
listen_addr,
tor_port,
} => {
info!("running swap node as Alice ...");
let bitcoin_wallet = bitcoin::Wallet::new("alice", bitcoind_url)
.await
.expect("failed to create bitcoin wallet");
let bitcoin_wallet = Arc::new(bitcoin_wallet);
let monero_wallet = Arc::new(monero::Wallet::new(monerod_url));
let rng = &mut OsRng;
let a = bitcoin::SecretKey::new_random(rng);
let s_a = cross_curve_dleq::Scalar::random(rng);
let v_a = xmr_btc::monero::PrivateViewKey::new_random(rng);
let redeem_address = bitcoin_wallet.as_ref().new_address().await?;
let punish_address = redeem_address.clone();
let state0 = State0::new(
a,
s_a,
v_a,
// todo: get from CLI args
bitcoin::Amount::from_sat(100),
// todo: get from CLI args
monero::Amount::from_piconero(1000000),
REFUND_TIMELOCK,
PUNISH_TIMELOCK,
redeem_address,
punish_address,
);
let behaviour = alice::Behaviour::new(state0);
let local_key_pair = behaviour.identity();
2020-11-03 00:00:39 +00:00
let (listen_addr, _ac, transport) = match tor_port {
Some(tor_port) => {
let tor_secret_key = torut::onion::TorSecretKeyV3::generate();
let onion_address = tor_secret_key
.public()
.get_onion_address()
.get_address_without_dot_onion();
let onion_address_string = format!("/onion3/{}:{}", onion_address, tor_port);
let addr: Multiaddr = onion_address_string.parse()?;
let ac = create_tor_service(tor_secret_key, tor_port).await?;
2020-11-03 03:54:36 +00:00
let transport = build_tor(local_key_pair, Some((addr.clone(), tor_port)))?;
(addr, Some(ac), transport)
}
None => {
let transport = build(local_key_pair)?;
(listen_addr, None, transport)
}
};
swap_as_alice(
bitcoin_wallet,
monero_wallet,
db,
listen_addr,
transport,
behaviour,
)
.await?;
}
Options::Bob {
alice_addr,
satoshis,
bitcoind_url,
monerod_url,
2020-11-03 03:54:36 +00:00
tor,
} => {
info!("running swap node as Bob ...");
2020-10-26 05:55:53 +00:00
2020-11-30 02:25:11 +00:00
let behaviour = bob::Behaviour::default();
let local_key_pair = behaviour.identity();
2020-11-03 03:54:36 +00:00
let transport = match tor {
true => build_tor(local_key_pair, None)?,
false => build(local_key_pair)?,
};
let bitcoin_wallet = bitcoin::Wallet::new("bob", bitcoind_url)
.await
.expect("failed to create bitcoin wallet");
let bitcoin_wallet = Arc::new(bitcoin_wallet);
let monero_wallet = Arc::new(monero::Wallet::new(monerod_url));
swap_as_bob(
bitcoin_wallet,
monero_wallet,
db,
satoshis,
alice_addr,
transport,
behaviour,
)
.await?;
}
2020-11-05 05:55:30 +00:00
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();
}
2020-11-06 00:10:22 +00:00
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(())
}
async fn create_tor_service(
tor_secret_key: torut::onion::TorSecretKeyV3,
tor_port: u16,
) -> Result<swap::tor::AuthenticatedConnection> {
// TODO use configurable ports for tor connection
let mut authenticated_connection = swap::tor::UnauthenticatedConnection::default()
2020-10-26 05:55:53 +00:00
.init_authenticated_connection()
.await?;
tracing::info!("Tor authenticated.");
authenticated_connection
.add_service(tor_port, &tor_secret_key)
2020-10-26 05:55:53 +00:00
.await?;
tracing::info!("Tor service added.");
Ok(authenticated_connection)
}
2020-10-21 03:41:50 +00:00
async fn swap_as_alice(
bitcoin_wallet: Arc<swap::bitcoin::Wallet>,
monero_wallet: Arc<swap::monero::Wallet>,
2020-11-03 04:26:47 +00:00
db: Database,
2020-10-21 03:41:50 +00:00
addr: Multiaddr,
transport: SwapTransport,
2020-11-30 02:25:11 +00:00
behaviour: alice::Behaviour,
2020-10-21 03:41:50 +00:00
) -> Result<()> {
alice::swap(
bitcoin_wallet,
monero_wallet,
db,
addr,
transport,
behaviour,
)
.await
}
async fn swap_as_bob(
bitcoin_wallet: Arc<swap::bitcoin::Wallet>,
monero_wallet: Arc<swap::monero::Wallet>,
2020-11-03 04:26:47 +00:00
db: Database,
sats: u64,
2020-10-22 02:48:30 +00:00
alice: Multiaddr,
transport: SwapTransport,
2020-11-30 02:25:11 +00:00
behaviour: bob::Behaviour,
) -> Result<()> {
let (cmd_tx, mut cmd_rx) = mpsc::channel(1);
let (mut rsp_tx, rsp_rx) = mpsc::channel(1);
tokio::spawn(bob::swap(
bitcoin_wallet,
monero_wallet,
db,
sats,
alice,
cmd_tx,
rsp_rx,
transport,
behaviour,
));
2020-10-22 02:48:30 +00:00
loop {
let read = cmd_rx.next().await;
match read {
Some(cmd) => match cmd {
Cmd::VerifyAmounts(p) => {
2020-10-15 23:43:32 +00:00
let rsp = verify(p);
rsp_tx.try_send(rsp)?;
if rsp == Rsp::Abort {
process::exit(0);
}
}
},
None => {
info!("Channel closed from other end");
return Ok(());
}
}
}
}
2020-10-22 02:30:07 +00:00
fn verify(amounts: SwapAmounts) -> Rsp {
2020-10-15 23:43:32 +00:00
let mut s = String::new();
println!("Got rate from Alice for XMR/BTC swap\n");
2020-10-22 02:30:07 +00:00
println!("{}", amounts);
2020-10-15 23:43:32 +00:00
print!("Would you like to continue with this swap [y/N]: ");
2020-10-22 02:48:30 +00:00
2020-10-15 23:43:32 +00:00
let _ = io::stdout().flush();
io::stdin()
.read_line(&mut s)
.expect("Did not enter a correct string");
2020-10-22 02:48:30 +00:00
2020-10-15 23:43:32 +00:00
if let Some('\n') = s.chars().next_back() {
s.pop();
}
if let Some('\r') = s.chars().next_back() {
s.pop();
}
2020-10-22 02:48:30 +00:00
2020-10-15 23:43:32 +00:00
if !is_yes(&s) {
println!("No worries, try again later - Alice updates her rate regularly");
return Rsp::Abort;
}
2020-10-22 02:55:50 +00:00
Rsp::VerifiedAmounts
2020-10-15 23:43:32 +00:00
}
fn is_yes(s: &str) -> bool {
matches!(s, "y" | "Y" | "yes" | "YES" | "Yes")
}