mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-09-19 12:24:37 -04:00
Merge branch 'master' into update-monero-17.3.0
This commit is contained in:
commit
cf248ae1e5
41 changed files with 572 additions and 331 deletions
|
@ -2,7 +2,7 @@
|
|||
name = "swap"
|
||||
version = "0.10.2"
|
||||
authors = [ "The COMIT guys <hello@comit.network>" ]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
description = "XMR/BTC trustless atomic swaps."
|
||||
|
||||
[lib]
|
||||
|
@ -13,18 +13,18 @@ anyhow = "1"
|
|||
async-compression = { version = "0.3", features = [ "bzip2", "tokio" ] }
|
||||
async-trait = "0.1"
|
||||
atty = "0.2"
|
||||
backoff = { version = "0.3", features = [ "tokio" ] }
|
||||
backoff = { version = "0.4", features = [ "tokio" ] }
|
||||
base64 = "0.13"
|
||||
bdk = "0.12"
|
||||
bdk = "0.16"
|
||||
big-bytes = "1"
|
||||
bitcoin = { version = "0.27", features = [ "rand", "use-serde" ] }
|
||||
bmrng = "0.5"
|
||||
comfy-table = "4.1.1"
|
||||
comfy-table = "5.0"
|
||||
config = { version = "0.11", default-features = false, features = [ "toml" ] }
|
||||
conquer-once = "0.3"
|
||||
curve25519-dalek = { package = "curve25519-dalek-ng", version = "4" }
|
||||
data-encoding = "2.3"
|
||||
dialoguer = "0.8"
|
||||
dialoguer = "0.10"
|
||||
directories-next = "2"
|
||||
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "libsecp_compat", "serde" ] }
|
||||
ed25519-dalek = "1"
|
||||
|
@ -50,13 +50,13 @@ sha2 = "0.9"
|
|||
sigma_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "ed25519", "serde" ] }
|
||||
sqlx = { version = "0.5", features = [ "sqlite", "runtime-tokio-rustls", "offline" ] }
|
||||
structopt = "0.3"
|
||||
strum = { version = "0.23", features = [ "derive" ] }
|
||||
strum = { version = "0.24", features = [ "derive" ] }
|
||||
thiserror = "1"
|
||||
time = "0.3"
|
||||
tokio = { version = "1", features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs", "net" ] }
|
||||
tokio-socks = "0.5"
|
||||
tokio-tungstenite = { version = "0.15", features = [ "rustls-tls" ] }
|
||||
tokio-util = { version = "0.6", features = [ "io" ] }
|
||||
tokio-util = { version = "0.7", features = [ "io", "codec" ] }
|
||||
toml = "0.5"
|
||||
torut = { version = "0.2", default-features = false, features = [ "v3", "control" ] }
|
||||
tracing = { version = "0.1", features = [ "attributes" ] }
|
||||
|
|
|
@ -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.")]
|
||||
|
|
|
@ -319,13 +319,46 @@ where
|
|||
min_buy: bitcoin::Amount,
|
||||
max_buy: bitcoin::Amount,
|
||||
) -> Result<BidQuote> {
|
||||
let rate = self
|
||||
let ask_price = self
|
||||
.latest_rate
|
||||
.latest_rate()
|
||||
.context("Failed to get latest rate")?;
|
||||
.context("Failed to get latest rate")?
|
||||
.ask()
|
||||
.context("Failed to compute asking price")?;
|
||||
|
||||
let max_bitcoin_for_monero = self
|
||||
.monero_wallet
|
||||
.get_balance()
|
||||
.await?
|
||||
.max_bitcoin_for_price(ask_price);
|
||||
|
||||
if min_buy > max_bitcoin_for_monero {
|
||||
tracing::warn!(
|
||||
"Your Monero balance is too low to initiate a swap, as your minimum swap amount is {}. You could at most swap {}",
|
||||
min_buy, max_bitcoin_for_monero
|
||||
);
|
||||
|
||||
return Ok(BidQuote {
|
||||
price: ask_price,
|
||||
min_quantity: bitcoin::Amount::ZERO,
|
||||
max_quantity: bitcoin::Amount::ZERO,
|
||||
});
|
||||
}
|
||||
|
||||
if max_buy > max_bitcoin_for_monero {
|
||||
tracing::warn!(
|
||||
"Your Monero balance is too low to initiate a swap with the maximum swap amount {} that you have specified in your config. You can at most swap {}",
|
||||
max_buy, max_bitcoin_for_monero
|
||||
);
|
||||
return Ok(BidQuote {
|
||||
price: ask_price,
|
||||
min_quantity: min_buy,
|
||||
max_quantity: max_bitcoin_for_monero,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(BidQuote {
|
||||
price: rate.ask().context("Failed to compute asking price")?,
|
||||
price: ask_price,
|
||||
min_quantity: min_buy,
|
||||
max_quantity: max_buy,
|
||||
})
|
||||
|
|
|
@ -29,7 +29,7 @@ use swap::cli::{list_sellers, EventLoop, SellerStatus};
|
|||
use swap::database::open_db;
|
||||
use swap::env::Config;
|
||||
use swap::libp2p_ext::MultiAddrExt;
|
||||
use swap::network::quote::BidQuote;
|
||||
use swap::network::quote::{BidQuote, ZeroQuoteReceived};
|
||||
use swap::network::swarm;
|
||||
use swap::protocol::bob;
|
||||
use swap::protocol::bob::{BobState, Swap};
|
||||
|
@ -47,7 +47,7 @@ async fn main() -> Result<()> {
|
|||
json,
|
||||
cmd,
|
||||
} = match parse_args_and_apply_defaults(env::args_os())? {
|
||||
ParseResult::Arguments(args) => args,
|
||||
ParseResult::Arguments(args) => *args,
|
||||
ParseResult::PrintAndExitZero { message } => {
|
||||
println!("{}", message);
|
||||
std::process::exit(0);
|
||||
|
@ -95,11 +95,11 @@ async fn main() -> Result<()> {
|
|||
tracing::debug!(peer_id = %swarm.local_peer_id(), "Network layer initialized");
|
||||
|
||||
let (event_loop, mut event_loop_handle) =
|
||||
EventLoop::new(swap_id, swarm, seller_peer_id, env_config)?;
|
||||
EventLoop::new(swap_id, swarm, seller_peer_id)?;
|
||||
let event_loop = tokio::spawn(event_loop.run());
|
||||
|
||||
let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size());
|
||||
let (amount, fees) = determine_btc_to_swap(
|
||||
let (amount, fees) = match determine_btc_to_swap(
|
||||
json,
|
||||
event_loop_handle.request_quote(),
|
||||
bitcoin_wallet.new_address(),
|
||||
|
@ -107,7 +107,16 @@ async fn main() -> Result<()> {
|
|||
max_givable,
|
||||
|| bitcoin_wallet.sync(),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
Ok(val) => val,
|
||||
Err(error) => match error.downcast::<ZeroQuoteReceived>() {
|
||||
Ok(_) => {
|
||||
bail!("Seller's XMR balance is currently too low to initiate a swap, please try again later")
|
||||
}
|
||||
Err(other) => bail!(other),
|
||||
},
|
||||
};
|
||||
|
||||
tracing::info!(%amount, %fees, "Determined swap amount");
|
||||
|
||||
|
@ -267,8 +276,7 @@ async fn main() -> Result<()> {
|
|||
.add_address(seller_peer_id, seller_address);
|
||||
}
|
||||
|
||||
let (event_loop, event_loop_handle) =
|
||||
EventLoop::new(swap_id, swarm, seller_peer_id, env_config)?;
|
||||
let (event_loop, event_loop_handle) = EventLoop::new(swap_id, swarm, seller_peer_id)?;
|
||||
let handle = tokio::spawn(event_loop.run());
|
||||
|
||||
let monero_receive_address = db.get_monero_address(swap_id).await?;
|
||||
|
@ -556,6 +564,11 @@ where
|
|||
{
|
||||
tracing::debug!("Requesting quote");
|
||||
let bid_quote = bid_quote.await?;
|
||||
|
||||
if bid_quote.max_quantity == bitcoin::Amount::ZERO {
|
||||
bail!(ZeroQuoteReceived)
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
price = %bid_quote.price,
|
||||
minimum_amount = %bid_quote.min_quantity,
|
||||
|
@ -915,6 +928,32 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_bid_quote_max_amount_0_return_errorq() {
|
||||
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
|
||||
Amount::from_btc(0.0001).unwrap(),
|
||||
Amount::from_btc(0.01).unwrap(),
|
||||
])));
|
||||
|
||||
let determination_error = determine_btc_to_swap(
|
||||
true,
|
||||
async { Ok(quote_with_max(0.00)) },
|
||||
get_dummy_address(),
|
||||
|| async { Ok(Amount::from_btc(0.0101)?) },
|
||||
|| async {
|
||||
let mut result = givable.lock().unwrap();
|
||||
result.give()
|
||||
},
|
||||
|| async { Ok(()) },
|
||||
)
|
||||
.await
|
||||
.err()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
assert_eq!("Received quote of 0", determination_error);
|
||||
}
|
||||
|
||||
struct MaxGiveable {
|
||||
amounts: Vec<Amount>,
|
||||
call_counter: usize,
|
||||
|
|
|
@ -171,7 +171,7 @@ pub fn verify_sig(
|
|||
) -> Result<()> {
|
||||
let ecdsa = ECDSA::verify_only();
|
||||
|
||||
if ecdsa.verify(&verification_key.0, &transaction_sighash.into_inner(), &sig) {
|
||||
if ecdsa.verify(&verification_key.0, &transaction_sighash.into_inner(), sig) {
|
||||
Ok(())
|
||||
} else {
|
||||
bail!(InvalidSignature)
|
||||
|
@ -194,7 +194,7 @@ pub fn verify_encsig(
|
|||
&verification_key.0,
|
||||
&encryption_key.0,
|
||||
&digest.into_inner(),
|
||||
&encsig,
|
||||
encsig,
|
||||
) {
|
||||
Ok(())
|
||||
} else {
|
||||
|
@ -213,7 +213,7 @@ pub fn build_shared_output_descriptor(A: Point, B: Point) -> Descriptor<bitcoin:
|
|||
let A = ToHex::to_hex(&secp256k1::PublicKey::from(A));
|
||||
let B = ToHex::to_hex(&secp256k1::PublicKey::from(B));
|
||||
|
||||
let miniscript = MINISCRIPT_TEMPLATE.replace("A", &A).replace("B", &B);
|
||||
let miniscript = MINISCRIPT_TEMPLATE.replace('A', &A).replace('B', &B);
|
||||
|
||||
let miniscript =
|
||||
bdk::miniscript::Miniscript::<bitcoin::PublicKey, Segwitv0>::from_str(&miniscript)
|
||||
|
|
|
@ -200,9 +200,10 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn bob_can_fund_without_a_change_output() {
|
||||
let (A, B) = alice_and_bob();
|
||||
let fees = 610;
|
||||
let fees = 300;
|
||||
let agreed_amount = Amount::from_sat(10000);
|
||||
let wallet = WalletBuilder::new(agreed_amount.as_sat() + fees).build();
|
||||
let amount = agreed_amount.as_sat() + fees;
|
||||
let wallet = WalletBuilder::new(amount).build();
|
||||
|
||||
let psbt = bob_make_psbt(A, B, &wallet, agreed_amount).await;
|
||||
assert_eq!(
|
||||
|
@ -262,7 +263,7 @@ mod tests {
|
|||
amount: Amount,
|
||||
) -> PartiallySignedTransaction {
|
||||
let change = wallet.new_address().await.unwrap();
|
||||
TxLock::new(&wallet, amount, A, B, change)
|
||||
TxLock::new(wallet, amount, A, B, change)
|
||||
.await
|
||||
.unwrap()
|
||||
.into()
|
||||
|
|
|
@ -128,7 +128,7 @@ impl TxRedeem {
|
|||
|
||||
let sig = sigs
|
||||
.into_iter()
|
||||
.find(|sig| verify_sig(&B, &self.digest(), &sig).is_ok())
|
||||
.find(|sig| verify_sig(&B, &self.digest(), sig).is_ok())
|
||||
.context("Neither signature on witness stack verifies against B")?;
|
||||
|
||||
Ok(sig)
|
||||
|
|
|
@ -132,7 +132,7 @@ impl TxRefund {
|
|||
|
||||
let sig = sigs
|
||||
.into_iter()
|
||||
.find(|sig| verify_sig(&B, &self.digest(), &sig).is_ok())
|
||||
.find(|sig| verify_sig(&B, &self.digest(), sig).is_ok())
|
||||
.context("Neither signature on witness stack verifies against B")?;
|
||||
|
||||
Ok(sig)
|
||||
|
|
|
@ -102,7 +102,7 @@ impl Wallet {
|
|||
self.wallet
|
||||
.lock()
|
||||
.await
|
||||
.broadcast(transaction)
|
||||
.broadcast(&transaction)
|
||||
.with_context(|| {
|
||||
format!("Failed to broadcast Bitcoin {} transaction {}", kind, txid)
|
||||
})?;
|
||||
|
@ -152,13 +152,13 @@ impl Wallet {
|
|||
ScriptStatus::Retrying
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if new_status != ScriptStatus::Retrying
|
||||
{
|
||||
last_status = Some(print_status_change(txid, last_status, new_status));
|
||||
|
||||
let all_receivers_gone = sender.send(new_status).is_err();
|
||||
|
||||
|
||||
if all_receivers_gone {
|
||||
tracing::debug!(%txid, "All receivers gone, removing subscription");
|
||||
client.lock().await.subscriptions.remove(&(txid, script));
|
||||
|
|
|
@ -15,8 +15,8 @@ use url::Url;
|
|||
use uuid::Uuid;
|
||||
|
||||
// See: https://moneroworld.com/
|
||||
pub const DEFAULT_MONERO_DAEMON_ADDRESS: &str = "node.melo.tools:18081";
|
||||
pub const DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET: &str = "stagenet.melo.tools:38081";
|
||||
pub const DEFAULT_MONERO_DAEMON_ADDRESS: &str = "node.community.rino.io:18081";
|
||||
pub const DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET: &str = "stagenet.community.rino.io:38081";
|
||||
|
||||
// See: https://1209k.com/bitcoin-eye/ele.php?chain=btc
|
||||
const DEFAULT_ELECTRUM_RPC_URL: &str = "ssl://blockstream.info:700";
|
||||
|
@ -41,7 +41,7 @@ pub struct Arguments {
|
|||
#[derive(Debug, PartialEq)]
|
||||
pub enum ParseResult {
|
||||
/// The arguments we were invoked in.
|
||||
Arguments(Arguments),
|
||||
Arguments(Box<Arguments>),
|
||||
/// A flag or command was given that does not need further processing other
|
||||
/// than printing the provided message.
|
||||
///
|
||||
|
@ -260,7 +260,7 @@ where
|
|||
},
|
||||
};
|
||||
|
||||
Ok(ParseResult::Arguments(arguments))
|
||||
Ok(ParseResult::Arguments(Box::new(arguments)))
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
@ -693,7 +693,8 @@ mod tests {
|
|||
MULTI_ADDRESS,
|
||||
];
|
||||
|
||||
let expected_args = ParseResult::Arguments(Arguments::buy_xmr_mainnet_defaults());
|
||||
let expected_args =
|
||||
ParseResult::Arguments(Arguments::buy_xmr_mainnet_defaults().into_boxed());
|
||||
let args = parse_args_and_apply_defaults(raw_ars).unwrap();
|
||||
|
||||
assert_eq!(expected_args, args);
|
||||
|
@ -717,7 +718,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
args,
|
||||
ParseResult::Arguments(Arguments::buy_xmr_testnet_defaults())
|
||||
ParseResult::Arguments(Arguments::buy_xmr_testnet_defaults().into_boxed())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -778,7 +779,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
args,
|
||||
ParseResult::Arguments(Arguments::resume_mainnet_defaults())
|
||||
ParseResult::Arguments(Arguments::resume_mainnet_defaults().into_boxed())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -790,7 +791,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
args,
|
||||
ParseResult::Arguments(Arguments::resume_testnet_defaults())
|
||||
ParseResult::Arguments(Arguments::resume_testnet_defaults().into_boxed())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -802,7 +803,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
args,
|
||||
ParseResult::Arguments(Arguments::cancel_mainnet_defaults())
|
||||
ParseResult::Arguments(Arguments::cancel_mainnet_defaults().into_boxed())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -814,7 +815,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
args,
|
||||
ParseResult::Arguments(Arguments::cancel_testnet_defaults())
|
||||
ParseResult::Arguments(Arguments::cancel_testnet_defaults().into_boxed())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -826,7 +827,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
args,
|
||||
ParseResult::Arguments(Arguments::refund_mainnet_defaults())
|
||||
ParseResult::Arguments(Arguments::refund_mainnet_defaults().into_boxed())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -838,7 +839,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
args,
|
||||
ParseResult::Arguments(Arguments::refund_testnet_defaults())
|
||||
ParseResult::Arguments(Arguments::refund_testnet_defaults().into_boxed())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -866,6 +867,7 @@ mod tests {
|
|||
ParseResult::Arguments(
|
||||
Arguments::buy_xmr_mainnet_defaults()
|
||||
.with_data_dir(PathBuf::from_str(data_dir).unwrap().join("mainnet"))
|
||||
.into_boxed()
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -890,6 +892,7 @@ mod tests {
|
|||
ParseResult::Arguments(
|
||||
Arguments::buy_xmr_testnet_defaults()
|
||||
.with_data_dir(PathBuf::from_str(data_dir).unwrap().join("testnet"))
|
||||
.into_boxed()
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -909,6 +912,7 @@ mod tests {
|
|||
ParseResult::Arguments(
|
||||
Arguments::resume_mainnet_defaults()
|
||||
.with_data_dir(PathBuf::from_str(data_dir).unwrap().join("mainnet"))
|
||||
.into_boxed()
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -929,6 +933,7 @@ mod tests {
|
|||
ParseResult::Arguments(
|
||||
Arguments::resume_testnet_defaults()
|
||||
.with_data_dir(PathBuf::from_str(data_dir).unwrap().join("testnet"))
|
||||
.into_boxed()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -950,7 +955,11 @@ mod tests {
|
|||
let args = parse_args_and_apply_defaults(raw_ars).unwrap();
|
||||
assert_eq!(
|
||||
args,
|
||||
ParseResult::Arguments(Arguments::buy_xmr_mainnet_defaults().with_debug())
|
||||
ParseResult::Arguments(
|
||||
Arguments::buy_xmr_mainnet_defaults()
|
||||
.with_debug()
|
||||
.into_boxed()
|
||||
)
|
||||
);
|
||||
|
||||
let raw_ars = vec![
|
||||
|
@ -969,7 +978,11 @@ mod tests {
|
|||
let args = parse_args_and_apply_defaults(raw_ars).unwrap();
|
||||
assert_eq!(
|
||||
args,
|
||||
ParseResult::Arguments(Arguments::buy_xmr_testnet_defaults().with_debug())
|
||||
ParseResult::Arguments(
|
||||
Arguments::buy_xmr_testnet_defaults()
|
||||
.with_debug()
|
||||
.into_boxed()
|
||||
)
|
||||
);
|
||||
|
||||
let raw_ars = vec![BINARY_NAME, "--debug", "resume", "--swap-id", SWAP_ID];
|
||||
|
@ -977,7 +990,11 @@ mod tests {
|
|||
let args = parse_args_and_apply_defaults(raw_ars).unwrap();
|
||||
assert_eq!(
|
||||
args,
|
||||
ParseResult::Arguments(Arguments::resume_mainnet_defaults().with_debug())
|
||||
ParseResult::Arguments(
|
||||
Arguments::resume_mainnet_defaults()
|
||||
.with_debug()
|
||||
.into_boxed()
|
||||
)
|
||||
);
|
||||
|
||||
let raw_ars = vec![
|
||||
|
@ -992,7 +1009,11 @@ mod tests {
|
|||
let args = parse_args_and_apply_defaults(raw_ars).unwrap();
|
||||
assert_eq!(
|
||||
args,
|
||||
ParseResult::Arguments(Arguments::resume_testnet_defaults().with_debug())
|
||||
ParseResult::Arguments(
|
||||
Arguments::resume_testnet_defaults()
|
||||
.with_debug()
|
||||
.into_boxed()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1013,7 +1034,11 @@ mod tests {
|
|||
let args = parse_args_and_apply_defaults(raw_ars).unwrap();
|
||||
assert_eq!(
|
||||
args,
|
||||
ParseResult::Arguments(Arguments::buy_xmr_mainnet_defaults().with_json())
|
||||
ParseResult::Arguments(
|
||||
Arguments::buy_xmr_mainnet_defaults()
|
||||
.with_json()
|
||||
.into_boxed()
|
||||
)
|
||||
);
|
||||
|
||||
let raw_ars = vec![
|
||||
|
@ -1032,7 +1057,11 @@ mod tests {
|
|||
let args = parse_args_and_apply_defaults(raw_ars).unwrap();
|
||||
assert_eq!(
|
||||
args,
|
||||
ParseResult::Arguments(Arguments::buy_xmr_testnet_defaults().with_json())
|
||||
ParseResult::Arguments(
|
||||
Arguments::buy_xmr_testnet_defaults()
|
||||
.with_json()
|
||||
.into_boxed()
|
||||
)
|
||||
);
|
||||
|
||||
let raw_ars = vec![BINARY_NAME, "--json", "resume", "--swap-id", SWAP_ID];
|
||||
|
@ -1040,7 +1069,11 @@ mod tests {
|
|||
let args = parse_args_and_apply_defaults(raw_ars).unwrap();
|
||||
assert_eq!(
|
||||
args,
|
||||
ParseResult::Arguments(Arguments::resume_mainnet_defaults().with_json())
|
||||
ParseResult::Arguments(
|
||||
Arguments::resume_mainnet_defaults()
|
||||
.with_json()
|
||||
.into_boxed()
|
||||
)
|
||||
);
|
||||
|
||||
let raw_ars = vec![
|
||||
|
@ -1055,7 +1088,11 @@ mod tests {
|
|||
let args = parse_args_and_apply_defaults(raw_ars).unwrap();
|
||||
assert_eq!(
|
||||
args,
|
||||
ParseResult::Arguments(Arguments::resume_testnet_defaults().with_json())
|
||||
ParseResult::Arguments(
|
||||
Arguments::resume_testnet_defaults()
|
||||
.with_json()
|
||||
.into_boxed()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1303,6 +1340,10 @@ mod tests {
|
|||
self.json = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn into_boxed(self) -> Box<Self> {
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn data_dir_path_cli() -> PathBuf {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::bitcoin::EncryptedSignature;
|
||||
use crate::cli::behaviour::{Behaviour, OutEvent};
|
||||
use crate::monero;
|
||||
use crate::network::encrypted_signature;
|
||||
use crate::network::quote::BidQuote;
|
||||
use crate::network::swap_setup::bob::NewSwap;
|
||||
use crate::protocol::bob::State2;
|
||||
use crate::{env, monero};
|
||||
use anyhow::{Context, Result};
|
||||
use futures::future::{BoxFuture, OptionFuture};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
|
@ -50,7 +50,6 @@ impl EventLoop {
|
|||
swap_id: Uuid,
|
||||
swarm: Swarm<Behaviour>,
|
||||
alice_peer_id: PeerId,
|
||||
env_config: env::Config,
|
||||
) -> Result<(Self, EventLoopHandle)> {
|
||||
let execution_setup = bmrng::channel_with_timeout(1, Duration::from_secs(60));
|
||||
let transfer_proof = bmrng::channel_with_timeout(1, Duration::from_secs(60));
|
||||
|
@ -76,7 +75,6 @@ impl EventLoop {
|
|||
transfer_proof: transfer_proof.1,
|
||||
encrypted_signature: encrypted_signature.0,
|
||||
quote: quote.0,
|
||||
env_config,
|
||||
};
|
||||
|
||||
Ok((event_loop, handle))
|
||||
|
@ -220,7 +218,6 @@ pub struct EventLoopHandle {
|
|||
transfer_proof: bmrng::RequestReceiver<monero::TransferProof, ()>,
|
||||
encrypted_signature: bmrng::RequestSender<EncryptedSignature, ()>,
|
||||
quote: bmrng::RequestSender<(), BidQuote>,
|
||||
env_config: env::Config,
|
||||
}
|
||||
|
||||
impl EventLoopHandle {
|
||||
|
|
|
@ -271,7 +271,7 @@ impl EventLoop {
|
|||
QuoteStatus::Received(Status::Online(quote)) => {
|
||||
let address = self
|
||||
.reachable_asb_address
|
||||
.get(&peer_id)
|
||||
.get(peer_id)
|
||||
.expect("if we got a quote we must have stored an address");
|
||||
|
||||
Ok(Seller {
|
||||
|
@ -282,7 +282,7 @@ impl EventLoop {
|
|||
QuoteStatus::Received(Status::Unreachable) => {
|
||||
let address = self
|
||||
.unreachable_asb_address
|
||||
.get(&peer_id)
|
||||
.get(peer_id)
|
||||
.expect("if we got a quote we must have stored an address");
|
||||
|
||||
Ok(Seller {
|
||||
|
|
|
@ -20,27 +20,22 @@ pub fn init(debug: bool, json: bool, dir: impl AsRef<Path>, swap_id: Option<Uuid
|
|||
|
||||
std::mem::forget(guard);
|
||||
|
||||
let file_logger = fmt::layer()
|
||||
.with_ansi(false)
|
||||
.with_target(false)
|
||||
.with_writer(appender);
|
||||
let file_logger = registry.with(
|
||||
fmt::layer()
|
||||
.with_ansi(false)
|
||||
.with_target(false)
|
||||
.json()
|
||||
.with_writer(appender),
|
||||
);
|
||||
|
||||
if json && debug {
|
||||
set_global_default(
|
||||
registry
|
||||
.with(file_logger.json())
|
||||
.with(debug_json_terminal_printer()),
|
||||
)?;
|
||||
set_global_default(file_logger.with(debug_json_terminal_printer()))?;
|
||||
} else if json && !debug {
|
||||
set_global_default(
|
||||
registry
|
||||
.with(file_logger.json())
|
||||
.with(info_json_terminal_printer()),
|
||||
)?;
|
||||
set_global_default(file_logger.with(info_json_terminal_printer()))?;
|
||||
} else if !json && debug {
|
||||
set_global_default(registry.with(file_logger).with(debug_terminal_printer()))?;
|
||||
set_global_default(file_logger.with(debug_terminal_printer()))?;
|
||||
} else {
|
||||
set_global_default(registry.with(file_logger).with(info_terminal_printer()))?;
|
||||
set_global_default(file_logger.with(info_terminal_printer()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -48,7 +48,7 @@ impl GetConfig for Mainnet {
|
|||
Config {
|
||||
bitcoin_lock_mempool_timeout: 3.std_minutes(),
|
||||
bitcoin_lock_confirmed_timeout: 2.std_hours(),
|
||||
bitcoin_finality_confirmations: 2,
|
||||
bitcoin_finality_confirmations: 1,
|
||||
bitcoin_avg_block_time: 10.std_minutes(),
|
||||
bitcoin_cancel_timelock: CancelTimelock::new(72),
|
||||
bitcoin_punish_timelock: PunishTimelock::new(72),
|
||||
|
@ -65,7 +65,7 @@ impl GetConfig for Testnet {
|
|||
Config {
|
||||
bitcoin_lock_mempool_timeout: 3.std_minutes(),
|
||||
bitcoin_lock_confirmed_timeout: 1.std_hours(),
|
||||
bitcoin_finality_confirmations: 2,
|
||||
bitcoin_finality_confirmations: 1,
|
||||
bitcoin_avg_block_time: 10.std_minutes(),
|
||||
bitcoin_cancel_timelock: CancelTimelock::new(12),
|
||||
bitcoin_punish_timelock: PunishTimelock::new(6),
|
||||
|
@ -138,7 +138,7 @@ mod monero_network {
|
|||
Network::Stagenet => "stagenet",
|
||||
Network::Testnet => "testnet",
|
||||
};
|
||||
s.serialize_str(&str)
|
||||
s.serialize_str(str)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ pub fn connect(price_ticker_ws_url: Url) -> Result<PriceUpdates> {
|
|||
}
|
||||
}
|
||||
|
||||
Err(backoff::Error::Transient(anyhow!("stream ended")))
|
||||
Err(backoff::Error::transient(anyhow!("stream ended")))
|
||||
}
|
||||
},
|
||||
|error, next: Duration| {
|
||||
|
@ -108,8 +108,8 @@ fn to_backoff(e: connection::Error) -> backoff::Error<anyhow::Error> {
|
|||
|
||||
match e {
|
||||
// Connection closures and websocket errors will be retried
|
||||
connection::Error::ConnectionClosed => Transient(anyhow::Error::from(e)),
|
||||
connection::Error::WebSocket(_) => Transient(anyhow::Error::from(e)),
|
||||
connection::Error::ConnectionClosed => backoff::Error::transient(anyhow::Error::from(e)),
|
||||
connection::Error::WebSocket(_) => backoff::Error::transient(anyhow::Error::from(e)),
|
||||
|
||||
// Failures while parsing a message are permanent because they most likely present a
|
||||
// programmer error
|
||||
|
@ -275,8 +275,6 @@ mod wire {
|
|||
pub struct TickerData {
|
||||
#[serde(rename = "a")]
|
||||
ask: Vec<RateElement>,
|
||||
#[serde(rename = "b")]
|
||||
bid: Vec<RateElement>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
|
@ -99,6 +99,20 @@ impl Amount {
|
|||
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());
|
||||
|
||||
if piconero_minus_fee == 0 {
|
||||
return 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;
|
||||
|
||||
bitcoin::Amount::from_sat(piconero_calc)
|
||||
}
|
||||
|
||||
pub fn from_monero(amount: f64) -> Result<Self> {
|
||||
let decimal = Decimal::try_from(amount)?;
|
||||
Self::from_decimal(decimal)
|
||||
|
@ -360,6 +374,30 @@ 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);
|
||||
|
||||
let monero_max_from_bitcoin = amount.max_bitcoin_for_price(bitcoin_price_sats);
|
||||
|
||||
assert_eq!(
|
||||
bitcoin::Amount::from_sat(3_828_988),
|
||||
monero_max_from_bitcoin
|
||||
);
|
||||
}
|
||||
|
||||
#[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 monero_max_from_bitcoin = amount.max_bitcoin_for_price(bitcoin_price_sats);
|
||||
|
||||
assert_eq!(bitcoin::Amount::ZERO, monero_max_from_bitcoin);
|
||||
}
|
||||
|
||||
use rand::rngs::OsRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
|
@ -37,6 +37,10 @@ pub struct BidQuote {
|
|||
pub max_quantity: bitcoin::Amount,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, thiserror::Error)]
|
||||
#[error("Received quote of 0")]
|
||||
pub struct ZeroQuoteReceived;
|
||||
|
||||
/// Constructs a new instance of the `quote` behaviour to be used by the ASB.
|
||||
///
|
||||
/// The ASB is always listening and only supports inbound connections, i.e.
|
||||
|
|
|
@ -6,7 +6,7 @@ pub mod ecdsa_fun {
|
|||
use ::ecdsa_fun::fun::{Point, Scalar, G};
|
||||
|
||||
pub fn point() -> impl Strategy<Value = Point> {
|
||||
scalar().prop_map(|mut scalar| Point::from_scalar_mul(&G, &mut scalar).mark::<Normal>())
|
||||
scalar().prop_map(|mut scalar| Point::from_scalar_mul(G, &mut scalar).mark::<Normal>())
|
||||
}
|
||||
|
||||
pub fn scalar() -> impl Strategy<Value = Scalar> {
|
||||
|
|
|
@ -659,8 +659,7 @@ impl State6 {
|
|||
|
||||
pub async fn publish_refund_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<()> {
|
||||
let signed_tx_refund = self.signed_refund_transaction()?;
|
||||
let (_, subscription) = bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?;
|
||||
subscription.wait_until_final().await?;
|
||||
bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -278,9 +278,12 @@ async fn next_state(
|
|||
state.publish_refund_btc(bitcoin_wallet).await?;
|
||||
BobState::BtcRefunded(state)
|
||||
}
|
||||
ExpiredTimelocks::Punish => BobState::BtcPunished {
|
||||
tx_lock_id: state.tx_lock_id(),
|
||||
},
|
||||
ExpiredTimelocks::Punish => {
|
||||
tracing::info!("You have been punished for not refunding in time");
|
||||
BobState::BtcPunished {
|
||||
tx_lock_id: state.tx_lock_id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BobState::BtcRefunded(state4) => BobState::BtcRefunded(state4),
|
||||
|
|
|
@ -29,7 +29,7 @@ impl Image for Bitcoind {
|
|||
container
|
||||
.logs()
|
||||
.stdout
|
||||
.wait_for_message(&"init message: Done loading")
|
||||
.wait_for_message("init message: Done loading")
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ pub struct Electrs {
|
|||
entrypoint: Option<String>,
|
||||
wait_for_message: String,
|
||||
volume: String,
|
||||
bitcoind_container_name: String,
|
||||
}
|
||||
|
||||
impl Image for Electrs {
|
||||
|
@ -73,7 +72,6 @@ impl Default for Electrs {
|
|||
entrypoint: Some("/build/electrs".into()),
|
||||
wait_for_message: "Running accept thread".to_string(),
|
||||
volume: uuid::Uuid::new_v4().to_string(),
|
||||
bitcoind_container_name: uuid::Uuid::new_v4().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,14 +146,14 @@ async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) {
|
|||
let prefix = random_prefix();
|
||||
let bitcoind_name = format!("{}_{}", prefix, "bitcoind");
|
||||
let (bitcoind, bitcoind_url) =
|
||||
init_bitcoind_container(&cli, prefix.clone(), bitcoind_name.clone(), prefix.clone())
|
||||
init_bitcoind_container(cli, prefix.clone(), bitcoind_name.clone(), prefix.clone())
|
||||
.await
|
||||
.expect("could not init bitcoind");
|
||||
let electrs = init_electrs_container(&cli, prefix.clone(), bitcoind_name, prefix)
|
||||
let electrs = init_electrs_container(cli, prefix.clone(), bitcoind_name, prefix)
|
||||
.await
|
||||
.expect("could not init electrs");
|
||||
let (monero, monerod_container, monero_wallet_rpc_containers) =
|
||||
Monero::new(&cli, vec![MONERO_WALLET_NAME_ALICE, MONERO_WALLET_NAME_BOB])
|
||||
Monero::new(cli, vec![MONERO_WALLET_NAME_ALICE, MONERO_WALLET_NAME_BOB])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -237,7 +237,7 @@ async fn start_alice(
|
|||
let resume_only = false;
|
||||
|
||||
let mut swarm = swarm::asb(
|
||||
&seed,
|
||||
seed,
|
||||
min_buy,
|
||||
max_buy,
|
||||
latest_rate,
|
||||
|
@ -485,7 +485,7 @@ impl BobParams {
|
|||
.behaviour_mut()
|
||||
.add_address(self.alice_peer_id, self.alice_address.clone());
|
||||
|
||||
cli::EventLoop::new(swap_id, swarm, self.alice_peer_id, self.env_config)
|
||||
cli::EventLoop::new(swap_id, swarm, self.alice_peer_id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue