2020-10-15 18:14:39 -04:00
|
|
|
#![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::{bail, Result};
|
|
|
|
use futures::{channel::mpsc, StreamExt};
|
|
|
|
use libp2p::Multiaddr;
|
|
|
|
use log::LevelFilter;
|
2020-10-20 23:41:50 -04:00
|
|
|
use rand::rngs::OsRng;
|
2020-10-15 19:43:32 -04:00
|
|
|
use std::{io, io::Write, process};
|
2020-10-15 18:14:39 -04:00
|
|
|
use structopt::StructOpt;
|
|
|
|
use tracing::info;
|
2020-10-21 18:58:22 -04:00
|
|
|
use url::Url;
|
|
|
|
|
2020-10-15 18:14:39 -04:00
|
|
|
mod cli;
|
|
|
|
mod trace;
|
|
|
|
|
|
|
|
use cli::Options;
|
2020-10-21 18:58:22 -04:00
|
|
|
use swap::{alice, bitcoin::Wallet, bob, Cmd, Rsp, SwapParams};
|
|
|
|
use xmr_btc::bitcoin::BuildTxLockPsbt;
|
2020-10-15 18:14:39 -04:00
|
|
|
// TODO: Add root seed file instead of generating new seed each run.
|
|
|
|
|
|
|
|
// Alice's address and port until we have a config file.
|
|
|
|
pub const PORT: u16 = 9876; // Arbitrarily chosen.
|
|
|
|
pub const ADDR: &str = "127.0.0.1";
|
2020-10-21 18:58:22 -04:00
|
|
|
pub const BITCOIND_JSON_RPC_URL: &str = "127.0.0.1:8332";
|
2020-10-15 18:14:39 -04:00
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
async fn main() -> Result<()> {
|
|
|
|
let opt = Options::from_args();
|
|
|
|
|
|
|
|
trace::init_tracing(LevelFilter::Debug)?;
|
|
|
|
|
|
|
|
let addr = format!("/ip4/{}/tcp/{}", ADDR, PORT);
|
2020-10-20 23:41:50 -04:00
|
|
|
let alice: Multiaddr = addr.parse().expect("failed to parse Alice's address");
|
2020-10-15 18:14:39 -04:00
|
|
|
|
|
|
|
if opt.as_alice {
|
|
|
|
info!("running swap node as Alice ...");
|
|
|
|
|
|
|
|
if opt.piconeros.is_some() || opt.satoshis.is_some() {
|
|
|
|
bail!("Alice cannot set the amount to swap via the cli");
|
|
|
|
}
|
|
|
|
|
2020-10-21 18:58:22 -04:00
|
|
|
let url = Url::parse(BITCOIND_JSON_RPC_URL).expect("failed to parse url");
|
|
|
|
let bitcoin_wallet = Wallet::new("alice", &url)
|
|
|
|
.await
|
|
|
|
.expect("failed to create bitcoin wallet");
|
|
|
|
|
|
|
|
let redeem = bitcoin_wallet
|
|
|
|
.new_address()
|
|
|
|
.await
|
|
|
|
.expect("failed to get new redeem address");
|
|
|
|
let punish = bitcoin_wallet
|
|
|
|
.new_address()
|
|
|
|
.await
|
|
|
|
.expect("failed to get new punish address");
|
|
|
|
|
|
|
|
swap_as_alice(alice.clone(), redeem, punish).await?;
|
2020-10-15 18:14:39 -04:00
|
|
|
} else {
|
|
|
|
info!("running swap node as Bob ...");
|
|
|
|
|
2020-10-21 18:58:22 -04:00
|
|
|
let url = Url::parse(BITCOIND_JSON_RPC_URL).expect("failed to parse url");
|
|
|
|
let bitcoin_wallet = Wallet::new("bob", &url)
|
|
|
|
.await
|
|
|
|
.expect("failed to create bitcoin wallet");
|
|
|
|
|
|
|
|
let refund = bitcoin_wallet
|
|
|
|
.new_address()
|
|
|
|
.await
|
|
|
|
.expect("failed to get new address");
|
2020-10-20 23:41:50 -04:00
|
|
|
|
2020-10-15 18:14:39 -04:00
|
|
|
match (opt.piconeros, opt.satoshis) {
|
|
|
|
(Some(_), Some(_)) => bail!("Please supply only a single amount to swap"),
|
|
|
|
(None, None) => bail!("Please supply an amount to swap"),
|
|
|
|
(Some(_picos), _) => todo!("support starting with picos"),
|
|
|
|
(None, Some(sats)) => {
|
2020-10-21 18:58:22 -04:00
|
|
|
swap_as_bob(sats, alice, refund, bitcoin_wallet).await?;
|
2020-10-15 18:14:39 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-10-20 23:41:50 -04:00
|
|
|
async fn swap_as_alice(
|
|
|
|
addr: Multiaddr,
|
2020-10-21 18:58:22 -04:00
|
|
|
redeem: bitcoin::Address,
|
|
|
|
punish: bitcoin::Address,
|
2020-10-20 23:41:50 -04:00
|
|
|
) -> Result<()> {
|
2020-10-21 18:58:22 -04:00
|
|
|
alice::swap(addr, &mut OsRng, redeem, punish).await
|
2020-10-15 18:14:39 -04:00
|
|
|
}
|
|
|
|
|
2020-10-21 18:58:22 -04:00
|
|
|
async fn swap_as_bob<W>(
|
|
|
|
sats: u64,
|
|
|
|
addr: Multiaddr,
|
|
|
|
refund: bitcoin::Address,
|
|
|
|
wallet: W,
|
|
|
|
) -> Result<()>
|
|
|
|
where
|
|
|
|
W: BuildTxLockPsbt + Send + Sync + 'static,
|
|
|
|
{
|
2020-10-15 18:14:39 -04:00
|
|
|
let (cmd_tx, mut cmd_rx) = mpsc::channel(1);
|
|
|
|
let (mut rsp_tx, rsp_rx) = mpsc::channel(1);
|
2020-10-21 18:58:22 -04:00
|
|
|
tokio::spawn(bob::swap(sats, addr, cmd_tx, rsp_rx, refund, wallet));
|
2020-10-15 18:14:39 -04:00
|
|
|
loop {
|
|
|
|
let read = cmd_rx.next().await;
|
|
|
|
match read {
|
|
|
|
Some(cmd) => match cmd {
|
|
|
|
Cmd::VerifyAmounts(p) => {
|
2020-10-15 19:43:32 -04:00
|
|
|
let rsp = verify(p);
|
|
|
|
rsp_tx.try_send(rsp)?;
|
|
|
|
if rsp == Rsp::Abort {
|
|
|
|
process::exit(0);
|
2020-10-15 18:14:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
info!("Channel closed from other end");
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-15 19:43:32 -04:00
|
|
|
fn verify(p: SwapParams) -> Rsp {
|
|
|
|
let mut s = String::new();
|
|
|
|
println!("Got rate from Alice for XMR/BTC swap\n");
|
|
|
|
println!("{}", p);
|
|
|
|
print!("Would you like to continue with this swap [y/N]: ");
|
|
|
|
let _ = io::stdout().flush();
|
|
|
|
io::stdin()
|
|
|
|
.read_line(&mut s)
|
|
|
|
.expect("Did not enter a correct string");
|
|
|
|
if let Some('\n') = s.chars().next_back() {
|
|
|
|
s.pop();
|
|
|
|
}
|
|
|
|
if let Some('\r') = s.chars().next_back() {
|
|
|
|
s.pop();
|
|
|
|
}
|
|
|
|
if !is_yes(&s) {
|
|
|
|
println!("No worries, try again later - Alice updates her rate regularly");
|
|
|
|
return Rsp::Abort;
|
|
|
|
}
|
|
|
|
|
|
|
|
Rsp::Verified
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_yes(s: &str) -> bool {
|
|
|
|
matches!(s, "y" | "Y" | "yes" | "YES" | "Yes")
|
2020-10-15 18:14:39 -04:00
|
|
|
}
|