mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
Merge #490
490: Mainnet switch r=da-kami a=da-kami Fixes #446 Fixes #360 Fixes #506 Fixes #478 To be precise: It is actually a testnet switch, because I think mainnet should be default. I took several assumptions on the way (e.g. network support, ...). At this stage any feedback welcome :) TODO: - [ ] successful mainnet swap with this code base before merging :) Co-authored-by: Daniel Karzel <daniel@comit.network>
This commit is contained in:
commit
bdb88f89cb
@ -43,11 +43,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
1. Balance of ASB too low
|
||||
2. Buy amount sent by CLI exceeds maximum buy amount accepted by ASB
|
||||
3. ASB is running in resume-only mode and does not accept incoming swap requests
|
||||
- An issue where the monero daemon port used by the `monero-wallet-rpc` could not be specified.
|
||||
The CLI parameter `--monero-daemon-host` was changed to `--monero-daemon-address` where host and port have to be specified.
|
||||
|
||||
### Changed
|
||||
|
||||
- The ASB's `--max-buy` and `ask-spread` parameter were removed in favour of entries in the config file.
|
||||
The initial setup includes setting these two values now.
|
||||
- From this version on the CLI and ASB run on **mainnet** by default!
|
||||
When running either application with `--testnet` Monero network defaults to `stagenet` and Bitcoin network to `testnet3`.
|
||||
This is a breaking change.
|
||||
It is recommended to run the applications with `--testnet` first and not just run the application on `mainnet` without experience.
|
||||
|
||||
## [0.5.0] - 2021-04-17
|
||||
|
||||
|
@ -10,6 +10,9 @@ use uuid::Uuid;
|
||||
author
|
||||
)]
|
||||
pub struct Arguments {
|
||||
#[structopt(long, help = "Swap on testnet")]
|
||||
pub testnet: bool,
|
||||
|
||||
#[structopt(
|
||||
short,
|
||||
long = "json",
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::env::{Mainnet, Testnet};
|
||||
use crate::fs::{ensure_directory_exists, system_config_dir, system_data_dir};
|
||||
use crate::tor::{DEFAULT_CONTROL_PORT, DEFAULT_SOCKS5_PORT};
|
||||
use anyhow::{bail, Context, Result};
|
||||
@ -11,14 +12,71 @@ use serde::{Deserialize, Serialize};
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use tracing::info;
|
||||
use url::Url;
|
||||
|
||||
const DEFAULT_LISTEN_ADDRESS_TCP: &str = "/ip4/0.0.0.0/tcp/9939";
|
||||
const DEFAULT_LISTEN_ADDRESS_WS: &str = "/ip4/0.0.0.0/tcp/9940/ws";
|
||||
const DEFAULT_ELECTRUM_RPC_URL: &str = "ssl://electrum.blockstream.info:60002";
|
||||
const DEFAULT_MONERO_WALLET_RPC_TESTNET_URL: &str = "http://127.0.0.1:38083/json_rpc";
|
||||
const DEFAULT_BITCOIN_CONFIRMATION_TARGET: usize = 3;
|
||||
pub trait GetDefaults {
|
||||
fn getConfigFileDefaults() -> Result<Defaults>;
|
||||
}
|
||||
|
||||
pub struct Defaults {
|
||||
pub config_path: PathBuf,
|
||||
data_dir: PathBuf,
|
||||
listen_address_tcp: Multiaddr,
|
||||
listen_address_ws: Multiaddr,
|
||||
electrum_rpc_url: Url,
|
||||
monero_wallet_rpc_url: Url,
|
||||
bitcoin_confirmation_target: usize,
|
||||
}
|
||||
|
||||
impl GetDefaults for Testnet {
|
||||
fn getConfigFileDefaults() -> Result<Defaults> {
|
||||
let defaults = Defaults {
|
||||
config_path: default_asb_config_dir()?
|
||||
.join("testnet")
|
||||
.join("config.toml"),
|
||||
data_dir: default_asb_data_dir()?.join("testnet"),
|
||||
listen_address_tcp: Multiaddr::from_str("/ip4/0.0.0.0/tcp/9939")?,
|
||||
listen_address_ws: Multiaddr::from_str("/ip4/0.0.0.0/tcp/9940/ws")?,
|
||||
electrum_rpc_url: Url::parse("ssl://electrum.blockstream.info:60002")?,
|
||||
monero_wallet_rpc_url: Url::parse("http://127.0.0.1:38083/json_rpc")?,
|
||||
bitcoin_confirmation_target: 1,
|
||||
};
|
||||
|
||||
Ok(defaults)
|
||||
}
|
||||
}
|
||||
|
||||
impl GetDefaults for Mainnet {
|
||||
fn getConfigFileDefaults() -> Result<Defaults> {
|
||||
let defaults = Defaults {
|
||||
config_path: default_asb_config_dir()?
|
||||
.join("mainnet")
|
||||
.join("config.toml"),
|
||||
data_dir: default_asb_data_dir()?.join("mainnet"),
|
||||
listen_address_tcp: Multiaddr::from_str("/ip4/0.0.0.0/tcp/9939")?,
|
||||
listen_address_ws: Multiaddr::from_str("/ip4/0.0.0.0/tcp/9940/ws")?,
|
||||
electrum_rpc_url: Url::parse("ssl://electrum.blockstream.info:50002")?,
|
||||
monero_wallet_rpc_url: Url::parse("http://127.0.0.1:18083/json_rpc")?,
|
||||
bitcoin_confirmation_target: 3,
|
||||
};
|
||||
|
||||
Ok(defaults)
|
||||
}
|
||||
}
|
||||
|
||||
fn default_asb_config_dir() -> Result<PathBuf> {
|
||||
system_config_dir()
|
||||
.map(|dir| Path::join(&dir, "asb"))
|
||||
.context("Could not generate default config file path")
|
||||
}
|
||||
|
||||
fn default_asb_data_dir() -> Result<PathBuf> {
|
||||
system_data_dir()
|
||||
.map(|dir| Path::join(&dir, "asb"))
|
||||
.context("Could not generate default config file path")
|
||||
}
|
||||
|
||||
const DEFAULT_MIN_BUY_AMOUNT: f64 = 0.002f64;
|
||||
const DEFAULT_MAX_BUY_AMOUNT: f64 = 0.02f64;
|
||||
@ -64,12 +122,18 @@ pub struct Network {
|
||||
pub struct Bitcoin {
|
||||
pub electrum_rpc_url: Url,
|
||||
pub target_block: usize,
|
||||
pub finality_confirmations: Option<u32>,
|
||||
#[serde(with = "crate::bitcoin::network")]
|
||||
pub network: bitcoin::Network,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Monero {
|
||||
pub wallet_rpc_url: Url,
|
||||
pub finality_confirmations: Option<u64>,
|
||||
#[serde(with = "crate::monero::network")]
|
||||
pub network: monero::Network,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
@ -118,31 +182,8 @@ pub fn read_config(config_path: PathBuf) -> Result<Result<Config, ConfigNotIniti
|
||||
Ok(Ok(file))
|
||||
}
|
||||
|
||||
/// Default location for storing the config file for the ASB
|
||||
// Takes the default system config-dir and adds a `/asb/config.toml`
|
||||
pub fn default_config_path() -> Result<PathBuf> {
|
||||
system_config_dir()
|
||||
.map(|dir| Path::join(&dir, "asb"))
|
||||
.map(|dir| Path::join(&dir, "config.toml"))
|
||||
.context("Could not generate default config file path")
|
||||
}
|
||||
|
||||
/// Default location for storing data for the CLI
|
||||
// Takes the default system data-dir and adds a `/asb`
|
||||
fn default_data_dir() -> Result<PathBuf> {
|
||||
system_data_dir()
|
||||
.map(|proj_dir| Path::join(&proj_dir, "asb"))
|
||||
.context("Could not generate default data dir")
|
||||
}
|
||||
|
||||
pub fn initial_setup<F>(config_path: PathBuf, config_file: F) -> Result<()>
|
||||
where
|
||||
F: Fn() -> Result<Config>,
|
||||
{
|
||||
info!("Config file not found, running initial setup...");
|
||||
let initial_config = config_file()?;
|
||||
|
||||
let toml = toml::to_string(&initial_config)?;
|
||||
pub fn initial_setup(config_path: PathBuf, config: Config) -> Result<()> {
|
||||
let toml = toml::to_string(&config)?;
|
||||
|
||||
ensure_directory_exists(config_path.as_path())?;
|
||||
fs::write(&config_path, toml)?;
|
||||
@ -154,13 +195,30 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn query_user_for_initial_testnet_config() -> Result<Config> {
|
||||
pub fn query_user_for_initial_config(testnet: bool) -> Result<Config> {
|
||||
let (bitcoin_network, monero_network, defaults) = if testnet {
|
||||
tracing::info!("Running initial setup for testnet");
|
||||
|
||||
let bitcoin_network = bitcoin::Network::Testnet;
|
||||
let monero_network = monero::Network::Stagenet;
|
||||
let defaults = Testnet::getConfigFileDefaults()?;
|
||||
|
||||
(bitcoin_network, monero_network, defaults)
|
||||
} else {
|
||||
tracing::info!("Running initial setup for mainnet");
|
||||
let bitcoin_network = bitcoin::Network::Bitcoin;
|
||||
let monero_network = monero::Network::Mainnet;
|
||||
let defaults = Mainnet::getConfigFileDefaults()?;
|
||||
|
||||
(bitcoin_network, monero_network, defaults)
|
||||
};
|
||||
|
||||
println!();
|
||||
let data_dir = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Enter data directory for asb or hit return to use default")
|
||||
.default(
|
||||
default_data_dir()
|
||||
.context("No default data dir value for this system")?
|
||||
defaults
|
||||
.data_dir
|
||||
.to_str()
|
||||
.context("Unsupported characters in default path")?
|
||||
.to_string(),
|
||||
@ -170,28 +228,27 @@ pub fn query_user_for_initial_testnet_config() -> Result<Config> {
|
||||
|
||||
let target_block = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("How fast should your Bitcoin transactions be confirmed? Your transaction fee will be calculated based on this target. Hit return to use default")
|
||||
.default(DEFAULT_BITCOIN_CONFIRMATION_TARGET)
|
||||
.default(defaults.bitcoin_confirmation_target)
|
||||
.interact_text()?;
|
||||
|
||||
let listen_addresses = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Enter multiaddresses (comma separated) on which asb should list for peer-to-peer communications or hit return to use default")
|
||||
.default( format!("{},{}", DEFAULT_LISTEN_ADDRESS_TCP, DEFAULT_LISTEN_ADDRESS_WS))
|
||||
.default( format!("{},{}", defaults.listen_address_tcp, defaults.listen_address_ws))
|
||||
.interact_text()?;
|
||||
let listen_addresses = listen_addresses
|
||||
.split(',')
|
||||
.map(|str| str.parse())
|
||||
.collect::<Result<Vec<Multiaddr>, _>>()?;
|
||||
|
||||
let electrum_rpc_url: String = Input::with_theme(&ColorfulTheme::default())
|
||||
let electrum_rpc_url: Url = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Enter Electrum RPC URL or hit return to use default")
|
||||
.default(DEFAULT_ELECTRUM_RPC_URL.to_owned())
|
||||
.default(defaults.electrum_rpc_url)
|
||||
.interact_text()?;
|
||||
let electrum_rpc_url = Url::parse(electrum_rpc_url.as_str())?;
|
||||
|
||||
let monero_wallet_rpc_url = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Enter Monero Wallet RPC URL or hit enter to use default")
|
||||
.default(DEFAULT_MONERO_WALLET_RPC_TESTNET_URL.to_owned())
|
||||
.default(defaults.monero_wallet_rpc_url)
|
||||
.interact_text()?;
|
||||
let monero_wallet_rpc_url = monero_wallet_rpc_url.as_str().parse()?;
|
||||
|
||||
let tor_control_port = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Enter Tor control port or hit enter to use default. If Tor is not running on your machine, no hidden service will be created.")
|
||||
@ -234,9 +291,13 @@ pub fn query_user_for_initial_testnet_config() -> Result<Config> {
|
||||
bitcoin: Bitcoin {
|
||||
electrum_rpc_url,
|
||||
target_block,
|
||||
finality_confirmations: None,
|
||||
network: bitcoin_network,
|
||||
},
|
||||
monero: Monero {
|
||||
wallet_rpc_url: monero_wallet_rpc_url,
|
||||
finality_confirmations: None,
|
||||
network: monero_network,
|
||||
},
|
||||
tor: TorConf {
|
||||
control_port: tor_control_port,
|
||||
@ -253,31 +314,33 @@ pub fn query_user_for_initial_testnet_config() -> Result<Config> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn config_roundtrip() {
|
||||
fn config_roundtrip_testnet() {
|
||||
let temp_dir = tempdir().unwrap().path().to_path_buf();
|
||||
let config_path = Path::join(&temp_dir, "config.toml");
|
||||
|
||||
let defaults = Testnet::getConfigFileDefaults().unwrap();
|
||||
|
||||
let expected = Config {
|
||||
data: Data {
|
||||
dir: Default::default(),
|
||||
},
|
||||
bitcoin: Bitcoin {
|
||||
electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(),
|
||||
target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET,
|
||||
electrum_rpc_url: defaults.electrum_rpc_url,
|
||||
target_block: defaults.bitcoin_confirmation_target,
|
||||
finality_confirmations: None,
|
||||
network: bitcoin::Network::Testnet,
|
||||
},
|
||||
network: Network {
|
||||
listen: vec![
|
||||
DEFAULT_LISTEN_ADDRESS_TCP.parse().unwrap(),
|
||||
DEFAULT_LISTEN_ADDRESS_WS.parse().unwrap(),
|
||||
],
|
||||
listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
|
||||
},
|
||||
|
||||
monero: Monero {
|
||||
wallet_rpc_url: Url::from_str(DEFAULT_MONERO_WALLET_RPC_TESTNET_URL).unwrap(),
|
||||
wallet_rpc_url: defaults.monero_wallet_rpc_url,
|
||||
finality_confirmations: None,
|
||||
network: monero::Network::Stagenet,
|
||||
},
|
||||
tor: Default::default(),
|
||||
maker: Maker {
|
||||
@ -287,7 +350,47 @@ mod tests {
|
||||
},
|
||||
};
|
||||
|
||||
initial_setup(config_path.clone(), || Ok(expected.clone())).unwrap();
|
||||
initial_setup(config_path.clone(), expected.clone()).unwrap();
|
||||
let actual = read_config(config_path).unwrap().unwrap();
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_roundtrip_mainnet() {
|
||||
let temp_dir = tempdir().unwrap().path().to_path_buf();
|
||||
let config_path = Path::join(&temp_dir, "config.toml");
|
||||
|
||||
let defaults = Mainnet::getConfigFileDefaults().unwrap();
|
||||
|
||||
let expected = Config {
|
||||
data: Data {
|
||||
dir: Default::default(),
|
||||
},
|
||||
bitcoin: Bitcoin {
|
||||
electrum_rpc_url: defaults.electrum_rpc_url,
|
||||
target_block: defaults.bitcoin_confirmation_target,
|
||||
finality_confirmations: None,
|
||||
network: bitcoin::Network::Bitcoin,
|
||||
},
|
||||
network: Network {
|
||||
listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
|
||||
},
|
||||
|
||||
monero: Monero {
|
||||
wallet_rpc_url: defaults.monero_wallet_rpc_url,
|
||||
finality_confirmations: None,
|
||||
network: monero::Network::Mainnet,
|
||||
},
|
||||
tor: Default::default(),
|
||||
maker: Maker {
|
||||
min_buy_btc: bitcoin::Amount::from_btc(DEFAULT_MIN_BUY_AMOUNT).unwrap(),
|
||||
max_buy_btc: bitcoin::Amount::from_btc(DEFAULT_MAX_BUY_AMOUNT).unwrap(),
|
||||
ask_spread: Decimal::from_f64(DEFAULT_SPREAD).unwrap(),
|
||||
},
|
||||
};
|
||||
|
||||
initial_setup(config_path.clone(), expected.clone()).unwrap();
|
||||
let actual = read_config(config_path).unwrap().unwrap();
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
|
@ -12,7 +12,7 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use libp2p::core::multiaddr::Protocol;
|
||||
use libp2p::core::Multiaddr;
|
||||
use libp2p::Swarm;
|
||||
@ -22,11 +22,10 @@ use std::sync::Arc;
|
||||
use structopt::StructOpt;
|
||||
use swap::asb::command::{Arguments, Command, ManualRecovery, RecoverCommandParams};
|
||||
use swap::asb::config::{
|
||||
default_config_path, initial_setup, query_user_for_initial_testnet_config, read_config, Config,
|
||||
ConfigNotInitialized,
|
||||
initial_setup, query_user_for_initial_config, read_config, Config, ConfigNotInitialized,
|
||||
GetDefaults,
|
||||
};
|
||||
use swap::database::Database;
|
||||
use swap::env::GetConfig;
|
||||
use swap::monero::Amount;
|
||||
use swap::network::swarm;
|
||||
use swap::protocol::alice;
|
||||
@ -45,23 +44,45 @@ const DEFAULT_WALLET_NAME: &str = "asb-wallet";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let opt = Arguments::from_args();
|
||||
asb::tracing::init(LevelFilter::DEBUG, opt.json).expect("initialize tracing");
|
||||
let Arguments {
|
||||
testnet,
|
||||
json,
|
||||
config,
|
||||
cmd,
|
||||
} = Arguments::from_args();
|
||||
asb::tracing::init(LevelFilter::DEBUG, json).expect("initialize tracing");
|
||||
|
||||
let config_path = if let Some(config_path) = opt.config {
|
||||
let config_path = if let Some(config_path) = config {
|
||||
config_path
|
||||
} else if testnet {
|
||||
env::Testnet::getConfigFileDefaults()?.config_path
|
||||
} else {
|
||||
default_config_path()?
|
||||
env::Mainnet::getConfigFileDefaults()?.config_path
|
||||
};
|
||||
|
||||
let config = match read_config(config_path.clone())? {
|
||||
Ok(config) => config,
|
||||
Err(ConfigNotInitialized {}) => {
|
||||
initial_setup(config_path.clone(), query_user_for_initial_testnet_config)?;
|
||||
initial_setup(config_path.clone(), query_user_for_initial_config(testnet)?)?;
|
||||
read_config(config_path)?.expect("after initial setup config can be read")
|
||||
}
|
||||
};
|
||||
|
||||
let env_config = env::new(testnet, &config);
|
||||
|
||||
if config.monero.network != env_config.monero_network {
|
||||
bail!(format!(
|
||||
"Expected monero network in config file to be {:?} but was {:?}",
|
||||
env_config.monero_network, config.monero.network
|
||||
));
|
||||
}
|
||||
if config.bitcoin.network != env_config.bitcoin_network {
|
||||
bail!(format!(
|
||||
"Expected bitcoin network in config file to be {:?} but was {:?}",
|
||||
env_config.bitcoin_network, config.bitcoin.network
|
||||
));
|
||||
}
|
||||
|
||||
info!(
|
||||
db_folder = %config.data.dir.display(),
|
||||
"Database and Seed will be stored in",
|
||||
@ -75,9 +96,7 @@ async fn main() -> Result<()> {
|
||||
let seed =
|
||||
Seed::from_file_or_generate(&config.data.dir).expect("Could not retrieve/initialize seed");
|
||||
|
||||
let env_config = env::Testnet::get_config();
|
||||
|
||||
match opt.cmd {
|
||||
match cmd {
|
||||
Command::Start { resume_only } => {
|
||||
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
|
||||
|
||||
@ -127,6 +146,7 @@ async fn main() -> Result<()> {
|
||||
config.maker.max_buy_btc,
|
||||
kraken_rate.clone(),
|
||||
resume_only,
|
||||
env_config,
|
||||
)?;
|
||||
|
||||
for listen in config.network.listen {
|
||||
|
@ -15,21 +15,21 @@
|
||||
use anyhow::{bail, Context, Result};
|
||||
use prettytable::{row, Table};
|
||||
use std::cmp::min;
|
||||
use std::env;
|
||||
use std::future::Future;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use structopt::StructOpt;
|
||||
use swap::bitcoin::TxLock;
|
||||
use swap::cli::command::{Arguments, Command, MoneroParams};
|
||||
use swap::cli::command::{parse_args_and_apply_defaults, Arguments, Command};
|
||||
use swap::database::Database;
|
||||
use swap::env::{Config, GetConfig};
|
||||
use swap::env::Config;
|
||||
use swap::network::quote::BidQuote;
|
||||
use swap::network::swarm;
|
||||
use swap::protocol::bob;
|
||||
use swap::protocol::bob::{EventLoop, Swap};
|
||||
use swap::seed::Seed;
|
||||
use swap::{bitcoin, cli, env, monero};
|
||||
use swap::{bitcoin, cli, monero};
|
||||
use tracing::{debug, error, info, warn};
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
@ -39,41 +39,33 @@ extern crate prettytable;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let Arguments { data, debug, cmd } = Arguments::from_args();
|
||||
let Arguments {
|
||||
env_config,
|
||||
data_dir,
|
||||
debug,
|
||||
cmd,
|
||||
} = parse_args_and_apply_defaults(env::args_os())?;
|
||||
|
||||
match cmd {
|
||||
Command::BuyXmr {
|
||||
alice_peer_id,
|
||||
alice_multiaddr,
|
||||
monero_params:
|
||||
MoneroParams {
|
||||
receive_monero_address,
|
||||
monero_daemon_host,
|
||||
},
|
||||
electrum_rpc_url,
|
||||
tor_socks5_port,
|
||||
seller_peer_id,
|
||||
seller_addr,
|
||||
bitcoin_electrum_rpc_url,
|
||||
bitcoin_target_block,
|
||||
monero_receive_address,
|
||||
monero_daemon_address,
|
||||
tor_socks5_port,
|
||||
} => {
|
||||
let swap_id = Uuid::new_v4();
|
||||
|
||||
let data_dir = data.0;
|
||||
cli::tracing::init(debug, data_dir.join("logs"), swap_id)?;
|
||||
let db = Database::open(data_dir.join("database").as_path())
|
||||
.context("Failed to open database")?;
|
||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
||||
.context("Failed to read in seed file")?;
|
||||
let env_config = env::Testnet::get_config();
|
||||
|
||||
if receive_monero_address.network != env_config.monero_network {
|
||||
bail!(
|
||||
"Given monero address is on network {:?}, expected address on network {:?}",
|
||||
receive_monero_address.network,
|
||||
env_config.monero_network
|
||||
)
|
||||
}
|
||||
|
||||
let bitcoin_wallet = init_bitcoin_wallet(
|
||||
electrum_rpc_url,
|
||||
bitcoin_electrum_rpc_url,
|
||||
&seed,
|
||||
data_dir.clone(),
|
||||
env_config,
|
||||
@ -81,17 +73,22 @@ async fn main() -> Result<()> {
|
||||
)
|
||||
.await?;
|
||||
let (monero_wallet, _process) =
|
||||
init_monero_wallet(data_dir, monero_daemon_host, env_config).await?;
|
||||
init_monero_wallet(data_dir, monero_daemon_address, env_config).await?;
|
||||
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
||||
|
||||
let mut swarm = swarm::bob(&seed, alice_peer_id, tor_socks5_port).await?;
|
||||
let mut swarm = swarm::bob(&seed, seller_peer_id, tor_socks5_port).await?;
|
||||
swarm
|
||||
.behaviour_mut()
|
||||
.add_address(alice_peer_id, alice_multiaddr);
|
||||
.add_address(seller_peer_id, seller_addr);
|
||||
|
||||
let swap_id = Uuid::new_v4();
|
||||
let (event_loop, mut event_loop_handle) =
|
||||
EventLoop::new(swap_id, swarm, alice_peer_id, bitcoin_wallet.clone())?;
|
||||
let (event_loop, mut event_loop_handle) = EventLoop::new(
|
||||
swap_id,
|
||||
swarm,
|
||||
seller_peer_id,
|
||||
bitcoin_wallet.clone(),
|
||||
env_config,
|
||||
)?;
|
||||
let event_loop = tokio::spawn(event_loop.run());
|
||||
|
||||
let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size());
|
||||
@ -106,7 +103,7 @@ async fn main() -> Result<()> {
|
||||
|
||||
info!("Swapping {} with {} fees", send_bitcoin, fees);
|
||||
|
||||
db.insert_peer_id(swap_id, alice_peer_id).await?;
|
||||
db.insert_peer_id(swap_id, seller_peer_id).await?;
|
||||
|
||||
let swap = Swap::new(
|
||||
db,
|
||||
@ -115,7 +112,7 @@ async fn main() -> Result<()> {
|
||||
Arc::new(monero_wallet),
|
||||
env_config,
|
||||
event_loop_handle,
|
||||
receive_monero_address,
|
||||
monero_receive_address,
|
||||
send_bitcoin,
|
||||
);
|
||||
|
||||
@ -130,8 +127,6 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
Command::History => {
|
||||
let data_dir = data.0;
|
||||
|
||||
let db = Database::open(data_dir.join("database").as_path())
|
||||
.context("Failed to open database")?;
|
||||
|
||||
@ -148,30 +143,25 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
Command::Resume {
|
||||
swap_id,
|
||||
alice_multiaddr,
|
||||
monero_params:
|
||||
MoneroParams {
|
||||
receive_monero_address,
|
||||
monero_daemon_host,
|
||||
},
|
||||
electrum_rpc_url,
|
||||
tor_socks5_port,
|
||||
seller_addr,
|
||||
bitcoin_electrum_rpc_url,
|
||||
bitcoin_target_block,
|
||||
monero_receive_address,
|
||||
monero_daemon_address,
|
||||
tor_socks5_port,
|
||||
} => {
|
||||
let data_dir = data.0;
|
||||
cli::tracing::init(debug, data_dir.join("logs"), swap_id)?;
|
||||
let db = Database::open(data_dir.join("database").as_path())
|
||||
.context("Failed to open database")?;
|
||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
||||
.context("Failed to read in seed file")?;
|
||||
let env_config = env::Testnet::get_config();
|
||||
|
||||
if receive_monero_address.network != env_config.monero_network {
|
||||
bail!("The given monero address is on network {:?}, expected address of network {:?}.", receive_monero_address.network, env_config.monero_network)
|
||||
if monero_receive_address.network != env_config.monero_network {
|
||||
bail!("The given monero address is on network {:?}, expected address of network {:?}.", monero_receive_address.network, env_config.monero_network)
|
||||
}
|
||||
|
||||
let bitcoin_wallet = init_bitcoin_wallet(
|
||||
electrum_rpc_url,
|
||||
bitcoin_electrum_rpc_url,
|
||||
&seed,
|
||||
data_dir.clone(),
|
||||
env_config,
|
||||
@ -179,20 +169,25 @@ async fn main() -> Result<()> {
|
||||
)
|
||||
.await?;
|
||||
let (monero_wallet, _process) =
|
||||
init_monero_wallet(data_dir, monero_daemon_host, env_config).await?;
|
||||
init_monero_wallet(data_dir, monero_daemon_address, env_config).await?;
|
||||
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
||||
|
||||
let alice_peer_id = db.get_peer_id(swap_id)?;
|
||||
let seller_peer_id = db.get_peer_id(swap_id)?;
|
||||
|
||||
let mut swarm = swarm::bob(&seed, alice_peer_id, tor_socks5_port).await?;
|
||||
let mut swarm = swarm::bob(&seed, seller_peer_id, tor_socks5_port).await?;
|
||||
let bob_peer_id = swarm.local_peer_id();
|
||||
tracing::debug!("Our peer-id: {}", bob_peer_id);
|
||||
swarm
|
||||
.behaviour_mut()
|
||||
.add_address(alice_peer_id, alice_multiaddr);
|
||||
.add_address(seller_peer_id, seller_addr);
|
||||
|
||||
let (event_loop, event_loop_handle) =
|
||||
EventLoop::new(swap_id, swarm, alice_peer_id, bitcoin_wallet.clone())?;
|
||||
let (event_loop, event_loop_handle) = EventLoop::new(
|
||||
swap_id,
|
||||
swarm,
|
||||
seller_peer_id,
|
||||
bitcoin_wallet.clone(),
|
||||
env_config,
|
||||
)?;
|
||||
let handle = tokio::spawn(event_loop.run());
|
||||
|
||||
let swap = Swap::from_db(
|
||||
@ -202,7 +197,7 @@ async fn main() -> Result<()> {
|
||||
Arc::new(monero_wallet),
|
||||
env_config,
|
||||
event_loop_handle,
|
||||
receive_monero_address,
|
||||
monero_receive_address,
|
||||
)?;
|
||||
|
||||
tokio::select! {
|
||||
@ -217,19 +212,17 @@ async fn main() -> Result<()> {
|
||||
Command::Cancel {
|
||||
swap_id,
|
||||
force,
|
||||
electrum_rpc_url,
|
||||
bitcoin_electrum_rpc_url,
|
||||
bitcoin_target_block,
|
||||
} => {
|
||||
let data_dir = data.0;
|
||||
cli::tracing::init(debug, data_dir.join("logs"), swap_id)?;
|
||||
let db = Database::open(data_dir.join("database").as_path())
|
||||
.context("Failed to open database")?;
|
||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
||||
.context("Failed to read in seed file")?;
|
||||
let env_config = env::Testnet::get_config();
|
||||
|
||||
let bitcoin_wallet = init_bitcoin_wallet(
|
||||
electrum_rpc_url,
|
||||
bitcoin_electrum_rpc_url,
|
||||
&seed,
|
||||
data_dir,
|
||||
env_config,
|
||||
@ -251,19 +244,17 @@ async fn main() -> Result<()> {
|
||||
Command::Refund {
|
||||
swap_id,
|
||||
force,
|
||||
electrum_rpc_url,
|
||||
bitcoin_electrum_rpc_url,
|
||||
bitcoin_target_block,
|
||||
} => {
|
||||
let data_dir = data.0;
|
||||
cli::tracing::init(debug, data_dir.join("logs"), swap_id)?;
|
||||
let db = Database::open(data_dir.join("database").as_path())
|
||||
.context("Failed to open database")?;
|
||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
||||
.context("Failed to read in seed file")?;
|
||||
let env_config = env::Testnet::get_config();
|
||||
|
||||
let bitcoin_wallet = init_bitcoin_wallet(
|
||||
electrum_rpc_url,
|
||||
bitcoin_electrum_rpc_url,
|
||||
&seed,
|
||||
data_dir,
|
||||
env_config,
|
||||
@ -303,7 +294,7 @@ async fn init_bitcoin_wallet(
|
||||
|
||||
async fn init_monero_wallet(
|
||||
data_dir: PathBuf,
|
||||
monero_daemon_host: String,
|
||||
monero_daemon_address: String,
|
||||
env_config: Config,
|
||||
) -> Result<(monero::Wallet, monero::WalletRpcProcess)> {
|
||||
let network = env_config.monero_network;
|
||||
@ -313,7 +304,7 @@ async fn init_monero_wallet(
|
||||
let monero_wallet_rpc = monero::WalletRpc::new(data_dir.join("monero")).await?;
|
||||
|
||||
let monero_wallet_rpc_process = monero_wallet_rpc
|
||||
.run(network, monero_daemon_host.as_str())
|
||||
.run(network, monero_daemon_address.as_str())
|
||||
.await?;
|
||||
|
||||
let monero_wallet = monero::Wallet::open_or_create(
|
||||
|
@ -37,6 +37,17 @@ use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "Network")]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum network {
|
||||
#[serde(rename = "Mainnet")]
|
||||
Bitcoin,
|
||||
Testnet,
|
||||
Signet,
|
||||
Regtest,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
pub struct SecretKey {
|
||||
inner: Scalar,
|
||||
|
@ -16,6 +16,7 @@ use reqwest::Url;
|
||||
use rust_decimal::prelude::*;
|
||||
use rust_decimal::Decimal;
|
||||
use rust_decimal_macros::dec;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
@ -108,18 +109,6 @@ impl Wallet {
|
||||
Ok((txid, subscription))
|
||||
}
|
||||
|
||||
pub async fn sign_and_finalize(&self, psbt: PartiallySignedTransaction) -> Result<Transaction> {
|
||||
let (signed_psbt, finalized) = self.wallet.lock().await.sign(psbt, None)?;
|
||||
|
||||
if !finalized {
|
||||
bail!("PSBT is not finalized")
|
||||
}
|
||||
|
||||
let tx = signed_psbt.extract_tx();
|
||||
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
pub async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction> {
|
||||
self.get_tx(txid)
|
||||
.await?
|
||||
@ -257,6 +246,18 @@ where
|
||||
C: EstimateFeeRate,
|
||||
D: BatchDatabase,
|
||||
{
|
||||
pub async fn sign_and_finalize(&self, psbt: PartiallySignedTransaction) -> Result<Transaction> {
|
||||
let (signed_psbt, finalized) = self.wallet.lock().await.sign(psbt, None)?;
|
||||
|
||||
if !finalized {
|
||||
bail!("PSBT is not finalized")
|
||||
}
|
||||
|
||||
let tx = signed_psbt.extract_tx();
|
||||
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
pub async fn balance(&self) -> Result<Amount> {
|
||||
let balance = self
|
||||
.wallet
|
||||
@ -293,6 +294,10 @@ where
|
||||
Ok(Amount::from_sat(fees))
|
||||
}
|
||||
|
||||
/// Builds a partially signed transaction
|
||||
///
|
||||
/// Ensures that the address script is at output index `0`
|
||||
/// for the partially signed transaction.
|
||||
pub async fn send_to_address(
|
||||
&self,
|
||||
address: Address,
|
||||
@ -301,11 +306,30 @@ where
|
||||
let wallet = self.wallet.lock().await;
|
||||
let client = self.client.lock().await;
|
||||
let fee_rate = client.estimate_feerate(self.target_block)?;
|
||||
let script = address.script_pubkey();
|
||||
|
||||
let mut tx_builder = wallet.build_tx();
|
||||
tx_builder.add_recipient(address.script_pubkey(), amount.as_sat());
|
||||
tx_builder.add_recipient(script.clone(), amount.as_sat());
|
||||
tx_builder.fee_rate(fee_rate);
|
||||
let (psbt, _details) = tx_builder.finish()?;
|
||||
let mut psbt: PartiallySignedTransaction = psbt;
|
||||
|
||||
// When subscribing to transactions we depend on the relevant script being at
|
||||
// output index 0, thus we ensure the relevant output to be at index `0`.
|
||||
psbt.outputs.sort_by(|a, _| {
|
||||
if a.witness_script.as_ref() == Some(&script) {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
});
|
||||
psbt.global.unsigned_tx.output.sort_by(|a, _| {
|
||||
if a.script_pubkey == script {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
});
|
||||
|
||||
Ok(psbt)
|
||||
}
|
||||
@ -480,7 +504,7 @@ where
|
||||
use bitcoin::OutPoint;
|
||||
use testutils::testutils;
|
||||
|
||||
let descriptors = testutils!(@descriptors ("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"));
|
||||
let descriptors = testutils!(@descriptors ("wpkh(tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m/*)"));
|
||||
|
||||
let mut database = MemoryDatabase::new();
|
||||
bdk::populate_test_db!(
|
||||
@ -527,59 +551,39 @@ impl Watchable for (Txid, Script) {
|
||||
|
||||
pub struct Client {
|
||||
electrum: bdk::electrum_client::Client,
|
||||
latest_block: BlockHeight,
|
||||
last_ping: Instant,
|
||||
interval: Duration,
|
||||
latest_block_height: BlockHeight,
|
||||
last_sync: Instant,
|
||||
sync_interval: Duration,
|
||||
script_history: BTreeMap<Script, Vec<GetHistoryRes>>,
|
||||
subscriptions: HashMap<(Txid, Script), Subscription>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
fn new(electrum: bdk::electrum_client::Client, interval: Duration) -> Result<Self> {
|
||||
// Initially fetch the latest block for storing the height.
|
||||
// We do not act on this subscription after this call.
|
||||
let latest_block = electrum
|
||||
.block_headers_subscribe()
|
||||
.context("Failed to subscribe to header notifications")?;
|
||||
|
||||
Ok(Self {
|
||||
electrum,
|
||||
latest_block: BlockHeight::try_from(latest_block)?,
|
||||
last_ping: Instant::now(),
|
||||
interval,
|
||||
latest_block_height: BlockHeight::try_from(latest_block)?,
|
||||
last_sync: Instant::now(),
|
||||
sync_interval: interval,
|
||||
script_history: Default::default(),
|
||||
subscriptions: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Ping the electrum server unless we already did within the set interval.
|
||||
///
|
||||
/// Returns a boolean indicating whether we actually pinged the server.
|
||||
fn ping(&mut self) -> bool {
|
||||
if self.last_ping.elapsed() <= self.interval {
|
||||
return false;
|
||||
}
|
||||
|
||||
match self.electrum.ping() {
|
||||
Ok(()) => {
|
||||
self.last_ping = Instant::now();
|
||||
|
||||
true
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::debug!(?error, "Failed to ping electrum server");
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn drain_notifications(&mut self) -> Result<()> {
|
||||
let pinged = self.ping();
|
||||
|
||||
if !pinged {
|
||||
fn update_state(&mut self) -> Result<()> {
|
||||
let now = Instant::now();
|
||||
if now < self.last_sync + self.sync_interval {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.drain_blockheight_notifications()?;
|
||||
self.last_sync = now;
|
||||
self.update_latest_block()?;
|
||||
self.update_script_histories()?;
|
||||
|
||||
Ok(())
|
||||
@ -596,7 +600,7 @@ impl Client {
|
||||
self.script_history.insert(script.clone(), vec![]);
|
||||
}
|
||||
|
||||
self.drain_notifications()?;
|
||||
self.update_state()?;
|
||||
|
||||
let history = self.script_history.entry(script).or_default();
|
||||
|
||||
@ -618,7 +622,7 @@ impl Client {
|
||||
Ok(ScriptStatus::Confirmed(
|
||||
Confirmed::from_inclusion_and_latest_block(
|
||||
u32::try_from(last.height)?,
|
||||
u32::from(self.latest_block),
|
||||
u32::from(self.latest_block_height),
|
||||
),
|
||||
))
|
||||
}
|
||||
@ -626,18 +630,24 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
fn drain_blockheight_notifications(&mut self) -> Result<()> {
|
||||
let latest_block = std::iter::from_fn(|| self.electrum.block_headers_pop().transpose())
|
||||
.last()
|
||||
.transpose()
|
||||
.context("Failed to pop header notification")?;
|
||||
fn update_latest_block(&mut self) -> Result<()> {
|
||||
// Fetch the latest block for storing the height.
|
||||
// We do not act on this subscription after this call, as we cannot rely on
|
||||
// subscription push notifications because eventually the Electrum server will
|
||||
// close the connection and subscriptions are not automatically renewed
|
||||
// upon renewing the connection.
|
||||
let latest_block = self
|
||||
.electrum
|
||||
.block_headers_subscribe()
|
||||
.context("Failed to subscribe to header notifications")?;
|
||||
let latest_block_height = BlockHeight::try_from(latest_block)?;
|
||||
|
||||
if let Some(new_block) = latest_block {
|
||||
if latest_block_height > self.latest_block_height {
|
||||
tracing::debug!(
|
||||
block_height = new_block.height,
|
||||
block_height = u32::from(latest_block_height),
|
||||
"Got notification for new block"
|
||||
);
|
||||
self.latest_block = BlockHeight::try_from(new_block)?;
|
||||
self.latest_block_height = latest_block_height;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -774,7 +784,7 @@ impl fmt::Display for ScriptStatus {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::bitcoin::TxLock;
|
||||
use crate::bitcoin::{PublicKey, TxLock};
|
||||
use proptest::prelude::*;
|
||||
|
||||
#[test]
|
||||
@ -1004,4 +1014,51 @@ mod tests {
|
||||
|
||||
assert!(amount.as_sat() > 0);
|
||||
}
|
||||
|
||||
/// This test ensures that the relevant script output of the transaction
|
||||
/// created out of the PSBT is at index 0. This is important because
|
||||
/// subscriptions to the transaction are on index `0` when broadcasting the
|
||||
/// transaction.
|
||||
#[tokio::test]
|
||||
async fn given_amounts_with_change_outputs_when_signing_tx_then_output_index_0_is_ensured_for_script(
|
||||
) {
|
||||
// We don't care about fees in this test, thus use a zero fee rate
|
||||
struct NoFeeRate();
|
||||
impl EstimateFeeRate for NoFeeRate {
|
||||
fn estimate_feerate(&self, _target_block: usize) -> Result<FeeRate> {
|
||||
Ok(FeeRate::from_sat_per_vb(0.0))
|
||||
}
|
||||
|
||||
fn min_relay_fee(&self) -> Result<bitcoin::Amount> {
|
||||
Ok(bitcoin::Amount::from_sat(0))
|
||||
}
|
||||
}
|
||||
|
||||
// This value is somewhat arbitrary but the indexation problem usually occurred
|
||||
// on the first or second value (i.e. 547, 548) We keep the test
|
||||
// iterations relatively low because these tests are expensive.
|
||||
let above_dust = 547;
|
||||
let balance = 2000;
|
||||
|
||||
let wallet = Wallet::new_funded(balance, NoFeeRate());
|
||||
|
||||
// sorting is only relevant for amounts that have a change output
|
||||
// if the change output is below dust it will be dropped by the BDK
|
||||
for amount in above_dust..(balance - (above_dust - 1)) {
|
||||
let (A, B) = (PublicKey::random(), PublicKey::random());
|
||||
let txlock = TxLock::new(&wallet, bitcoin::Amount::from_sat(amount), A, B)
|
||||
.await
|
||||
.unwrap();
|
||||
let txlock_output = txlock.script_pubkey();
|
||||
|
||||
let tx = wallet.sign_and_finalize(txlock.into()).await.unwrap();
|
||||
let tx_output = tx.output[0].script_pubkey.clone();
|
||||
|
||||
assert_eq!(
|
||||
tx_output, txlock_output,
|
||||
"Output {:?} index mismatch for amount {} and balance {}",
|
||||
tx.output, amount, balance
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,10 @@
|
||||
use crate::asb;
|
||||
use crate::bitcoin::{CancelTimelock, PunishTimelock};
|
||||
use std::cmp::max;
|
||||
use std::time::Duration;
|
||||
use time::NumericalStdDurationShort;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Config {
|
||||
pub bitcoin_lock_confirmed_timeout: Duration,
|
||||
pub bitcoin_finality_confirmations: u32,
|
||||
@ -43,13 +44,13 @@ impl GetConfig for Mainnet {
|
||||
fn get_config() -> Config {
|
||||
Config {
|
||||
bitcoin_lock_confirmed_timeout: 24.hours(),
|
||||
bitcoin_finality_confirmations: 3,
|
||||
bitcoin_finality_confirmations: 2,
|
||||
bitcoin_avg_block_time: 10.minutes(),
|
||||
bitcoin_cancel_timelock: CancelTimelock::new(72),
|
||||
bitcoin_punish_timelock: PunishTimelock::new(72),
|
||||
bitcoin_network: bitcoin::Network::Bitcoin,
|
||||
monero_avg_block_time: 2.minutes(),
|
||||
monero_finality_confirmations: 15,
|
||||
monero_finality_confirmations: 10,
|
||||
monero_network: monero::Network::Mainnet,
|
||||
}
|
||||
}
|
||||
@ -59,8 +60,8 @@ impl GetConfig for Testnet {
|
||||
fn get_config() -> Config {
|
||||
Config {
|
||||
bitcoin_lock_confirmed_timeout: 12.hours(),
|
||||
bitcoin_finality_confirmations: 1,
|
||||
bitcoin_avg_block_time: 5.minutes(),
|
||||
bitcoin_finality_confirmations: 2,
|
||||
bitcoin_avg_block_time: 10.minutes(),
|
||||
bitcoin_cancel_timelock: CancelTimelock::new(12),
|
||||
bitcoin_punish_timelock: PunishTimelock::new(6),
|
||||
bitcoin_network: bitcoin::Network::Testnet,
|
||||
@ -91,6 +92,33 @@ fn sync_interval(avg_block_time: Duration) -> Duration {
|
||||
max(avg_block_time / 10, Duration::from_secs(1))
|
||||
}
|
||||
|
||||
pub fn new(is_testnet: bool, asb_config: &asb::config::Config) -> Config {
|
||||
let env_config = if is_testnet {
|
||||
Testnet::get_config()
|
||||
} else {
|
||||
Mainnet::get_config()
|
||||
};
|
||||
|
||||
let env_config =
|
||||
if let Some(bitcoin_finality_confirmations) = asb_config.bitcoin.finality_confirmations {
|
||||
Config {
|
||||
bitcoin_finality_confirmations,
|
||||
..env_config
|
||||
}
|
||||
} else {
|
||||
env_config
|
||||
};
|
||||
|
||||
if let Some(monero_finality_confirmations) = asb_config.monero.finality_confirmations {
|
||||
Config {
|
||||
monero_finality_confirmations,
|
||||
..env_config
|
||||
}
|
||||
} else {
|
||||
env_config
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -1,7 +1,8 @@
|
||||
pub mod wallet;
|
||||
mod wallet_rpc;
|
||||
|
||||
pub use ::monero::{Address, Network, PrivateKey, PublicKey};
|
||||
pub use ::monero::network::Network;
|
||||
pub use ::monero::{Address, PrivateKey, PublicKey};
|
||||
pub use curve25519_dalek::scalar::Scalar;
|
||||
pub use wallet::Wallet;
|
||||
pub use wallet_rpc::{WalletRpc, WalletRpcProcess};
|
||||
@ -19,6 +20,15 @@ use std::str::FromStr;
|
||||
|
||||
pub const PICONERO_OFFSET: u64 = 1_000_000_000_000;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "Network")]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum network {
|
||||
Mainnet,
|
||||
Stagenet,
|
||||
Testnet,
|
||||
}
|
||||
|
||||
pub fn private_key_from_secp256k1_scalar(scalar: bitcoin::Scalar) -> PrivateKey {
|
||||
let mut bytes = scalar.to_bytes();
|
||||
|
||||
|
@ -115,7 +115,7 @@ impl WalletRpc {
|
||||
Ok(monero_wallet_rpc)
|
||||
}
|
||||
|
||||
pub async fn run(&self, network: Network, daemon_host: &str) -> Result<WalletRpcProcess> {
|
||||
pub async fn run(&self, network: Network, daemon_address: &str) -> Result<WalletRpcProcess> {
|
||||
let port = tokio::net::TcpListener::bind("127.0.0.1:0")
|
||||
.await?
|
||||
.local_addr()?
|
||||
@ -126,17 +126,25 @@ impl WalletRpc {
|
||||
"Starting monero-wallet-rpc on"
|
||||
);
|
||||
|
||||
let network_flag = match network {
|
||||
Network::Mainnet => {
|
||||
vec![]
|
||||
}
|
||||
Network::Stagenet => {
|
||||
vec!["--stagenet"]
|
||||
}
|
||||
Network::Testnet => {
|
||||
vec!["--testnet"]
|
||||
}
|
||||
};
|
||||
|
||||
let mut child = Command::new(self.exec_path())
|
||||
.env("LANG", "en_AU.UTF-8")
|
||||
.stdout(Stdio::piped())
|
||||
.kill_on_drop(true)
|
||||
.arg(match network {
|
||||
Network::Mainnet => "--mainnet",
|
||||
Network::Stagenet => "--stagenet",
|
||||
Network::Testnet => "--testnet",
|
||||
})
|
||||
.arg("--daemon-host")
|
||||
.arg(daemon_host)
|
||||
.args(network_flag)
|
||||
.arg("--daemon-address")
|
||||
.arg(daemon_address)
|
||||
.arg("--rpc-bind-port")
|
||||
.arg(format!("{}", port))
|
||||
.arg("--disable-rpc-login")
|
||||
|
@ -32,6 +32,7 @@ impl ProtocolName for SpotPriceProtocol {
|
||||
pub struct Request {
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
pub btc: bitcoin::Amount,
|
||||
pub blockchain_network: BlockchainNetwork,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
@ -59,11 +60,23 @@ pub enum Error {
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
buy: bitcoin::Amount,
|
||||
},
|
||||
BlockchainNetworkMismatch {
|
||||
cli: BlockchainNetwork,
|
||||
asb: BlockchainNetwork,
|
||||
},
|
||||
/// To be used for errors that cannot be explained on the CLI side (e.g.
|
||||
/// rate update problems on the seller side)
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct BlockchainNetwork {
|
||||
#[serde(with = "crate::bitcoin::network")]
|
||||
pub bitcoin: bitcoin::Network,
|
||||
#[serde(with = "crate::monero::network")]
|
||||
pub monero: monero::Network,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -103,6 +116,21 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(error, serialized);
|
||||
|
||||
let error = r#"{"Error":{"BlockchainNetworkMismatch":{"cli":{"bitcoin":"Mainnet","monero":"Mainnet"},"asb":{"bitcoin":"Testnet","monero":"Stagenet"}}}}"#.to_string();
|
||||
let serialized =
|
||||
serde_json::to_string(&Response::Error(Error::BlockchainNetworkMismatch {
|
||||
cli: BlockchainNetwork {
|
||||
bitcoin: bitcoin::Network::Bitcoin,
|
||||
monero: monero::Network::Mainnet,
|
||||
},
|
||||
asb: BlockchainNetwork {
|
||||
bitcoin: bitcoin::Network::Testnet,
|
||||
monero: monero::Network::Stagenet,
|
||||
},
|
||||
}))
|
||||
.unwrap();
|
||||
assert_eq!(error, serialized);
|
||||
|
||||
let error = r#"{"Error":"Other"}"#.to_string();
|
||||
let serialized = serde_json::to_string(&Response::Error(Error::Other)).unwrap();
|
||||
assert_eq!(error, serialized);
|
||||
|
@ -2,12 +2,13 @@ use crate::network::transport;
|
||||
use crate::protocol::alice::event_loop::LatestRate;
|
||||
use crate::protocol::{alice, bob};
|
||||
use crate::seed::Seed;
|
||||
use crate::{monero, tor};
|
||||
use crate::{env, monero, tor};
|
||||
use anyhow::Result;
|
||||
use libp2p::swarm::{NetworkBehaviour, SwarmBuilder};
|
||||
use libp2p::{PeerId, Swarm};
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn alice<LR>(
|
||||
seed: &Seed,
|
||||
balance: monero::Amount,
|
||||
@ -16,6 +17,7 @@ pub fn alice<LR>(
|
||||
max_buy: bitcoin::Amount,
|
||||
latest_rate: LR,
|
||||
resume_only: bool,
|
||||
env_config: env::Config,
|
||||
) -> Result<Swarm<alice::Behaviour<LR>>>
|
||||
where
|
||||
LR: LatestRate + Send + 'static + Debug,
|
||||
@ -29,6 +31,7 @@ where
|
||||
max_buy,
|
||||
latest_rate,
|
||||
resume_only,
|
||||
env_config,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::monero;
|
||||
use crate::network::quote::BidQuote;
|
||||
use crate::network::{encrypted_signature, quote, transfer_proof};
|
||||
use crate::protocol::alice::event_loop::LatestRate;
|
||||
use crate::protocol::alice::{execution_setup, spot_price, State3};
|
||||
use crate::{env, monero};
|
||||
use anyhow::{anyhow, Error};
|
||||
use libp2p::request_response::{RequestId, ResponseChannel};
|
||||
use libp2p::{NetworkBehaviour, PeerId};
|
||||
@ -88,6 +88,7 @@ where
|
||||
max_buy: bitcoin::Amount,
|
||||
latest_rate: LR,
|
||||
resume_only: bool,
|
||||
env_config: env::Config,
|
||||
) -> Self {
|
||||
Self {
|
||||
quote: quote::alice(),
|
||||
@ -96,6 +97,7 @@ where
|
||||
lock_fee,
|
||||
min_buy,
|
||||
max_buy,
|
||||
env_config,
|
||||
latest_rate,
|
||||
resume_only,
|
||||
),
|
||||
|
@ -211,7 +211,8 @@ where
|
||||
match error {
|
||||
Error::ResumeOnlyMode
|
||||
| Error::AmountBelowMinimum { .. }
|
||||
| Error::AmountAboveMaximum { .. } => {
|
||||
| Error::AmountAboveMaximum { .. }
|
||||
| Error::BlockchainNetworkMismatch { .. } => {
|
||||
tracing::warn!(%peer, "Ignoring spot price request because: {}", error);
|
||||
}
|
||||
Error::BalanceTooLow { .. }
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::monero;
|
||||
use crate::network::cbor_request_response::CborCodec;
|
||||
use crate::network::spot_price;
|
||||
use crate::network::spot_price::SpotPriceProtocol;
|
||||
use crate::network::spot_price::{BlockchainNetwork, SpotPriceProtocol};
|
||||
use crate::protocol::alice;
|
||||
use crate::protocol::alice::event_loop::LatestRate;
|
||||
use crate::{env, monero};
|
||||
use libp2p::request_response::{
|
||||
ProtocolSupport, RequestResponseConfig, RequestResponseEvent, RequestResponseMessage,
|
||||
ResponseChannel,
|
||||
@ -48,6 +48,8 @@ where
|
||||
#[behaviour(ignore)]
|
||||
max_buy: bitcoin::Amount,
|
||||
#[behaviour(ignore)]
|
||||
env_config: env::Config,
|
||||
#[behaviour(ignore)]
|
||||
latest_rate: LR,
|
||||
#[behaviour(ignore)]
|
||||
resume_only: bool,
|
||||
@ -66,6 +68,7 @@ where
|
||||
lock_fee: monero::Amount,
|
||||
min_buy: bitcoin::Amount,
|
||||
max_buy: bitcoin::Amount,
|
||||
env_config: env::Config,
|
||||
latest_rate: LR,
|
||||
resume_only: bool,
|
||||
) -> Self {
|
||||
@ -80,6 +83,7 @@ where
|
||||
lock_fee,
|
||||
min_buy,
|
||||
max_buy,
|
||||
env_config,
|
||||
latest_rate,
|
||||
resume_only,
|
||||
}
|
||||
@ -154,6 +158,19 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
let blockchain_network = BlockchainNetwork {
|
||||
bitcoin: self.env_config.bitcoin_network,
|
||||
monero: self.env_config.monero_network,
|
||||
};
|
||||
|
||||
if request.blockchain_network != blockchain_network {
|
||||
self.decline(peer, channel, Error::BlockchainNetworkMismatch {
|
||||
cli: request.blockchain_network,
|
||||
asb: blockchain_network,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if self.resume_only {
|
||||
self.decline(peer, channel, Error::ResumeOnlyMode);
|
||||
return;
|
||||
@ -246,12 +263,15 @@ pub enum Error {
|
||||
balance: monero::Amount,
|
||||
buy: bitcoin::Amount,
|
||||
},
|
||||
|
||||
#[error("Failed to fetch latest rate")]
|
||||
LatestRateFetchFailed(#[source] Box<dyn std::error::Error + Send + 'static>),
|
||||
|
||||
#[error("Failed to calculate quote: {0}")]
|
||||
SellQuoteCalculationFailed(#[source] anyhow::Error),
|
||||
#[error("Blockchain networks did not match, we are on {asb:?}, but request from {cli:?}")]
|
||||
BlockchainNetworkMismatch {
|
||||
cli: spot_price::BlockchainNetwork,
|
||||
asb: spot_price::BlockchainNetwork,
|
||||
},
|
||||
}
|
||||
|
||||
impl Error {
|
||||
@ -267,6 +287,12 @@ impl Error {
|
||||
buy: *buy,
|
||||
},
|
||||
Error::BalanceTooLow { buy, .. } => spot_price::Error::BalanceTooLow { buy: *buy },
|
||||
Error::BlockchainNetworkMismatch { cli, asb } => {
|
||||
spot_price::Error::BlockchainNetworkMismatch {
|
||||
cli: *cli,
|
||||
asb: *asb,
|
||||
}
|
||||
}
|
||||
Error::LatestRateFetchFailed(_) | Error::SellQuoteCalculationFailed(_) => {
|
||||
spot_price::Error::Other
|
||||
}
|
||||
@ -278,6 +304,7 @@ impl Error {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::asb::Rate;
|
||||
use crate::env::GetConfig;
|
||||
use crate::monero;
|
||||
use crate::network::test::{await_events_or_timeout, connect, new_swarm};
|
||||
use crate::protocol::{alice, bob};
|
||||
@ -294,6 +321,7 @@ mod tests {
|
||||
max_buy: bitcoin::Amount::from_btc(0.01).unwrap(),
|
||||
rate: TestRate::default(), // 0.01
|
||||
resume_only: false,
|
||||
env_config: env::Testnet::get_config(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -305,9 +333,7 @@ mod tests {
|
||||
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||
let expected_xmr = monero::Amount::from_monero(1.0).unwrap();
|
||||
|
||||
let request = spot_price::Request { btc: btc_to_swap };
|
||||
|
||||
test.send_request(request);
|
||||
test.construct_and_send_request(btc_to_swap);
|
||||
test.assert_price((btc_to_swap, expected_xmr), expected_xmr)
|
||||
.await;
|
||||
}
|
||||
@ -321,9 +347,7 @@ mod tests {
|
||||
|
||||
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||
|
||||
let request = spot_price::Request { btc: btc_to_swap };
|
||||
|
||||
test.send_request(request);
|
||||
test.construct_and_send_request(btc_to_swap);
|
||||
test.assert_error(
|
||||
alice::spot_price::Error::BalanceTooLow {
|
||||
balance: monero::Amount::ZERO,
|
||||
@ -341,9 +365,7 @@ mod tests {
|
||||
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||
let expected_xmr = monero::Amount::from_monero(1.0).unwrap();
|
||||
|
||||
let request = spot_price::Request { btc: btc_to_swap };
|
||||
|
||||
test.send_request(request);
|
||||
test.construct_and_send_request(btc_to_swap);
|
||||
test.assert_price((btc_to_swap, expected_xmr), expected_xmr)
|
||||
.await;
|
||||
|
||||
@ -351,9 +373,7 @@ mod tests {
|
||||
.behaviour_mut()
|
||||
.update_balance(monero::Amount::ZERO);
|
||||
|
||||
let request = spot_price::Request { btc: btc_to_swap };
|
||||
|
||||
test.send_request(request);
|
||||
test.construct_and_send_request(btc_to_swap);
|
||||
test.assert_error(
|
||||
alice::spot_price::Error::BalanceTooLow {
|
||||
balance: monero::Amount::ZERO,
|
||||
@ -376,10 +396,7 @@ mod tests {
|
||||
.await;
|
||||
|
||||
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||
|
||||
let request = spot_price::Request { btc: btc_to_swap };
|
||||
|
||||
test.send_request(request);
|
||||
test.construct_and_send_request(btc_to_swap);
|
||||
test.assert_error(
|
||||
alice::spot_price::Error::BalanceTooLow {
|
||||
balance,
|
||||
@ -398,10 +415,7 @@ mod tests {
|
||||
SpotPriceTest::setup(AliceBehaviourValues::default().with_min_buy(min_buy)).await;
|
||||
|
||||
let btc_to_swap = bitcoin::Amount::from_btc(0.0001).unwrap();
|
||||
|
||||
let request = spot_price::Request { btc: btc_to_swap };
|
||||
|
||||
test.send_request(request);
|
||||
test.construct_and_send_request(btc_to_swap);
|
||||
test.assert_error(
|
||||
alice::spot_price::Error::AmountBelowMinimum {
|
||||
buy: btc_to_swap,
|
||||
@ -424,9 +438,7 @@ mod tests {
|
||||
|
||||
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||
|
||||
let request = spot_price::Request { btc: btc_to_swap };
|
||||
|
||||
test.send_request(request);
|
||||
test.construct_and_send_request(btc_to_swap);
|
||||
test.assert_error(
|
||||
alice::spot_price::Error::AmountAboveMaximum {
|
||||
buy: btc_to_swap,
|
||||
@ -446,10 +458,7 @@ mod tests {
|
||||
SpotPriceTest::setup(AliceBehaviourValues::default().with_resume_only(true)).await;
|
||||
|
||||
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||
|
||||
let request = spot_price::Request { btc: btc_to_swap };
|
||||
|
||||
test.send_request(request);
|
||||
test.construct_and_send_request(btc_to_swap);
|
||||
test.assert_error(
|
||||
alice::spot_price::Error::ResumeOnlyMode,
|
||||
bob::spot_price::Error::NoSwapsAccepted,
|
||||
@ -464,10 +473,7 @@ mod tests {
|
||||
.await;
|
||||
|
||||
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||
|
||||
let request = spot_price::Request { btc: btc_to_swap };
|
||||
|
||||
test.send_request(request);
|
||||
test.construct_and_send_request(btc_to_swap);
|
||||
test.assert_error(
|
||||
alice::spot_price::Error::LatestRateFetchFailed(Box::new(TestRateError {})),
|
||||
bob::spot_price::Error::Other,
|
||||
@ -484,9 +490,7 @@ mod tests {
|
||||
|
||||
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||
|
||||
let request = spot_price::Request { btc: btc_to_swap };
|
||||
|
||||
test.send_request(request);
|
||||
test.construct_and_send_request(btc_to_swap);
|
||||
test.assert_error(
|
||||
alice::spot_price::Error::SellQuoteCalculationFailed(anyhow!(
|
||||
"Error text irrelevant, won't be checked here"
|
||||
@ -496,6 +500,79 @@ mod tests {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_alice_mainnnet_bob_testnet_then_network_mismatch_error() {
|
||||
let mut test = SpotPriceTest::setup(
|
||||
AliceBehaviourValues::default().with_env_config(env::Mainnet::get_config()),
|
||||
)
|
||||
.await;
|
||||
|
||||
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||
test.construct_and_send_request(btc_to_swap);
|
||||
test.assert_error(
|
||||
alice::spot_price::Error::BlockchainNetworkMismatch {
|
||||
cli: BlockchainNetwork {
|
||||
bitcoin: bitcoin::Network::Testnet,
|
||||
monero: monero::Network::Stagenet,
|
||||
},
|
||||
asb: BlockchainNetwork {
|
||||
bitcoin: bitcoin::Network::Bitcoin,
|
||||
monero: monero::Network::Mainnet,
|
||||
},
|
||||
},
|
||||
bob::spot_price::Error::BlockchainNetworkMismatch {
|
||||
cli: BlockchainNetwork {
|
||||
bitcoin: bitcoin::Network::Testnet,
|
||||
monero: monero::Network::Stagenet,
|
||||
},
|
||||
asb: BlockchainNetwork {
|
||||
bitcoin: bitcoin::Network::Bitcoin,
|
||||
monero: monero::Network::Mainnet,
|
||||
},
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_alice_testnet_bob_mainnet_then_network_mismatch_error() {
|
||||
let mut test = SpotPriceTest::setup(AliceBehaviourValues::default()).await;
|
||||
|
||||
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||
let request = spot_price::Request {
|
||||
btc: btc_to_swap,
|
||||
blockchain_network: BlockchainNetwork {
|
||||
bitcoin: bitcoin::Network::Bitcoin,
|
||||
monero: monero::Network::Mainnet,
|
||||
},
|
||||
};
|
||||
|
||||
test.send_request(request);
|
||||
test.assert_error(
|
||||
alice::spot_price::Error::BlockchainNetworkMismatch {
|
||||
cli: BlockchainNetwork {
|
||||
bitcoin: bitcoin::Network::Bitcoin,
|
||||
monero: monero::Network::Mainnet,
|
||||
},
|
||||
asb: BlockchainNetwork {
|
||||
bitcoin: bitcoin::Network::Testnet,
|
||||
monero: monero::Network::Stagenet,
|
||||
},
|
||||
},
|
||||
bob::spot_price::Error::BlockchainNetworkMismatch {
|
||||
cli: BlockchainNetwork {
|
||||
bitcoin: bitcoin::Network::Bitcoin,
|
||||
monero: monero::Network::Mainnet,
|
||||
},
|
||||
asb: BlockchainNetwork {
|
||||
bitcoin: bitcoin::Network::Testnet,
|
||||
monero: monero::Network::Stagenet,
|
||||
},
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
struct SpotPriceTest {
|
||||
alice_swarm: Swarm<alice::spot_price::Behaviour<TestRate>>,
|
||||
bob_swarm: Swarm<spot_price::Behaviour>,
|
||||
@ -511,6 +588,7 @@ mod tests {
|
||||
values.lock_fee,
|
||||
values.min_buy,
|
||||
values.max_buy,
|
||||
values.env_config,
|
||||
values.rate.clone(),
|
||||
values.resume_only,
|
||||
)
|
||||
@ -526,6 +604,17 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn construct_and_send_request(&mut self, btc_to_swap: bitcoin::Amount) {
|
||||
let request = spot_price::Request {
|
||||
btc: btc_to_swap,
|
||||
blockchain_network: BlockchainNetwork {
|
||||
bitcoin: bitcoin::Network::Testnet,
|
||||
monero: monero::Network::Stagenet,
|
||||
},
|
||||
};
|
||||
self.send_request(request);
|
||||
}
|
||||
|
||||
pub fn send_request(&mut self, spot_price_request: spot_price::Request) {
|
||||
self.bob_swarm
|
||||
.behaviour_mut()
|
||||
@ -588,6 +677,19 @@ mod tests {
|
||||
assert_eq!(balance1, balance2);
|
||||
assert_eq!(buy1, buy2);
|
||||
}
|
||||
(
|
||||
alice::spot_price::Error::BlockchainNetworkMismatch {
|
||||
cli: cli1,
|
||||
asb: asb1,
|
||||
},
|
||||
alice::spot_price::Error::BlockchainNetworkMismatch {
|
||||
cli: cli2,
|
||||
asb: asb2,
|
||||
},
|
||||
) => {
|
||||
assert_eq!(cli1, cli2);
|
||||
assert_eq!(asb1, asb2);
|
||||
}
|
||||
(
|
||||
alice::spot_price::Error::AmountBelowMinimum { .. },
|
||||
alice::spot_price::Error::AmountBelowMinimum { .. },
|
||||
@ -640,6 +742,7 @@ mod tests {
|
||||
pub max_buy: bitcoin::Amount,
|
||||
pub rate: TestRate, // 0.01
|
||||
pub resume_only: bool,
|
||||
pub env_config: env::Config,
|
||||
}
|
||||
|
||||
impl AliceBehaviourValues {
|
||||
@ -672,6 +775,11 @@ mod tests {
|
||||
self.rate = rate;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_env_config(mut self, env_config: env::Config) -> AliceBehaviourValues {
|
||||
self.env_config = env_config;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::bitcoin::EncryptedSignature;
|
||||
use crate::network::quote::BidQuote;
|
||||
use crate::network::spot_price::Response;
|
||||
use crate::network::spot_price::{BlockchainNetwork, Response};
|
||||
use crate::network::{encrypted_signature, spot_price};
|
||||
use crate::protocol::bob;
|
||||
use crate::protocol::bob::{Behaviour, OutEvent, State0, State2};
|
||||
use crate::{bitcoin, monero};
|
||||
use crate::{bitcoin, env, monero};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use futures::future::{BoxFuture, OptionFuture};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
@ -55,6 +55,7 @@ impl EventLoop {
|
||||
swarm: Swarm<Behaviour>,
|
||||
alice_peer_id: PeerId,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
env_config: env::Config,
|
||||
) -> Result<(Self, EventLoopHandle)> {
|
||||
let execution_setup = bmrng::channel_with_timeout(1, Duration::from_secs(30));
|
||||
let transfer_proof = bmrng::channel_with_timeout(1, Duration::from_secs(30));
|
||||
@ -85,6 +86,7 @@ impl EventLoop {
|
||||
encrypted_signature: encrypted_signature.0,
|
||||
spot_price: spot_price.0,
|
||||
quote: quote.0,
|
||||
env_config,
|
||||
};
|
||||
|
||||
Ok((event_loop, handle))
|
||||
@ -242,6 +244,7 @@ pub struct EventLoopHandle {
|
||||
encrypted_signature: bmrng::RequestSender<EncryptedSignature, ()>,
|
||||
spot_price: bmrng::RequestSender<spot_price::Request, spot_price::Response>,
|
||||
quote: bmrng::RequestSender<(), BidQuote>,
|
||||
env_config: env::Config,
|
||||
}
|
||||
|
||||
impl EventLoopHandle {
|
||||
@ -265,7 +268,13 @@ impl EventLoopHandle {
|
||||
pub async fn request_spot_price(&mut self, btc: bitcoin::Amount) -> Result<monero::Amount> {
|
||||
let response = self
|
||||
.spot_price
|
||||
.send_receive(spot_price::Request { btc })
|
||||
.send_receive(spot_price::Request {
|
||||
btc,
|
||||
blockchain_network: BlockchainNetwork {
|
||||
bitcoin: self.env_config.bitcoin_network,
|
||||
monero: self.env_config.monero_network,
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
|
||||
match response {
|
||||
|
@ -54,6 +54,12 @@ pub enum Error {
|
||||
#[error("Seller's XMR balance is currently too low to fulfill the swap request to buy {buy}, please try again later")]
|
||||
BalanceTooLow { buy: bitcoin::Amount },
|
||||
|
||||
#[error("Seller blockchain network {asb:?} setup did not match your blockchain network setup {cli:?}")]
|
||||
BlockchainNetworkMismatch {
|
||||
cli: spot_price::BlockchainNetwork,
|
||||
asb: spot_price::BlockchainNetwork,
|
||||
},
|
||||
|
||||
/// To be used for errors that cannot be explained on the CLI side (e.g.
|
||||
/// rate update problems on the seller side)
|
||||
#[error("Seller encountered a problem, please try again later.")]
|
||||
@ -71,6 +77,9 @@ impl From<spot_price::Error> for Error {
|
||||
Error::AmountAboveMaximum { max, buy }
|
||||
}
|
||||
spot_price::Error::BalanceTooLow { buy } => Error::BalanceTooLow { buy },
|
||||
spot_price::Error::BlockchainNetworkMismatch { cli, asb } => {
|
||||
Error::BlockchainNetworkMismatch { cli, asb }
|
||||
}
|
||||
spot_price::Error::Other => Error::Other,
|
||||
}
|
||||
}
|
||||
|
@ -239,6 +239,7 @@ async fn start_alice(
|
||||
max_buy,
|
||||
latest_rate,
|
||||
resume_only,
|
||||
env_config,
|
||||
)
|
||||
.unwrap();
|
||||
swarm.listen_on(listen_address).unwrap();
|
||||
@ -458,6 +459,7 @@ impl BobParams {
|
||||
swarm,
|
||||
self.alice_peer_id,
|
||||
self.bitcoin_wallet.clone(),
|
||||
self.env_config,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user