mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-04-21 16:29:20 -04:00
Upgrade CLI, enhance logging, add network to wallet
This commit is contained in:
parent
f88ed9183b
commit
92ccaee387
@ -13,21 +13,28 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use anyhow::Result;
|
||||
use futures::{channel::mpsc, StreamExt};
|
||||
use libp2p::Multiaddr;
|
||||
use prettytable::{row, Table};
|
||||
use std::{io, io::Write, process, sync::Arc};
|
||||
use rand::rngs::OsRng;
|
||||
use std::sync::Arc;
|
||||
use structopt::StructOpt;
|
||||
use swap::{
|
||||
alice, bitcoin, bob,
|
||||
alice,
|
||||
alice::swap::AliceState,
|
||||
bitcoin, bob,
|
||||
bob::swap::BobState,
|
||||
cli::Options,
|
||||
monero,
|
||||
network::transport::{build, build_tor, SwapTransport},
|
||||
network::transport::{build, build_tor},
|
||||
recover::recover,
|
||||
storage::Database,
|
||||
Cmd, Rsp, SwapAmounts,
|
||||
SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
|
||||
};
|
||||
use tracing::info;
|
||||
use tracing::{info, log::LevelFilter, subscriber};
|
||||
use tracing_log::LogTracer;
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
use uuid::Uuid;
|
||||
use xmr_btc::{config::Config, cross_curve_dleq};
|
||||
|
||||
#[macro_use]
|
||||
extern crate prettytable;
|
||||
@ -36,21 +43,33 @@ extern crate prettytable;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
init_tracing(LevelFilter::Trace).expect("initialize tracing");
|
||||
|
||||
let opt = Options::from_args();
|
||||
|
||||
// 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();
|
||||
|
||||
let rng = &mut OsRng;
|
||||
|
||||
match opt {
|
||||
Options::Alice {
|
||||
bitcoind_url,
|
||||
monerod_url,
|
||||
bitcoin_wallet_name,
|
||||
monero_wallet_rpc_url,
|
||||
listen_addr,
|
||||
tor_port,
|
||||
send_monero,
|
||||
receive_bitcoin,
|
||||
} => {
|
||||
info!("running swap node as Alice ...");
|
||||
|
||||
let behaviour = alice::Behaviour::default();
|
||||
let alice_peer_id = behaviour.peer_id().clone();
|
||||
info!(
|
||||
"Alice Peer ID (to be used by Bob to dial her): {}",
|
||||
alice_peer_id
|
||||
);
|
||||
let local_key_pair = behaviour.identity();
|
||||
|
||||
let (listen_addr, _ac, transport) = match tor_port {
|
||||
@ -72,29 +91,67 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
};
|
||||
|
||||
let bitcoin_wallet = bitcoin::Wallet::new("alice", bitcoind_url)
|
||||
.await
|
||||
.expect("failed to create bitcoin wallet");
|
||||
let amounts = SwapAmounts {
|
||||
btc: receive_bitcoin,
|
||||
xmr: send_monero,
|
||||
};
|
||||
|
||||
// TODO: network should be configurable through CLI, defaulting to mainnet
|
||||
let bitcoin_wallet = bitcoin::Wallet::new(
|
||||
bitcoin_wallet_name.as_str(),
|
||||
bitcoind_url,
|
||||
::bitcoin::Network::Bitcoin,
|
||||
)
|
||||
.await
|
||||
.expect("failed to create bitcoin wallet");
|
||||
|
||||
let bitcoin_balance = bitcoin_wallet.balance().await?;
|
||||
info!(
|
||||
"Connection to Bitcoin wallet succeeded, balance: {}",
|
||||
bitcoin_balance
|
||||
);
|
||||
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
||||
|
||||
let monero_wallet = Arc::new(monero::Wallet::new(monerod_url));
|
||||
let monero_wallet = monero::Wallet::new(monero_wallet_rpc_url);
|
||||
let monero_balance = monero_wallet.get_balance().await?;
|
||||
// TODO: impl Display for monero wallet to display proper monero balance
|
||||
info!(
|
||||
"Connection to Monero wallet succeeded, balance: {:?}",
|
||||
monero_balance
|
||||
);
|
||||
let monero_wallet = Arc::new(monero_wallet);
|
||||
|
||||
swap_as_alice(
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
db,
|
||||
listen_addr,
|
||||
transport,
|
||||
behaviour,
|
||||
let alice_state = {
|
||||
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);
|
||||
AliceState::Started {
|
||||
amounts,
|
||||
a,
|
||||
s_a,
|
||||
v_a,
|
||||
}
|
||||
};
|
||||
let alice_swarm = alice::new_swarm(listen_addr.clone(), transport, behaviour).unwrap();
|
||||
|
||||
alice::swap::swap(
|
||||
alice_state,
|
||||
alice_swarm,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet.clone(),
|
||||
Config::mainnet(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Options::Bob {
|
||||
alice_addr,
|
||||
satoshis,
|
||||
alice_peer_id,
|
||||
bitcoind_url,
|
||||
monerod_url,
|
||||
bitcoin_wallet_name,
|
||||
monero_wallet_rpc_url,
|
||||
tor,
|
||||
send_bitcoin,
|
||||
receive_monero,
|
||||
} => {
|
||||
info!("running swap node as Bob ...");
|
||||
|
||||
@ -106,21 +163,58 @@ async fn main() -> Result<()> {
|
||||
false => build(local_key_pair)?,
|
||||
};
|
||||
|
||||
let bitcoin_wallet = bitcoin::Wallet::new("bob", bitcoind_url)
|
||||
.await
|
||||
.expect("failed to create bitcoin wallet");
|
||||
let amounts = SwapAmounts {
|
||||
btc: send_bitcoin,
|
||||
xmr: receive_monero,
|
||||
};
|
||||
|
||||
let bitcoin_wallet = bitcoin::Wallet::new(
|
||||
bitcoin_wallet_name.as_str(),
|
||||
bitcoind_url,
|
||||
::bitcoin::Network::Bitcoin,
|
||||
)
|
||||
.await
|
||||
.expect("failed to create bitcoin wallet");
|
||||
let bitcoin_balance = bitcoin_wallet.balance().await?;
|
||||
info!(
|
||||
"Connection to Bitcoin wallet succeeded, balance: {}",
|
||||
bitcoin_balance
|
||||
);
|
||||
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
||||
|
||||
let monero_wallet = Arc::new(monero::Wallet::new(monerod_url));
|
||||
let monero_wallet = monero::Wallet::new(monero_wallet_rpc_url);
|
||||
let monero_balance = monero_wallet.get_balance().await?;
|
||||
info!(
|
||||
"Connection to Monero wallet succeeded, balance: {:?}",
|
||||
monero_balance
|
||||
);
|
||||
let monero_wallet = Arc::new(monero_wallet);
|
||||
|
||||
swap_as_bob(
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
let refund_address = bitcoin_wallet.new_address().await.unwrap();
|
||||
let state0 = xmr_btc::bob::State0::new(
|
||||
rng,
|
||||
send_bitcoin,
|
||||
receive_monero,
|
||||
REFUND_TIMELOCK,
|
||||
PUNISH_TIMELOCK,
|
||||
refund_address,
|
||||
);
|
||||
|
||||
let bob_state = BobState::Started {
|
||||
state0,
|
||||
amounts,
|
||||
peer_id: alice_peer_id,
|
||||
addr: alice_addr,
|
||||
};
|
||||
let bob_swarm = bob::new_swarm(transport, behaviour).unwrap();
|
||||
bob::swap::swap(
|
||||
bob_state,
|
||||
bob_swarm,
|
||||
db,
|
||||
satoshis,
|
||||
alice_addr,
|
||||
transport,
|
||||
behaviour,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet.clone(),
|
||||
OsRng,
|
||||
Uuid::new_v4(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@ -140,11 +234,16 @@ async fn main() -> Result<()> {
|
||||
swap_id,
|
||||
bitcoind_url,
|
||||
monerod_url,
|
||||
bitcoin_wallet_name,
|
||||
} => {
|
||||
let state = db.get_state(swap_id)?;
|
||||
let bitcoin_wallet = bitcoin::Wallet::new("bob", bitcoind_url)
|
||||
.await
|
||||
.expect("failed to create bitcoin wallet");
|
||||
let bitcoin_wallet = bitcoin::Wallet::new(
|
||||
bitcoin_wallet_name.as_ref(),
|
||||
bitcoind_url,
|
||||
::bitcoin::Network::Bitcoin,
|
||||
)
|
||||
.await
|
||||
.expect("failed to create bitcoin wallet");
|
||||
let monero_wallet = monero::Wallet::new(monerod_url);
|
||||
|
||||
recover(bitcoin_wallet, monero_wallet, state).await?;
|
||||
@ -172,94 +271,26 @@ async fn create_tor_service(
|
||||
Ok(authenticated_connection)
|
||||
}
|
||||
|
||||
async fn swap_as_alice(
|
||||
bitcoin_wallet: Arc<swap::bitcoin::Wallet>,
|
||||
monero_wallet: Arc<swap::monero::Wallet>,
|
||||
db: Database,
|
||||
addr: Multiaddr,
|
||||
transport: SwapTransport,
|
||||
behaviour: alice::Behaviour,
|
||||
) -> 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>,
|
||||
db: Database,
|
||||
sats: u64,
|
||||
alice: Multiaddr,
|
||||
transport: SwapTransport,
|
||||
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,
|
||||
));
|
||||
|
||||
loop {
|
||||
let read = cmd_rx.next().await;
|
||||
match read {
|
||||
Some(cmd) => match cmd {
|
||||
Cmd::VerifyAmounts(p) => {
|
||||
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(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn verify(amounts: SwapAmounts) -> Rsp {
|
||||
let mut s = String::new();
|
||||
println!("Got rate from Alice for XMR/BTC swap\n");
|
||||
println!("{}", amounts);
|
||||
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();
|
||||
pub fn init_tracing(level: log::LevelFilter) -> anyhow::Result<()> {
|
||||
if level == LevelFilter::Off {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !is_yes(&s) {
|
||||
println!("No worries, try again later - Alice updates her rate regularly");
|
||||
return Rsp::Abort;
|
||||
}
|
||||
// We want upstream library log messages, just only at Info level.
|
||||
LogTracer::init_with_filter(LevelFilter::Info)?;
|
||||
|
||||
Rsp::VerifiedAmounts
|
||||
}
|
||||
let is_terminal = atty::is(atty::Stream::Stderr);
|
||||
let subscriber = FmtSubscriber::builder()
|
||||
.with_env_filter(format!(
|
||||
"swap={},xmr-btc={},http=info,warp=info",
|
||||
level, level
|
||||
))
|
||||
.with_writer(std::io::stderr)
|
||||
.with_ansi(is_terminal)
|
||||
.finish();
|
||||
|
||||
fn is_yes(s: &str) -> bool {
|
||||
matches!(s, "y" | "Y" | "yes" | "YES" | "Yes")
|
||||
subscriber::set_global_default(subscriber)?;
|
||||
info!("Initialized tracing with level: {}", level);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -20,27 +20,33 @@ pub use xmr_btc::bitcoin::*;
|
||||
pub const TX_LOCK_MINE_TIMEOUT: u64 = 3600;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Wallet(pub bitcoin_harness::Wallet);
|
||||
pub struct Wallet {
|
||||
pub inner: bitcoin_harness::Wallet,
|
||||
pub network: bitcoin::Network,
|
||||
}
|
||||
|
||||
impl Wallet {
|
||||
pub async fn new(name: &str, url: Url) -> Result<Self> {
|
||||
pub async fn new(name: &str, url: Url, network: bitcoin::Network) -> Result<Self> {
|
||||
let wallet = bitcoin_harness::Wallet::new(name, url).await?;
|
||||
|
||||
Ok(Self(wallet))
|
||||
Ok(Self {
|
||||
inner: wallet,
|
||||
network,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn balance(&self) -> Result<Amount> {
|
||||
let balance = self.0.balance().await?;
|
||||
let balance = self.inner.balance().await?;
|
||||
Ok(balance)
|
||||
}
|
||||
|
||||
pub async fn new_address(&self) -> Result<Address> {
|
||||
self.0.new_address().await.map_err(Into::into)
|
||||
self.inner.new_address().await.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub async fn transaction_fee(&self, txid: Txid) -> Result<Amount> {
|
||||
let fee = self
|
||||
.0
|
||||
.inner
|
||||
.get_wallet_transaction(txid)
|
||||
.await
|
||||
.map(|res| {
|
||||
@ -64,7 +70,7 @@ impl BuildTxLockPsbt for Wallet {
|
||||
output_address: Address,
|
||||
output_amount: Amount,
|
||||
) -> Result<PartiallySignedTransaction> {
|
||||
let psbt = self.0.fund_psbt(output_address, output_amount).await?;
|
||||
let psbt = self.inner.fund_psbt(output_address, output_amount).await?;
|
||||
let as_hex = base64::decode(psbt)?;
|
||||
|
||||
let psbt = bitcoin::consensus::deserialize(&as_hex)?;
|
||||
@ -81,7 +87,10 @@ impl SignTxLock for Wallet {
|
||||
let psbt = bitcoin::consensus::serialize(&psbt);
|
||||
let as_base64 = base64::encode(psbt);
|
||||
|
||||
let psbt = self.0.wallet_process_psbt(PsbtBase64(as_base64)).await?;
|
||||
let psbt = self
|
||||
.inner
|
||||
.wallet_process_psbt(PsbtBase64(as_base64))
|
||||
.await?;
|
||||
let PsbtBase64(signed_psbt) = PsbtBase64::from(psbt);
|
||||
|
||||
let as_hex = base64::decode(signed_psbt)?;
|
||||
@ -96,7 +105,9 @@ impl SignTxLock for Wallet {
|
||||
#[async_trait]
|
||||
impl BroadcastSignedTransaction for Wallet {
|
||||
async fn broadcast_signed_transaction(&self, transaction: Transaction) -> Result<Txid> {
|
||||
Ok(self.0.send_raw_transaction(transaction).await?)
|
||||
let txid = self.inner.send_raw_transaction(transaction).await?;
|
||||
tracing::debug!("Bitcoin tx broadcasted! TXID = {}", txid);
|
||||
Ok(txid)
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,7 +116,7 @@ impl BroadcastSignedTransaction for Wallet {
|
||||
#[async_trait]
|
||||
impl WatchForRawTransaction for Wallet {
|
||||
async fn watch_for_raw_transaction(&self, txid: Txid) -> Transaction {
|
||||
(|| async { Ok(self.0.get_raw_transaction(txid).await?) })
|
||||
(|| async { Ok(self.inner.get_raw_transaction(txid).await?) })
|
||||
.retry(ConstantBackoff::new(Duration::from_secs(1)))
|
||||
.await
|
||||
.expect("transient errors to be retried")
|
||||
@ -116,14 +127,14 @@ impl WatchForRawTransaction for Wallet {
|
||||
impl GetRawTransaction for Wallet {
|
||||
// todo: potentially replace with option
|
||||
async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction> {
|
||||
Ok(self.0.get_raw_transaction(txid).await?)
|
||||
Ok(self.inner.get_raw_transaction(txid).await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl BlockHeight for Wallet {
|
||||
async fn block_height(&self) -> u32 {
|
||||
(|| async { Ok(self.0.client.getblockcount().await?) })
|
||||
(|| async { Ok(self.inner.client.getblockcount().await?) })
|
||||
.retry(ConstantBackoff::new(Duration::from_secs(1)))
|
||||
.await
|
||||
.expect("transient errors to be retried")
|
||||
@ -141,7 +152,7 @@ impl TransactionBlockHeight for Wallet {
|
||||
|
||||
(|| async {
|
||||
let block_height = self
|
||||
.0
|
||||
.inner
|
||||
.transaction_block_height(txid)
|
||||
.await
|
||||
.map_err(|_| backoff::Error::Transient(Error::Io))?;
|
||||
@ -167,7 +178,7 @@ impl WaitForTransactionFinality for Wallet {
|
||||
let mut interval = interval(config.bitcoin_avg_block_time / 4);
|
||||
|
||||
loop {
|
||||
let tx = self.0.client.get_raw_transaction_verbose(txid).await?;
|
||||
let tx = self.inner.client.get_raw_transaction_verbose(txid).await?;
|
||||
if let Some(confirmations) = tx.confirmations {
|
||||
if confirmations >= config.bitcoin_finality_confirmations {
|
||||
break;
|
||||
@ -179,3 +190,9 @@ impl WaitForTransactionFinality for Wallet {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Network for Wallet {
|
||||
fn get_network(&self) -> bitcoin::Network {
|
||||
self.network
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use libp2p::core::Multiaddr;
|
||||
use libp2p::{core::Multiaddr, PeerId};
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
@ -6,33 +7,71 @@ use uuid::Uuid;
|
||||
#[structopt(name = "xmr-btc-swap", about = "Trustless XMR BTC swaps")]
|
||||
pub enum Options {
|
||||
Alice {
|
||||
#[structopt(default_value = "http://127.0.0.1:8332", long = "bitcoind")]
|
||||
#[structopt(
|
||||
short = "b",
|
||||
long = "bitcoind",
|
||||
default_value = "http://127.0.0.1:8332"
|
||||
)]
|
||||
bitcoind_url: Url,
|
||||
|
||||
#[structopt(default_value = "http://127.0.0.1:18083/json_rpc", long = "monerod")]
|
||||
monerod_url: Url,
|
||||
#[structopt(short = "n", long = "bitcoin-wallet-name")]
|
||||
bitcoin_wallet_name: String,
|
||||
|
||||
#[structopt(default_value = "/ip4/127.0.0.1/tcp/9876", long = "listen-addr")]
|
||||
#[structopt(
|
||||
short = "m",
|
||||
long = "monero-wallet-rpc",
|
||||
default_value = "http://127.0.0.1:18083/json_rpc"
|
||||
)]
|
||||
monero_wallet_rpc_url: Url,
|
||||
|
||||
#[structopt(
|
||||
short = "a",
|
||||
long = "listen-addr",
|
||||
default_value = "/ip4/127.0.0.1/tcp/9876"
|
||||
)]
|
||||
listen_addr: Multiaddr,
|
||||
|
||||
#[structopt(long = "tor-port")]
|
||||
#[structopt(short = "t", long = "tor-port")]
|
||||
tor_port: Option<u16>,
|
||||
|
||||
#[structopt(short = "s", long = "send-piconeros", parse(try_from_str = parse_pics))]
|
||||
send_monero: xmr_btc::monero::Amount,
|
||||
|
||||
#[structopt(short = "r", long = "receive-sats", parse(try_from_str = parse_sats))]
|
||||
receive_bitcoin: bitcoin::Amount,
|
||||
},
|
||||
Bob {
|
||||
#[structopt(long = "sats")]
|
||||
satoshis: u64,
|
||||
|
||||
#[structopt(long = "alice-addr")]
|
||||
#[structopt(short = "a", long = "alice-addr")]
|
||||
alice_addr: Multiaddr,
|
||||
|
||||
#[structopt(default_value = "http://127.0.0.1:8332", long = "bitcoind")]
|
||||
#[structopt(short = "p", long = "alice-peer-id")]
|
||||
alice_peer_id: PeerId,
|
||||
|
||||
#[structopt(
|
||||
short = "b",
|
||||
long = "bitcoind",
|
||||
default_value = "http://127.0.0.1:8332"
|
||||
)]
|
||||
bitcoind_url: Url,
|
||||
|
||||
#[structopt(default_value = "http://127.0.0.1:18083/json_rpc", long = "monerod")]
|
||||
monerod_url: Url,
|
||||
#[structopt(short = "n", long = "bitcoin-wallet-name")]
|
||||
bitcoin_wallet_name: String,
|
||||
|
||||
#[structopt(long = "tor")]
|
||||
#[structopt(
|
||||
short = "m",
|
||||
long = "monerod",
|
||||
default_value = "http://127.0.0.1:18083/json_rpc"
|
||||
)]
|
||||
monero_wallet_rpc_url: Url,
|
||||
|
||||
#[structopt(short = "t", long = "tor")]
|
||||
tor: bool,
|
||||
|
||||
#[structopt(short = "s", long = "send-sats", parse(try_from_str = parse_sats))]
|
||||
send_bitcoin: bitcoin::Amount,
|
||||
|
||||
#[structopt(short = "r", long = "receive-piconeros", parse(try_from_str = parse_pics))]
|
||||
receive_monero: xmr_btc::monero::Amount,
|
||||
},
|
||||
History,
|
||||
Recover {
|
||||
@ -44,5 +83,20 @@ pub enum Options {
|
||||
|
||||
#[structopt(default_value = "http://127.0.0.1:18083/json_rpc", long = "monerod")]
|
||||
monerod_url: Url,
|
||||
|
||||
#[structopt(short = "n", long = "bitcoin-wallet-name")]
|
||||
bitcoin_wallet_name: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn parse_sats(str: &str) -> anyhow::Result<bitcoin::Amount> {
|
||||
let sats = u64::from_str(str)?;
|
||||
let amount = bitcoin::Amount::from_sat(sats);
|
||||
Ok(amount)
|
||||
}
|
||||
|
||||
fn parse_pics(str: &str) -> anyhow::Result<xmr_btc::monero::Amount> {
|
||||
let pics = u64::from_str(str)?;
|
||||
let amount = xmr_btc::monero::Amount::from_piconero(pics);
|
||||
Ok(amount)
|
||||
}
|
||||
|
@ -14,8 +14,23 @@ pub mod state;
|
||||
pub mod storage;
|
||||
pub mod tor;
|
||||
|
||||
pub const REFUND_TIMELOCK: u32 = 10; // Relative timelock, this is number of blocks. TODO: What should it be?
|
||||
pub const PUNISH_TIMELOCK: u32 = 10; // FIXME: What should this be?
|
||||
// REFUND_TIMELOCK determines the interval between lock-time until TX_cancel is
|
||||
// allowed, PUNISH_TIMELOCK determines the interval between TX_cancel and
|
||||
// TX_punish being allowed.
|
||||
//
|
||||
// *[1]
|
||||
// |----REFUND_TIMELOCK--|
|
||||
// *[2]
|
||||
// |----PUNISH_TIMELOCK----|
|
||||
// *[3]
|
||||
// [1] LockTime point
|
||||
// [2] TX_cancel+TX_Refund point
|
||||
// [3] TX_punish point
|
||||
//
|
||||
// Given the above, setting both to 24 blocks (roughly 4h) is reasonable.
|
||||
// TODO: More reasoning what are "good" timelocks
|
||||
pub const REFUND_TIMELOCK: u32 = 24; // Relative timelock, this is number of blocks.
|
||||
pub const PUNISH_TIMELOCK: u32 = 24;
|
||||
|
||||
pub type Never = std::convert::Infallible;
|
||||
|
||||
|
@ -39,11 +39,15 @@ impl Transfer for Wallet {
|
||||
.await?;
|
||||
|
||||
let tx_hash = TxHash(res.tx_hash);
|
||||
tracing::debug!("Monero tx broadcasted!, tx hash: {:?}", tx_hash);
|
||||
let tx_key = PrivateKey::from_str(&res.tx_key)?;
|
||||
|
||||
let fee = Amount::from_piconero(res.fee);
|
||||
|
||||
Ok((TransferProof::new(tx_hash, tx_key), fee))
|
||||
let transfer_proof = TransferProof::new(tx_hash, tx_key);
|
||||
tracing::debug!(" Transfer proof: {:?}", transfer_proof);
|
||||
|
||||
Ok((transfer_proof, fee))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ pub async fn alice_recover(
|
||||
|
||||
info!("Checking if the Bitcoin cancel transaction has been published");
|
||||
if bitcoin_wallet
|
||||
.0
|
||||
.inner
|
||||
.get_raw_transaction(tx_cancel.txid())
|
||||
.await
|
||||
.is_err()
|
||||
@ -164,7 +164,7 @@ pub async fn alice_recover(
|
||||
.transaction_block_height(state.tx_lock.txid())
|
||||
.await;
|
||||
|
||||
let block_height = bitcoin_wallet.0.client.getblockcount().await?;
|
||||
let block_height = bitcoin_wallet.inner.client.getblockcount().await?;
|
||||
let refund_absolute_expiry = tx_lock_height + state.refund_timelock;
|
||||
|
||||
info!("Checking refund timelock");
|
||||
@ -187,7 +187,7 @@ pub async fn alice_recover(
|
||||
|
||||
info!("Checking if the Bitcoin cancel transaction has been published");
|
||||
if bitcoin_wallet
|
||||
.0
|
||||
.inner
|
||||
.get_raw_transaction(tx_cancel.txid())
|
||||
.await
|
||||
.is_err()
|
||||
@ -290,7 +290,11 @@ pub async fn alice_recover(
|
||||
|
||||
// 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 {
|
||||
match bitcoin_wallet
|
||||
.inner
|
||||
.get_raw_transaction(tx_refund.txid())
|
||||
.await
|
||||
{
|
||||
Ok(tx_refund_published) => {
|
||||
info!("Bitcoin already refunded");
|
||||
|
||||
@ -375,7 +379,7 @@ pub async fn bob_recover(
|
||||
|
||||
info!("Checking if the Bitcoin cancel transaction has been published");
|
||||
if bitcoin_wallet
|
||||
.0
|
||||
.inner
|
||||
.get_raw_transaction(tx_cancel.txid())
|
||||
.await
|
||||
.is_err()
|
||||
@ -431,7 +435,7 @@ pub async fn bob_recover(
|
||||
|
||||
let tx_redeem = bitcoin::TxRedeem::new(&state.tx_lock, &state.redeem_address);
|
||||
let tx_redeem_published = bitcoin_wallet
|
||||
.0
|
||||
.inner
|
||||
.get_raw_transaction(tx_redeem.txid())
|
||||
.await?;
|
||||
|
||||
|
@ -210,9 +210,13 @@ async fn init_alice(
|
||||
));
|
||||
|
||||
let alice_btc_wallet = Arc::new(
|
||||
swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone())
|
||||
.await
|
||||
.unwrap(),
|
||||
swap::bitcoin::Wallet::new(
|
||||
"alice",
|
||||
bitcoind.node_url.clone(),
|
||||
::bitcoin::Network::Regtest,
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let amounts = SwapAmounts {
|
||||
@ -265,13 +269,17 @@ async fn init_bob(
|
||||
Database,
|
||||
) {
|
||||
let bob_btc_wallet = Arc::new(
|
||||
swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone())
|
||||
.await
|
||||
.unwrap(),
|
||||
swap::bitcoin::Wallet::new(
|
||||
"bob",
|
||||
bitcoind.node_url.clone(),
|
||||
::bitcoin::Network::Regtest,
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
bitcoind
|
||||
.mint(
|
||||
bob_btc_wallet.0.new_address().await.unwrap(),
|
||||
bob_btc_wallet.inner.new_address().await.unwrap(),
|
||||
btc_starting_balance,
|
||||
)
|
||||
.await
|
||||
|
@ -15,7 +15,14 @@ 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;
|
||||
// TODO: Configurable tx-fee (note: parties have to agree prior to swapping)
|
||||
// Current reasoning:
|
||||
// tx with largest weight (as determined by get_weight() upon broadcast in e2e
|
||||
// test) = 609 assuming segwit and 60 sat/vB:
|
||||
// (609 / 4) * 60 (sat/vB) = 9135 sats
|
||||
// Recommended: Overpay a bit to ensure we don't have to wait too long for test
|
||||
// runs.
|
||||
pub const TX_FEE: u64 = 15_000;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
pub struct SecretKey {
|
||||
@ -212,6 +219,11 @@ pub trait GetRawTransaction {
|
||||
async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Network {
|
||||
fn get_network(&self) -> bitcoin::Network;
|
||||
}
|
||||
|
||||
pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Result<SecretKey> {
|
||||
let adaptor = Adaptor::<Sha256, Deterministic<Sha256>>::default();
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
use crate::bitcoin::{
|
||||
build_shared_output_descriptor, verify_sig, BuildTxLockPsbt, OutPoint, PublicKey, Txid, TX_FEE,
|
||||
build_shared_output_descriptor, verify_sig, BuildTxLockPsbt, Network, OutPoint, PublicKey,
|
||||
Txid, TX_FEE,
|
||||
};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use bitcoin::{
|
||||
util::{bip143::SigHashCache, psbt::PartiallySignedTransaction},
|
||||
Address, Amount, Network, SigHash, SigHashType, Transaction, TxIn, TxOut,
|
||||
Address, Amount, SigHash, SigHashType, Transaction, TxIn, TxOut,
|
||||
};
|
||||
use ecdsa_fun::Signature;
|
||||
use miniscript::{Descriptor, NullCtx};
|
||||
@ -20,11 +21,11 @@ pub struct TxLock {
|
||||
impl TxLock {
|
||||
pub async fn new<W>(wallet: &W, amount: Amount, A: PublicKey, B: PublicKey) -> Result<Self>
|
||||
where
|
||||
W: BuildTxLockPsbt,
|
||||
W: BuildTxLockPsbt + Network,
|
||||
{
|
||||
let lock_output_descriptor = build_shared_output_descriptor(A.0, B.0);
|
||||
let address = lock_output_descriptor
|
||||
.address(Network::Regtest, NullCtx)
|
||||
.address(wallet.get_network(), NullCtx)
|
||||
.expect("can derive address from descriptor");
|
||||
|
||||
// We construct a psbt for convenience
|
||||
|
@ -33,7 +33,7 @@ use tracing::error;
|
||||
|
||||
pub mod message;
|
||||
use crate::{
|
||||
bitcoin::{BlockHeight, GetRawTransaction, TransactionBlockHeight},
|
||||
bitcoin::{BlockHeight, GetRawTransaction, Network, TransactionBlockHeight},
|
||||
monero::{CreateWalletForOutput, WatchForTransfer},
|
||||
};
|
||||
use ::bitcoin::{Transaction, Txid};
|
||||
@ -267,7 +267,7 @@ where
|
||||
// send to one receive in the correct order.
|
||||
pub async fn next_state<
|
||||
R: RngCore + CryptoRng,
|
||||
B: WatchForRawTransaction + SignTxLock + BuildTxLockPsbt + BroadcastSignedTransaction,
|
||||
B: WatchForRawTransaction + SignTxLock + BuildTxLockPsbt + BroadcastSignedTransaction + Network,
|
||||
M: CreateWalletForOutput + WatchForTransfer,
|
||||
T: SendMessage<Message> + ReceiveMessage<alice::Message>,
|
||||
>(
|
||||
@ -401,7 +401,7 @@ impl State0 {
|
||||
|
||||
pub async fn receive<W>(self, wallet: &W, msg: alice::Message0) -> anyhow::Result<State1>
|
||||
where
|
||||
W: BuildTxLockPsbt,
|
||||
W: BuildTxLockPsbt + Network,
|
||||
{
|
||||
msg.dleq_proof_s_a.verify(
|
||||
msg.S_a_bitcoin.clone().into(),
|
||||
|
@ -7,8 +7,8 @@ use reqwest::Url;
|
||||
use std::time::Duration;
|
||||
use tokio::time;
|
||||
use xmr_btc::bitcoin::{
|
||||
BlockHeight, BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, TransactionBlockHeight,
|
||||
TxLock, WatchForRawTransaction,
|
||||
BlockHeight, BroadcastSignedTransaction, BuildTxLockPsbt, Network, SignTxLock,
|
||||
TransactionBlockHeight, TxLock, WatchForRawTransaction,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -162,3 +162,9 @@ impl TransactionBlockHeight for Wallet {
|
||||
.expect("transient errors to be retried")
|
||||
}
|
||||
}
|
||||
|
||||
impl Network for Wallet {
|
||||
fn get_network(&self) -> bitcoin::Network {
|
||||
bitcoin::Network::Regtest
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user