Merge remote-tracking branch 'upstream/master' into latest_version_check

This commit is contained in:
Byron Hambly 2022-08-08 11:43:11 +02:00
commit 493324ce1b
No known key found for this signature in database
GPG key ID: DE8F6EA20A661697
29 changed files with 500 additions and 243 deletions

View file

@ -276,7 +276,7 @@ pub enum RawCommand {
WithdrawBtc {
#[structopt(
long = "amount",
help = "Optionally specify the amount of Bitcoin to be withdrawn. If not specified the wallet will be drained."
help = "Optionally specify the amount of Bitcoin to be withdrawn. If not specified the wallet will be drained. Amount must be specified in quotes with denomination, e.g `--amount '0.1 BTC'`"
)]
amount: Option<Amount>,
#[structopt(long = "address", help = "The address to receive the Bitcoin.")]

View file

@ -326,11 +326,13 @@ where
.ask()
.context("Failed to compute asking price")?;
let max_bitcoin_for_monero = self
.monero_wallet
.get_balance()
.await?
.max_bitcoin_for_price(ask_price);
let xmr = self.monero_wallet.get_balance().await?;
let max_bitcoin_for_monero = xmr.max_bitcoin_for_price(ask_price).ok_or_else(|| {
anyhow::anyhow!("Bitcoin price ({}) x Monero ({}) overflow", ask_price, xmr)
})?;
tracing::debug!(%ask_price, %xmr, %max_bitcoin_for_monero);
if min_buy > max_bitcoin_for_monero {
tracing::warn!(

View file

@ -13,6 +13,7 @@ use libp2p::core::connection::ConnectionId;
use libp2p::core::muxing::StreamMuxerBox;
use libp2p::core::transport::Boxed;
use libp2p::dns::TokioDnsConfig;
use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent};
use libp2p::ping::{Ping, PingConfig, PingEvent};
use libp2p::request_response::{RequestId, ResponseChannel};
use libp2p::swarm::{
@ -111,6 +112,7 @@ pub mod behaviour {
pub swap_setup: alice::Behaviour<LR>,
pub transfer_proof: transfer_proof::Behaviour,
pub encrypted_signature: encrypted_signature::Behaviour,
pub identify: Identify,
/// Ping behaviour that ensures that the underlying network connection
/// is still alive. If the ping fails a connection close event
@ -128,8 +130,14 @@ pub mod behaviour {
latest_rate: LR,
resume_only: bool,
env_config: env::Config,
identify_params: (identity::Keypair, XmrBtcNamespace),
rendezvous_params: Option<(identity::Keypair, PeerId, Multiaddr, XmrBtcNamespace)>,
) -> Self {
let agentVersion = format!("asb/{} ({})", env!("CARGO_PKG_VERSION"), identify_params.1);
let protocolVersion = "/comit/xmr/btc/1.0.0".to_string();
let identifyConfig = IdentifyConfig::new(protocolVersion, identify_params.0.public())
.with_agent_version(agentVersion);
Self {
rendezvous: libp2p::swarm::toggle::Toggle::from(rendezvous_params.map(
|(identity, rendezvous_peer_id, rendezvous_address, namespace)| {
@ -153,6 +161,7 @@ pub mod behaviour {
transfer_proof: transfer_proof::alice(),
encrypted_signature: encrypted_signature::alice(),
ping: Ping::new(PingConfig::new().with_keep_alive(true)),
identify: Identify::new(identifyConfig),
}
}
}
@ -163,6 +172,12 @@ pub mod behaviour {
}
}
impl From<IdentifyEvent> for OutEvent {
fn from(_: IdentifyEvent) -> Self {
OutEvent::Other
}
}
impl From<libp2p::rendezvous::client::Event> for OutEvent {
fn from(event: libp2p::rendezvous::client::Event) -> Self {
OutEvent::Rendezvous(event)

View file

@ -1,6 +1,6 @@
use anyhow::Result;
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::fmt::time::ChronoLocal;
use tracing_subscriber::fmt::time::UtcTime;
use tracing_subscriber::FmtSubscriber;
pub fn init(level: LevelFilter, json_format: bool, timestamp: bool) -> Result<()> {
@ -14,7 +14,7 @@ pub fn init(level: LevelFilter, json_format: bool, timestamp: bool) -> Result<()
.with_env_filter(format!("asb={},swap={}", level, level))
.with_writer(std::io::stderr)
.with_ansi(is_terminal)
.with_timer(ChronoLocal::with_format("%F %T".to_owned()))
.with_timer(UtcTime::rfc_3339())
.with_target(false);
match (json_format, timestamp) {

View file

@ -136,6 +136,8 @@ async fn main() -> Result<()> {
};
let kraken_rate = KrakenRate::new(config.maker.ask_spread, kraken_price_updates);
let namespace = XmrBtcNamespace::from_is_testnet(testnet);
let mut swarm = swarm::asb(
&seed,
config.maker.min_buy_btc,
@ -143,16 +145,8 @@ async fn main() -> Result<()> {
kraken_rate.clone(),
resume_only,
env_config,
config.network.rendezvous_point.map(|rendezvous_point| {
(
rendezvous_point,
if testnet {
XmrBtcNamespace::Testnet
} else {
XmrBtcNamespace::Mainnet
},
)
}),
namespace,
config.network.rendezvous_point,
)?;
for listen in config.network.listen.clone() {

View file

@ -63,6 +63,7 @@ async fn main() -> Result<()> {
monero_receive_address,
monero_daemon_address,
tor_socks5_port,
namespace,
} => {
let swap_id = Uuid::new_v4();
@ -93,7 +94,12 @@ async fn main() -> Result<()> {
.context("Seller address must contain peer ID")?;
db.insert_address(seller_peer_id, seller.clone()).await?;
let behaviour = cli::Behaviour::new(seller_peer_id, env_config, bitcoin_wallet.clone());
let behaviour = cli::Behaviour::new(
seller_peer_id,
env_config,
bitcoin_wallet.clone(),
(seed.derive_libp2p_identity(), namespace),
);
let mut swarm =
swarm::cli(seed.derive_libp2p_identity(), tor_socks5_port, behaviour).await?;
swarm.behaviour_mut().add_address(seller_peer_id, seller);
@ -105,6 +111,8 @@ async fn main() -> Result<()> {
let event_loop = tokio::spawn(event_loop.run());
let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size());
let estimate_fee = |amount| bitcoin_wallet.estimate_fee(TxLock::weight(), amount);
let (amount, fees) = match determine_btc_to_swap(
json,
event_loop_handle.request_quote(),
@ -112,6 +120,7 @@ async fn main() -> Result<()> {
|| bitcoin_wallet.balance(),
max_givable,
|| bitcoin_wallet.sync(),
estimate_fee,
)
.await
{
@ -271,6 +280,7 @@ async fn main() -> Result<()> {
bitcoin_target_block,
monero_daemon_address,
tor_socks5_port,
namespace,
} => {
cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?;
@ -298,7 +308,12 @@ async fn main() -> Result<()> {
let seller_peer_id = db.get_peer_id(swap_id).await?;
let seller_addresses = db.get_addresses(seller_peer_id).await?;
let behaviour = cli::Behaviour::new(seller_peer_id, env_config, bitcoin_wallet.clone());
let behaviour = cli::Behaviour::new(
seller_peer_id,
env_config,
bitcoin_wallet.clone(),
(seed.derive_libp2p_identity(), namespace),
);
let mut swarm =
swarm::cli(seed.derive_libp2p_identity(), tor_socks5_port, behaviour).await?;
let our_peer_id = swarm.local_peer_id();
@ -609,13 +624,14 @@ fn qr_code(value: &impl ToString) -> Result<String> {
Ok(qr_code)
}
async fn determine_btc_to_swap<FB, TB, FMG, TMG, FS, TS>(
async fn determine_btc_to_swap<FB, TB, FMG, TMG, FS, TS, FFE, TFE>(
json: bool,
bid_quote: impl Future<Output = Result<BidQuote>>,
get_new_address: impl Future<Output = Result<bitcoin::Address>>,
balance: FB,
max_giveable_fn: FMG,
sync: FS,
estimate_fee: FFE,
) -> Result<(bitcoin::Amount, bitcoin::Amount)>
where
TB: Future<Output = Result<bitcoin::Amount>>,
@ -624,6 +640,8 @@ where
FMG: Fn() -> TMG,
TS: Future<Output = Result<()>>,
FS: Fn() -> TS,
FFE: Fn(bitcoin::Amount) -> TFE,
TFE: Future<Output = Result<bitcoin::Amount>>,
{
tracing::debug!("Requesting quote");
let bid_quote = bid_quote.await?;
@ -651,8 +669,17 @@ where
}
loop {
let min_outstanding = bid_quote.min_quantity - max_giveable;
let min_fee = estimate_fee(min_outstanding).await?;
let min_deposit = min_outstanding + min_fee;
tracing::info!(
"Deposit at least {} to cover the min quantity with fee!",
min_deposit
);
tracing::info!(
%deposit_address,
%min_deposit,
%max_giveable,
%minimum_amount,
%maximum_amount,
@ -684,9 +711,7 @@ where
let balance = balance().await?;
let fees = balance - max_giveable;
let max_accepted = bid_quote.max_quantity;
let btc_swap_amount = min(max_giveable, max_accepted);
Ok((btc_swap_amount, fees))
@ -752,6 +777,7 @@ mod tests {
result.give()
},
|| async { Ok(()) },
|_| async { Ok(Amount::from_sat(1000)) },
)
.await
.unwrap();
@ -763,7 +789,8 @@ mod tests {
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00000000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC
INFO swap: Deposit at least 0.00001000 BTC to cover the min quantity with fee!
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC
INFO swap: Received Bitcoin new_balance=0.00100000 BTC max_giveable=0.00090000 BTC
"
);
@ -787,6 +814,7 @@ mod tests {
result.give()
},
|| async { Ok(()) },
|_| async { Ok(Amount::from_sat(1000)) },
)
.await
.unwrap();
@ -798,14 +826,15 @@ mod tests {
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00000000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC
INFO swap: Deposit at least 0.00001000 BTC to cover the min quantity with fee!
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC
INFO swap: Received Bitcoin new_balance=0.10010000 BTC max_giveable=0.10000000 BTC
"
);
}
#[tokio::test]
async fn given_initial_balance_below_max_quantity_swaps_max_givable() {
async fn given_initial_balance_below_max_quantity_swaps_max_giveable() {
let writer = capture_logs(LevelFilter::INFO);
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
Amount::from_btc(0.0049).unwrap(),
@ -822,6 +851,7 @@ mod tests {
result.give()
},
|| async { Ok(()) },
|_| async { Ok(Amount::from_sat(1000)) },
)
.await
.unwrap();
@ -832,8 +862,7 @@ mod tests {
assert_eq!((amount, fees), (expected_amount, expected_fees));
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC
"
" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC\n"
);
}
@ -855,6 +884,7 @@ mod tests {
result.give()
},
|| async { Ok(()) },
|_| async { Ok(Amount::from_sat(1000)) },
)
.await
.unwrap();
@ -865,8 +895,7 @@ mod tests {
assert_eq!((amount, fees), (expected_amount, expected_fees));
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC
"
" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC\n"
);
}
@ -888,6 +917,7 @@ mod tests {
result.give()
},
|| async { Ok(()) },
|_| async { Ok(Amount::from_sat(1000)) },
)
.await
.unwrap();
@ -899,7 +929,8 @@ mod tests {
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00000000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Deposit at least 0.01001000 BTC to cover the min quantity with fee!
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.01001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC
"
);
@ -923,6 +954,7 @@ mod tests {
result.give()
},
|| async { Ok(()) },
|_| async { Ok(Amount::from_sat(1000)) },
)
.await
.unwrap();
@ -934,7 +966,8 @@ mod tests {
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00010000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Deposit at least 0.00991000 BTC to cover the min quantity with fee!
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00991000 BTC max_giveable=0.00010000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC
"
);
@ -963,6 +996,7 @@ mod tests {
result.give()
},
|| async { Ok(()) },
|_| async { Ok(Amount::from_sat(1000)) },
),
)
.await
@ -972,10 +1006,12 @@ mod tests {
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Deposit at least 0.10001000 BTC to cover the min quantity with fee!
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.10001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC
INFO swap: Deposited amount is less than `min_quantity`
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.01000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Deposit at least 0.09001000 BTC to cover the min quantity with fee!
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.09001000 BTC max_giveable=0.01000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC
"
);
}
@ -1009,6 +1045,7 @@ mod tests {
result.give()
},
|| async { Ok(()) },
|_| async { Ok(Amount::from_sat(1000)) },
),
)
.await
@ -1018,14 +1055,15 @@ mod tests {
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Deposit at least 0.10001000 BTC to cover the min quantity with fee!
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.10001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Received Bitcoin new_balance=0.21000000 BTC max_giveable=0.20000000 BTC
"
);
}
#[tokio::test]
async fn given_bid_quote_max_amount_0_return_errorq() {
async fn given_bid_quote_max_amount_0_return_error() {
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
Amount::from_btc(0.0001).unwrap(),
Amount::from_btc(0.01).unwrap(),
@ -1041,6 +1079,7 @@ mod tests {
result.give()
},
|| async { Ok(()) },
|_| async { Ok(Amount::from_sat(1000)) },
)
.await
.err()

View file

@ -11,6 +11,7 @@ use bitcoin::Script;
use serde::{Deserialize, Serialize};
const SCRIPT_SIZE: usize = 34;
const TX_LOCK_WEIGHT: usize = 485;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TxLock {
@ -161,6 +162,10 @@ impl TxLock {
output: vec![tx_out],
}
}
pub fn weight() -> usize {
TX_LOCK_WEIGHT
}
}
impl From<TxLock> for PartiallySignedTransaction {

View file

@ -1,13 +1,15 @@
use crate::network::quote::BidQuote;
use crate::network::rendezvous::XmrBtcNamespace;
use crate::network::swap_setup::bob;
use crate::network::{encrypted_signature, quote, redial, transfer_proof};
use crate::protocol::bob::State2;
use crate::{bitcoin, env};
use anyhow::{anyhow, Error, Result};
use libp2p::core::Multiaddr;
use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent};
use libp2p::ping::{Ping, PingConfig, PingEvent};
use libp2p::request_response::{RequestId, ResponseChannel};
use libp2p::{NetworkBehaviour, PeerId};
use libp2p::{identity, NetworkBehaviour, PeerId};
use std::sync::Arc;
use std::time::Duration;
@ -64,6 +66,7 @@ pub struct Behaviour {
pub transfer_proof: transfer_proof::Behaviour,
pub encrypted_signature: encrypted_signature::Behaviour,
pub redial: redial::Behaviour,
pub identify: Identify,
/// Ping behaviour that ensures that the underlying network connection is
/// still alive. If the ping fails a connection close event will be
@ -76,7 +79,13 @@ impl Behaviour {
alice: PeerId,
env_config: env::Config,
bitcoin_wallet: Arc<bitcoin::Wallet>,
identify_params: (identity::Keypair, XmrBtcNamespace),
) -> Self {
let agentVersion = format!("cli/{} ({})", env!("CARGO_PKG_VERSION"), identify_params.1);
let protocolVersion = "/comit/xmr/btc/1.0.0".to_string();
let identifyConfig = IdentifyConfig::new(protocolVersion, identify_params.0.public())
.with_agent_version(agentVersion);
Self {
quote: quote::cli(),
swap_setup: bob::Behaviour::new(env_config, bitcoin_wallet),
@ -84,6 +93,7 @@ impl Behaviour {
encrypted_signature: encrypted_signature::bob(),
redial: redial::Behaviour::new(alice, Duration::from_secs(2)),
ping: Ping::new(PingConfig::new().with_keep_alive(true)),
identify: Identify::new(identifyConfig),
}
}
@ -100,3 +110,9 @@ impl From<PingEvent> for OutEvent {
OutEvent::Other
}
}
impl From<IdentifyEvent> for OutEvent {
fn from(_: IdentifyEvent) -> Self {
OutEvent::Other
}
}

View file

@ -99,6 +99,7 @@ where
monero_receive_address,
monero_daemon_address,
tor_socks5_port,
namespace: XmrBtcNamespace::from_is_testnet(is_testnet),
},
}
}
@ -179,6 +180,7 @@ where
bitcoin_target_block,
monero_daemon_address,
tor_socks5_port,
namespace: XmrBtcNamespace::from_is_testnet(is_testnet),
},
}
}
@ -230,8 +232,8 @@ where
data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::ListSellers {
rendezvous_point,
namespace: rendezvous_namespace_from(is_testnet),
tor_socks5_port,
namespace: XmrBtcNamespace::from_is_testnet(is_testnet),
},
},
RawCommand::ExportBitcoinWallet { bitcoin } => {
@ -273,6 +275,7 @@ pub enum Command {
monero_receive_address: monero::Address,
monero_daemon_address: String,
tor_socks5_port: u16,
namespace: XmrBtcNamespace,
},
History,
Config,
@ -292,6 +295,7 @@ pub enum Command {
bitcoin_target_block: usize,
monero_daemon_address: String,
tor_socks5_port: u16,
namespace: XmrBtcNamespace,
},
Cancel {
swap_id: Uuid,
@ -562,14 +566,6 @@ mod data {
}
}
fn rendezvous_namespace_from(is_testnet: bool) -> XmrBtcNamespace {
if is_testnet {
XmrBtcNamespace::Testnet
} else {
XmrBtcNamespace::Mainnet
}
}
fn env_config_from(testnet: bool) -> env::Config {
if testnet {
env::Testnet::get_config()
@ -1212,6 +1208,7 @@ mod tests {
.unwrap(),
monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string(),
tor_socks5_port: DEFAULT_SOCKS5_PORT,
namespace: XmrBtcNamespace::Testnet,
},
}
}
@ -1231,6 +1228,7 @@ mod tests {
.unwrap(),
monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS.to_string(),
tor_socks5_port: DEFAULT_SOCKS5_PORT,
namespace: XmrBtcNamespace::Mainnet,
},
}
}
@ -1248,6 +1246,7 @@ mod tests {
bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET,
monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string(),
tor_socks5_port: DEFAULT_SOCKS5_PORT,
namespace: XmrBtcNamespace::Testnet,
},
}
}
@ -1264,6 +1263,7 @@ mod tests {
bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET,
monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS.to_string(),
tor_socks5_port: DEFAULT_SOCKS5_PORT,
namespace: XmrBtcNamespace::Mainnet,
},
}
}

View file

@ -1,10 +1,11 @@
use anyhow::Result;
use std::option::Option::Some;
use std::path::Path;
use time::format_description::well_known::Rfc3339;
use tracing::subscriber::set_global_default;
use tracing::{Event, Level, Subscriber};
use tracing_subscriber::fmt::format::{DefaultFields, Format, JsonFields};
use tracing_subscriber::fmt::time::ChronoLocal;
use tracing_subscriber::fmt::time::UtcTime;
use tracing_subscriber::layer::{Context, SubscriberExt};
use tracing_subscriber::{fmt, EnvFilter, FmtSubscriber, Layer, Registry};
use uuid::Uuid;
@ -15,7 +16,8 @@ pub fn init(debug: bool, json: bool, dir: impl AsRef<Path>, swap_id: Option<Uuid
let registry = Registry::default().with(level_filter);
let appender = tracing_appender::rolling::never(dir, format!("swap-{}.log", swap_id));
let appender =
tracing_appender::rolling::never(dir.as_ref(), format!("swap-{}.log", swap_id));
let (appender, guard) = tracing_appender::non_blocking(appender);
std::mem::forget(guard);
@ -37,8 +39,6 @@ pub fn init(debug: bool, json: bool, dir: impl AsRef<Path>, swap_id: Option<Uuid
} else {
set_global_default(file_logger.with(info_terminal_printer()))?;
}
Ok(())
} else {
let level = if debug { Level::DEBUG } else { Level::INFO };
let is_terminal = atty::is(atty::Stream::Stderr);
@ -47,7 +47,7 @@ pub fn init(debug: bool, json: bool, dir: impl AsRef<Path>, swap_id: Option<Uuid
.with_env_filter(format!("swap={}", level))
.with_writer(std::io::stderr)
.with_ansi(is_terminal)
.with_timer(ChronoLocal::with_format("%F %T".to_owned()))
.with_timer(UtcTime::rfc_3339())
.with_target(false);
if json {
@ -55,9 +55,10 @@ pub fn init(debug: bool, json: bool, dir: impl AsRef<Path>, swap_id: Option<Uuid
} else {
builder.init();
}
};
Ok(())
}
tracing::info!("Logging initialized to {}", dir.as_ref().display());
Ok(())
}
pub struct StdErrPrinter<L> {
@ -79,25 +80,25 @@ type StdErrJsonLayer<S, T> = tracing_subscriber::fmt::Layer<
fn() -> std::io::Stderr,
>;
fn debug_terminal_printer<S>() -> StdErrPrinter<StdErrLayer<S, ChronoLocal>> {
fn debug_terminal_printer<S>() -> StdErrPrinter<StdErrLayer<S, UtcTime<Rfc3339>>> {
let is_terminal = atty::is(atty::Stream::Stderr);
StdErrPrinter {
inner: fmt::layer()
.with_ansi(is_terminal)
.with_target(false)
.with_timer(ChronoLocal::with_format("%F %T".to_owned()))
.with_timer(UtcTime::rfc_3339())
.with_writer(std::io::stderr),
level: Level::DEBUG,
}
}
fn debug_json_terminal_printer<S>() -> StdErrPrinter<StdErrJsonLayer<S, ChronoLocal>> {
fn debug_json_terminal_printer<S>() -> StdErrPrinter<StdErrJsonLayer<S, UtcTime<Rfc3339>>> {
let is_terminal = atty::is(atty::Stream::Stderr);
StdErrPrinter {
inner: fmt::layer()
.with_ansi(is_terminal)
.with_target(false)
.with_timer(ChronoLocal::with_format("%F %T".to_owned()))
.with_timer(UtcTime::rfc_3339())
.json()
.with_writer(std::io::stderr),
level: Level::DEBUG,

View file

@ -81,8 +81,8 @@ pub struct PublicViewKey(PublicKey);
#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, PartialOrd)]
pub struct Amount(u64);
// Median tx fees on Monero as found here: https://www.monero.how/monero-transaction-fees, XMR 0.000_015 * 2 (to be on the safe side)
pub const MONERO_FEE: Amount = Amount::from_piconero(30000000);
// Median tx fees on Monero as found here: https://www.monero.how/monero-transaction-fees, XMR 0.000_008 * 2 (to be on the safe side)
pub const MONERO_FEE: Amount = Amount::from_piconero(16_000_000);
impl Amount {
pub const ZERO: Self = Self(0);
@ -95,22 +95,30 @@ impl Amount {
Amount(amount)
}
/// Return Monero Amount as Piconero.
pub fn as_piconero(&self) -> u64 {
self.0
}
pub fn max_bitcoin_for_price(&self, ask_price: bitcoin::Amount) -> bitcoin::Amount {
let piconero_minus_fee = self.as_piconero().saturating_sub(MONERO_FEE.as_piconero());
/// Calculate the maximum amount of Bitcoin that can be bought at a given
/// asking price for this amount of Monero including the median fee.
pub fn max_bitcoin_for_price(&self, ask_price: bitcoin::Amount) -> Option<bitcoin::Amount> {
let pico_minus_fee = self.as_piconero().saturating_sub(MONERO_FEE.as_piconero());
if piconero_minus_fee == 0 {
return bitcoin::Amount::ZERO;
if pico_minus_fee == 0 {
return Some(bitcoin::Amount::ZERO);
}
// There needs to be an offset for difference in zeroes beetween Piconeros and
// Satoshis
let piconero_calc = (piconero_minus_fee * ask_price.as_sat()) / PICONERO_OFFSET;
// safely convert the BTC/XMR rate to sat/pico
let ask_sats = Decimal::from(ask_price.as_sat());
let pico_per_xmr = Decimal::from(PICONERO_OFFSET);
let ask_sats_per_pico = ask_sats / pico_per_xmr;
bitcoin::Amount::from_sat(piconero_calc)
let pico = Decimal::from(pico_minus_fee);
let max_sats = pico.checked_mul(ask_sats_per_pico)?;
let satoshi = max_sats.to_u64()?;
Some(bitcoin::Amount::from_sat(satoshi))
}
pub fn from_monero(amount: f64) -> Result<Self> {
@ -375,27 +383,88 @@ mod tests {
}
#[test]
fn geting_max_bitcoin_to_trade() {
let amount = Amount::parse_monero("10").unwrap();
let bitcoin_price_sats = bitcoin::Amount::from_sat(382_900);
fn max_bitcoin_to_trade() {
// sanity check: if the asking price is 1 BTC / 1 XMR
// and we have μ XMR + fee
// then max BTC we can buy is μ
let ask = bitcoin::Amount::from_btc(1.0).unwrap();
let monero_max_from_bitcoin = amount.max_bitcoin_for_price(bitcoin_price_sats);
let xmr = Amount::parse_monero("1.0").unwrap() + MONERO_FEE;
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
assert_eq!(
bitcoin::Amount::from_sat(3_828_988),
monero_max_from_bitcoin
);
assert_eq!(btc, bitcoin::Amount::from_btc(1.0).unwrap());
let xmr = Amount::parse_monero("0.5").unwrap() + MONERO_FEE;
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
assert_eq!(btc, bitcoin::Amount::from_btc(0.5).unwrap());
let xmr = Amount::parse_monero("2.5").unwrap() + MONERO_FEE;
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
assert_eq!(btc, bitcoin::Amount::from_btc(2.5).unwrap());
let xmr = Amount::parse_monero("420").unwrap() + MONERO_FEE;
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
assert_eq!(btc, bitcoin::Amount::from_btc(420.0).unwrap());
let xmr = Amount::parse_monero("0.00001").unwrap() + MONERO_FEE;
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
assert_eq!(btc, bitcoin::Amount::from_btc(0.00001).unwrap());
// other ask prices
let ask = bitcoin::Amount::from_btc(0.5).unwrap();
let xmr = Amount::parse_monero("2").unwrap() + MONERO_FEE;
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
assert_eq!(btc, bitcoin::Amount::from_btc(1.0).unwrap());
let ask = bitcoin::Amount::from_btc(2.0).unwrap();
let xmr = Amount::parse_monero("1").unwrap() + MONERO_FEE;
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
assert_eq!(btc, bitcoin::Amount::from_btc(2.0).unwrap());
let ask = bitcoin::Amount::from_sat(382_900);
let xmr = Amount::parse_monero("10").unwrap();
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
assert_eq!(btc, bitcoin::Amount::from_sat(3_828_993));
// example from https://github.com/comit-network/xmr-btc-swap/issues/1084
// with rate from kraken at that time
let ask = bitcoin::Amount::from_sat(685_800);
let xmr = Amount::parse_monero("0.826286435921").unwrap();
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
assert_eq!(btc, bitcoin::Amount::from_sat(566_656));
}
#[test]
fn max_bitcoin_to_trade_overflow() {
let xmr = Amount::from_monero(30.0).unwrap();
let ask = bitcoin::Amount::from_sat(728_688);
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
assert_eq!(bitcoin::Amount::from_sat(21_860_628), btc);
let xmr = Amount::from_piconero(u64::MAX);
let ask = bitcoin::Amount::from_sat(u64::MAX);
let btc = xmr.max_bitcoin_for_price(ask);
assert!(btc.is_none());
}
#[test]
fn geting_max_bitcoin_to_trade_with_balance_smaller_than_locking_fee() {
let monero = "0.00001";
let amount = Amount::parse_monero(monero).unwrap();
let bitcoin_price_sats = bitcoin::Amount::from_sat(382_900);
let ask = bitcoin::Amount::from_sat(382_900);
let xmr = Amount::parse_monero("0.00001").unwrap();
let btc = xmr.max_bitcoin_for_price(ask).unwrap();
let monero_max_from_bitcoin = amount.max_bitcoin_for_price(bitcoin_price_sats);
assert_eq!(bitcoin::Amount::ZERO, monero_max_from_bitcoin);
assert_eq!(bitcoin::Amount::ZERO, btc);
}
use rand::rngs::OsRng;

View file

@ -314,8 +314,13 @@ async fn wait_for_confirmations<C: monero_rpc::wallet::MoneroWalletRpc<reqwest::
.await
{
Ok(proof) => proof,
Err(jsonrpc::Error::JsonRpc(jsonrpc::JsonRpcError { code: -1, .. })) => {
tracing::warn!(%txid, "`monero-wallet-rpc` failed to fetch transaction, may need to be restarted");
Err(jsonrpc::Error::JsonRpc(jsonrpc::JsonRpcError {
code: -1,
message,
data,
})) => {
tracing::debug!(message, ?data);
tracing::warn!(%txid, message, "`monero-wallet-rpc` failed to fetch transaction, may need to be restarted");
continue;
}
// TODO: Implement this using a generic proxy for each function call once https://github.com/thomaseizinger/rust-jsonrpc-client/issues/47 is fixed.

View file

@ -18,17 +18,17 @@ use tokio_util::io::StreamReader;
compile_error!("unsupported operating system");
#[cfg(target_os = "macos")]
const DOWNLOAD_URL: &str = "http://downloads.getmonero.org/cli/monero-mac-x64-v0.17.2.0.tar.bz2";
const DOWNLOAD_URL: &str = "http://downloads.getmonero.org/cli/monero-mac-x64-v0.17.3.0.tar.bz2";
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-linux-x64-v0.17.2.0.tar.bz2";
const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-linux-x64-v0.17.3.0.tar.bz2";
#[cfg(all(target_os = "linux", target_arch = "arm"))]
const DOWNLOAD_URL: &str =
"https://downloads.getmonero.org/cli/monero-linux-armv7-v0.17.2.0.tar.bz2";
"https://downloads.getmonero.org/cli/monero-linux-armv7-v0.17.3.0.tar.bz2";
#[cfg(target_os = "windows")]
const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-win-x64-v0.17.2.0.zip";
const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-win-x64-v0.17.3.0.zip";
#[cfg(any(target_os = "macos", target_os = "linux"))]
const PACKED_FILE: &str = "monero-wallet-rpc";

View file

@ -27,3 +27,13 @@ impl From<XmrBtcNamespace> for Namespace {
}
}
}
impl XmrBtcNamespace {
pub fn from_is_testnet(testnet: bool) -> XmrBtcNamespace {
if testnet {
XmrBtcNamespace::Testnet
} else {
XmrBtcNamespace::Mainnet
}
}
}

View file

@ -16,14 +16,15 @@ pub fn asb<LR>(
latest_rate: LR,
resume_only: bool,
env_config: env::Config,
rendezvous_params: Option<(Multiaddr, XmrBtcNamespace)>,
namespace: XmrBtcNamespace,
rendezvous_point: Option<Multiaddr>,
) -> Result<Swarm<asb::Behaviour<LR>>>
where
LR: LatestRate + Send + 'static + Debug + Clone,
{
let identity = seed.derive_libp2p_identity();
let rendezvous_params = if let Some((address, namespace)) = rendezvous_params {
let rendezvous_params = if let Some(address) = rendezvous_point {
let peer_id = address
.extract_peer_id()
.context("Rendezvous node address must contain peer ID")?;
@ -39,6 +40,7 @@ where
latest_rate,
resume_only,
env_config,
(identity.clone(), namespace),
rendezvous_params,
);

View file

@ -41,7 +41,7 @@ impl MakeCapturingWriter {
}
}
impl MakeWriter for MakeCapturingWriter {
impl<'a> MakeWriter<'a> for MakeCapturingWriter {
type Writer = CapturingWriter;
fn make_writer(&self) -> Self::Writer {