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:
bors[bot] 2021-05-20 04:16:55 +00:00 committed by GitHub
commit bdb88f89cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1469 additions and 344 deletions

View File

@ -43,11 +43,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1. Balance of ASB too low 1. Balance of ASB too low
2. Buy amount sent by CLI exceeds maximum buy amount accepted by ASB 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 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 ### Changed
- The ASB's `--max-buy` and `ask-spread` parameter were removed in favour of entries in the config file. - 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. 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 ## [0.5.0] - 2021-04-17

View File

@ -10,6 +10,9 @@ use uuid::Uuid;
author author
)] )]
pub struct Arguments { pub struct Arguments {
#[structopt(long, help = "Swap on testnet")]
pub testnet: bool,
#[structopt( #[structopt(
short, short,
long = "json", long = "json",

View File

@ -1,3 +1,4 @@
use crate::env::{Mainnet, Testnet};
use crate::fs::{ensure_directory_exists, system_config_dir, system_data_dir}; use crate::fs::{ensure_directory_exists, system_config_dir, system_data_dir};
use crate::tor::{DEFAULT_CONTROL_PORT, DEFAULT_SOCKS5_PORT}; use crate::tor::{DEFAULT_CONTROL_PORT, DEFAULT_SOCKS5_PORT};
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
@ -11,14 +12,71 @@ use serde::{Deserialize, Serialize};
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr;
use tracing::info; use tracing::info;
use url::Url; use url::Url;
const DEFAULT_LISTEN_ADDRESS_TCP: &str = "/ip4/0.0.0.0/tcp/9939"; pub trait GetDefaults {
const DEFAULT_LISTEN_ADDRESS_WS: &str = "/ip4/0.0.0.0/tcp/9940/ws"; fn getConfigFileDefaults() -> Result<Defaults>;
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 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_MIN_BUY_AMOUNT: f64 = 0.002f64;
const DEFAULT_MAX_BUY_AMOUNT: f64 = 0.02f64; const DEFAULT_MAX_BUY_AMOUNT: f64 = 0.02f64;
@ -64,12 +122,18 @@ pub struct Network {
pub struct Bitcoin { pub struct Bitcoin {
pub electrum_rpc_url: Url, pub electrum_rpc_url: Url,
pub target_block: usize, pub target_block: usize,
pub finality_confirmations: Option<u32>,
#[serde(with = "crate::bitcoin::network")]
pub network: bitcoin::Network,
} }
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Monero { pub struct Monero {
pub wallet_rpc_url: Url, 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)] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
@ -118,31 +182,8 @@ pub fn read_config(config_path: PathBuf) -> Result<Result<Config, ConfigNotIniti
Ok(Ok(file)) Ok(Ok(file))
} }
/// Default location for storing the config file for the ASB pub fn initial_setup(config_path: PathBuf, config: Config) -> Result<()> {
// Takes the default system config-dir and adds a `/asb/config.toml` let toml = toml::to_string(&config)?;
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)?;
ensure_directory_exists(config_path.as_path())?; ensure_directory_exists(config_path.as_path())?;
fs::write(&config_path, toml)?; fs::write(&config_path, toml)?;
@ -154,13 +195,30 @@ where
Ok(()) 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!(); println!();
let data_dir = Input::with_theme(&ColorfulTheme::default()) let data_dir = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter data directory for asb or hit return to use default") .with_prompt("Enter data directory for asb or hit return to use default")
.default( .default(
default_data_dir() defaults
.context("No default data dir value for this system")? .data_dir
.to_str() .to_str()
.context("Unsupported characters in default path")? .context("Unsupported characters in default path")?
.to_string(), .to_string(),
@ -170,28 +228,27 @@ pub fn query_user_for_initial_testnet_config() -> Result<Config> {
let target_block = Input::with_theme(&ColorfulTheme::default()) 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") .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()?; .interact_text()?;
let listen_addresses = Input::with_theme(&ColorfulTheme::default()) 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") .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()?; .interact_text()?;
let listen_addresses = listen_addresses let listen_addresses = listen_addresses
.split(',') .split(',')
.map(|str| str.parse()) .map(|str| str.parse())
.collect::<Result<Vec<Multiaddr>, _>>()?; .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") .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()?; .interact_text()?;
let electrum_rpc_url = Url::parse(electrum_rpc_url.as_str())?;
let monero_wallet_rpc_url = Input::with_theme(&ColorfulTheme::default()) let monero_wallet_rpc_url = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter Monero Wallet RPC URL or hit enter to use 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()?; .interact_text()?;
let monero_wallet_rpc_url = monero_wallet_rpc_url.as_str().parse()?;
let tor_control_port = Input::with_theme(&ColorfulTheme::default()) 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.") .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 { bitcoin: Bitcoin {
electrum_rpc_url, electrum_rpc_url,
target_block, target_block,
finality_confirmations: None,
network: bitcoin_network,
}, },
monero: Monero { monero: Monero {
wallet_rpc_url: monero_wallet_rpc_url, wallet_rpc_url: monero_wallet_rpc_url,
finality_confirmations: None,
network: monero_network,
}, },
tor: TorConf { tor: TorConf {
control_port: tor_control_port, control_port: tor_control_port,
@ -253,31 +314,33 @@ pub fn query_user_for_initial_testnet_config() -> Result<Config> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::str::FromStr;
use tempfile::tempdir; use tempfile::tempdir;
#[test] #[test]
fn config_roundtrip() { fn config_roundtrip_testnet() {
let temp_dir = tempdir().unwrap().path().to_path_buf(); let temp_dir = tempdir().unwrap().path().to_path_buf();
let config_path = Path::join(&temp_dir, "config.toml"); let config_path = Path::join(&temp_dir, "config.toml");
let defaults = Testnet::getConfigFileDefaults().unwrap();
let expected = Config { let expected = Config {
data: Data { data: Data {
dir: Default::default(), dir: Default::default(),
}, },
bitcoin: Bitcoin { bitcoin: Bitcoin {
electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(), electrum_rpc_url: defaults.electrum_rpc_url,
target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET, target_block: defaults.bitcoin_confirmation_target,
finality_confirmations: None,
network: bitcoin::Network::Testnet,
}, },
network: Network { network: Network {
listen: vec![ listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
DEFAULT_LISTEN_ADDRESS_TCP.parse().unwrap(),
DEFAULT_LISTEN_ADDRESS_WS.parse().unwrap(),
],
}, },
monero: Monero { 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(), tor: Default::default(),
maker: Maker { 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(); let actual = read_config(config_path).unwrap().unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);

View File

@ -12,7 +12,7 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![allow(non_snake_case)] #![allow(non_snake_case)]
use anyhow::{Context, Result}; use anyhow::{bail, Context, Result};
use libp2p::core::multiaddr::Protocol; use libp2p::core::multiaddr::Protocol;
use libp2p::core::Multiaddr; use libp2p::core::Multiaddr;
use libp2p::Swarm; use libp2p::Swarm;
@ -22,11 +22,10 @@ use std::sync::Arc;
use structopt::StructOpt; use structopt::StructOpt;
use swap::asb::command::{Arguments, Command, ManualRecovery, RecoverCommandParams}; use swap::asb::command::{Arguments, Command, ManualRecovery, RecoverCommandParams};
use swap::asb::config::{ use swap::asb::config::{
default_config_path, initial_setup, query_user_for_initial_testnet_config, read_config, Config, initial_setup, query_user_for_initial_config, read_config, Config, ConfigNotInitialized,
ConfigNotInitialized, GetDefaults,
}; };
use swap::database::Database; use swap::database::Database;
use swap::env::GetConfig;
use swap::monero::Amount; use swap::monero::Amount;
use swap::network::swarm; use swap::network::swarm;
use swap::protocol::alice; use swap::protocol::alice;
@ -45,23 +44,45 @@ const DEFAULT_WALLET_NAME: &str = "asb-wallet";
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
let opt = Arguments::from_args(); let Arguments {
asb::tracing::init(LevelFilter::DEBUG, opt.json).expect("initialize tracing"); 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 config_path
} else if testnet {
env::Testnet::getConfigFileDefaults()?.config_path
} else { } else {
default_config_path()? env::Mainnet::getConfigFileDefaults()?.config_path
}; };
let config = match read_config(config_path.clone())? { let config = match read_config(config_path.clone())? {
Ok(config) => config, Ok(config) => config,
Err(ConfigNotInitialized {}) => { 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") 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!( info!(
db_folder = %config.data.dir.display(), db_folder = %config.data.dir.display(),
"Database and Seed will be stored in", "Database and Seed will be stored in",
@ -75,9 +96,7 @@ async fn main() -> Result<()> {
let seed = let seed =
Seed::from_file_or_generate(&config.data.dir).expect("Could not retrieve/initialize seed"); Seed::from_file_or_generate(&config.data.dir).expect("Could not retrieve/initialize seed");
let env_config = env::Testnet::get_config(); match cmd {
match opt.cmd {
Command::Start { resume_only } => { Command::Start { resume_only } => {
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
@ -127,6 +146,7 @@ async fn main() -> Result<()> {
config.maker.max_buy_btc, config.maker.max_buy_btc,
kraken_rate.clone(), kraken_rate.clone(),
resume_only, resume_only,
env_config,
)?; )?;
for listen in config.network.listen { for listen in config.network.listen {

View File

@ -15,21 +15,21 @@
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use prettytable::{row, Table}; use prettytable::{row, Table};
use std::cmp::min; use std::cmp::min;
use std::env;
use std::future::Future; use std::future::Future;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use structopt::StructOpt;
use swap::bitcoin::TxLock; 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::database::Database;
use swap::env::{Config, GetConfig}; use swap::env::Config;
use swap::network::quote::BidQuote; use swap::network::quote::BidQuote;
use swap::network::swarm; use swap::network::swarm;
use swap::protocol::bob; use swap::protocol::bob;
use swap::protocol::bob::{EventLoop, Swap}; use swap::protocol::bob::{EventLoop, Swap};
use swap::seed::Seed; use swap::seed::Seed;
use swap::{bitcoin, cli, env, monero}; use swap::{bitcoin, cli, monero};
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
use url::Url; use url::Url;
use uuid::Uuid; use uuid::Uuid;
@ -39,41 +39,33 @@ extern crate prettytable;
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { 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 { match cmd {
Command::BuyXmr { Command::BuyXmr {
alice_peer_id, seller_peer_id,
alice_multiaddr, seller_addr,
monero_params: bitcoin_electrum_rpc_url,
MoneroParams {
receive_monero_address,
monero_daemon_host,
},
electrum_rpc_url,
tor_socks5_port,
bitcoin_target_block, bitcoin_target_block,
monero_receive_address,
monero_daemon_address,
tor_socks5_port,
} => { } => {
let swap_id = Uuid::new_v4(); let swap_id = Uuid::new_v4();
let data_dir = data.0;
cli::tracing::init(debug, data_dir.join("logs"), swap_id)?; cli::tracing::init(debug, data_dir.join("logs"), swap_id)?;
let db = Database::open(data_dir.join("database").as_path()) let db = Database::open(data_dir.join("database").as_path())
.context("Failed to open database")?; .context("Failed to open database")?;
let seed = Seed::from_file_or_generate(data_dir.as_path()) let seed = Seed::from_file_or_generate(data_dir.as_path())
.context("Failed to read in seed file")?; .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( let bitcoin_wallet = init_bitcoin_wallet(
electrum_rpc_url, bitcoin_electrum_rpc_url,
&seed, &seed,
data_dir.clone(), data_dir.clone(),
env_config, env_config,
@ -81,17 +73,22 @@ async fn main() -> Result<()> {
) )
.await?; .await?;
let (monero_wallet, _process) = 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 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 swarm
.behaviour_mut() .behaviour_mut()
.add_address(alice_peer_id, alice_multiaddr); .add_address(seller_peer_id, seller_addr);
let swap_id = Uuid::new_v4(); let swap_id = Uuid::new_v4();
let (event_loop, mut event_loop_handle) = let (event_loop, mut event_loop_handle) = EventLoop::new(
EventLoop::new(swap_id, swarm, alice_peer_id, bitcoin_wallet.clone())?; swap_id,
swarm,
seller_peer_id,
bitcoin_wallet.clone(),
env_config,
)?;
let event_loop = tokio::spawn(event_loop.run()); let event_loop = tokio::spawn(event_loop.run());
let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size()); 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); 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( let swap = Swap::new(
db, db,
@ -115,7 +112,7 @@ async fn main() -> Result<()> {
Arc::new(monero_wallet), Arc::new(monero_wallet),
env_config, env_config,
event_loop_handle, event_loop_handle,
receive_monero_address, monero_receive_address,
send_bitcoin, send_bitcoin,
); );
@ -130,8 +127,6 @@ async fn main() -> Result<()> {
} }
} }
Command::History => { Command::History => {
let data_dir = data.0;
let db = Database::open(data_dir.join("database").as_path()) let db = Database::open(data_dir.join("database").as_path())
.context("Failed to open database")?; .context("Failed to open database")?;
@ -148,30 +143,25 @@ async fn main() -> Result<()> {
} }
Command::Resume { Command::Resume {
swap_id, swap_id,
alice_multiaddr, seller_addr,
monero_params: bitcoin_electrum_rpc_url,
MoneroParams {
receive_monero_address,
monero_daemon_host,
},
electrum_rpc_url,
tor_socks5_port,
bitcoin_target_block, 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)?; cli::tracing::init(debug, data_dir.join("logs"), swap_id)?;
let db = Database::open(data_dir.join("database").as_path()) let db = Database::open(data_dir.join("database").as_path())
.context("Failed to open database")?; .context("Failed to open database")?;
let seed = Seed::from_file_or_generate(data_dir.as_path()) let seed = Seed::from_file_or_generate(data_dir.as_path())
.context("Failed to read in seed file")?; .context("Failed to read in seed file")?;
let env_config = env::Testnet::get_config();
if 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 {:?}.", receive_monero_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( let bitcoin_wallet = init_bitcoin_wallet(
electrum_rpc_url, bitcoin_electrum_rpc_url,
&seed, &seed,
data_dir.clone(), data_dir.clone(),
env_config, env_config,
@ -179,20 +169,25 @@ async fn main() -> Result<()> {
) )
.await?; .await?;
let (monero_wallet, _process) = 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 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(); let bob_peer_id = swarm.local_peer_id();
tracing::debug!("Our peer-id: {}", bob_peer_id); tracing::debug!("Our peer-id: {}", bob_peer_id);
swarm swarm
.behaviour_mut() .behaviour_mut()
.add_address(alice_peer_id, alice_multiaddr); .add_address(seller_peer_id, seller_addr);
let (event_loop, event_loop_handle) = let (event_loop, event_loop_handle) = EventLoop::new(
EventLoop::new(swap_id, swarm, alice_peer_id, bitcoin_wallet.clone())?; swap_id,
swarm,
seller_peer_id,
bitcoin_wallet.clone(),
env_config,
)?;
let handle = tokio::spawn(event_loop.run()); let handle = tokio::spawn(event_loop.run());
let swap = Swap::from_db( let swap = Swap::from_db(
@ -202,7 +197,7 @@ async fn main() -> Result<()> {
Arc::new(monero_wallet), Arc::new(monero_wallet),
env_config, env_config,
event_loop_handle, event_loop_handle,
receive_monero_address, monero_receive_address,
)?; )?;
tokio::select! { tokio::select! {
@ -217,19 +212,17 @@ async fn main() -> Result<()> {
Command::Cancel { Command::Cancel {
swap_id, swap_id,
force, force,
electrum_rpc_url, bitcoin_electrum_rpc_url,
bitcoin_target_block, bitcoin_target_block,
} => { } => {
let data_dir = data.0;
cli::tracing::init(debug, data_dir.join("logs"), swap_id)?; cli::tracing::init(debug, data_dir.join("logs"), swap_id)?;
let db = Database::open(data_dir.join("database").as_path()) let db = Database::open(data_dir.join("database").as_path())
.context("Failed to open database")?; .context("Failed to open database")?;
let seed = Seed::from_file_or_generate(data_dir.as_path()) let seed = Seed::from_file_or_generate(data_dir.as_path())
.context("Failed to read in seed file")?; .context("Failed to read in seed file")?;
let env_config = env::Testnet::get_config();
let bitcoin_wallet = init_bitcoin_wallet( let bitcoin_wallet = init_bitcoin_wallet(
electrum_rpc_url, bitcoin_electrum_rpc_url,
&seed, &seed,
data_dir, data_dir,
env_config, env_config,
@ -251,19 +244,17 @@ async fn main() -> Result<()> {
Command::Refund { Command::Refund {
swap_id, swap_id,
force, force,
electrum_rpc_url, bitcoin_electrum_rpc_url,
bitcoin_target_block, bitcoin_target_block,
} => { } => {
let data_dir = data.0;
cli::tracing::init(debug, data_dir.join("logs"), swap_id)?; cli::tracing::init(debug, data_dir.join("logs"), swap_id)?;
let db = Database::open(data_dir.join("database").as_path()) let db = Database::open(data_dir.join("database").as_path())
.context("Failed to open database")?; .context("Failed to open database")?;
let seed = Seed::from_file_or_generate(data_dir.as_path()) let seed = Seed::from_file_or_generate(data_dir.as_path())
.context("Failed to read in seed file")?; .context("Failed to read in seed file")?;
let env_config = env::Testnet::get_config();
let bitcoin_wallet = init_bitcoin_wallet( let bitcoin_wallet = init_bitcoin_wallet(
electrum_rpc_url, bitcoin_electrum_rpc_url,
&seed, &seed,
data_dir, data_dir,
env_config, env_config,
@ -303,7 +294,7 @@ async fn init_bitcoin_wallet(
async fn init_monero_wallet( async fn init_monero_wallet(
data_dir: PathBuf, data_dir: PathBuf,
monero_daemon_host: String, monero_daemon_address: String,
env_config: Config, env_config: Config,
) -> Result<(monero::Wallet, monero::WalletRpcProcess)> { ) -> Result<(monero::Wallet, monero::WalletRpcProcess)> {
let network = env_config.monero_network; 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 = monero::WalletRpc::new(data_dir.join("monero")).await?;
let monero_wallet_rpc_process = monero_wallet_rpc let monero_wallet_rpc_process = monero_wallet_rpc
.run(network, monero_daemon_host.as_str()) .run(network, monero_daemon_address.as_str())
.await?; .await?;
let monero_wallet = monero::Wallet::open_or_create( let monero_wallet = monero::Wallet::open_or_create(

View File

@ -37,6 +37,17 @@ use serde::{Deserialize, Serialize};
use sha2::Sha256; use sha2::Sha256;
use std::str::FromStr; 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)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct SecretKey { pub struct SecretKey {
inner: Scalar, inner: Scalar,

View File

@ -16,6 +16,7 @@ use reqwest::Url;
use rust_decimal::prelude::*; use rust_decimal::prelude::*;
use rust_decimal::Decimal; use rust_decimal::Decimal;
use rust_decimal_macros::dec; use rust_decimal_macros::dec;
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt; use std::fmt;
@ -108,18 +109,6 @@ impl Wallet {
Ok((txid, subscription)) 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> { pub async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction> {
self.get_tx(txid) self.get_tx(txid)
.await? .await?
@ -257,6 +246,18 @@ where
C: EstimateFeeRate, C: EstimateFeeRate,
D: BatchDatabase, 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> { pub async fn balance(&self) -> Result<Amount> {
let balance = self let balance = self
.wallet .wallet
@ -293,6 +294,10 @@ where
Ok(Amount::from_sat(fees)) 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( pub async fn send_to_address(
&self, &self,
address: Address, address: Address,
@ -301,11 +306,30 @@ where
let wallet = self.wallet.lock().await; let wallet = self.wallet.lock().await;
let client = self.client.lock().await; let client = self.client.lock().await;
let fee_rate = client.estimate_feerate(self.target_block)?; let fee_rate = client.estimate_feerate(self.target_block)?;
let script = address.script_pubkey();
let mut tx_builder = wallet.build_tx(); 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); tx_builder.fee_rate(fee_rate);
let (psbt, _details) = tx_builder.finish()?; 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) Ok(psbt)
} }
@ -480,7 +504,7 @@ where
use bitcoin::OutPoint; use bitcoin::OutPoint;
use testutils::testutils; use testutils::testutils;
let descriptors = testutils!(@descriptors ("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)")); let descriptors = testutils!(@descriptors ("wpkh(tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m/*)"));
let mut database = MemoryDatabase::new(); let mut database = MemoryDatabase::new();
bdk::populate_test_db!( bdk::populate_test_db!(
@ -527,59 +551,39 @@ impl Watchable for (Txid, Script) {
pub struct Client { pub struct Client {
electrum: bdk::electrum_client::Client, electrum: bdk::electrum_client::Client,
latest_block: BlockHeight, latest_block_height: BlockHeight,
last_ping: Instant, last_sync: Instant,
interval: Duration, sync_interval: Duration,
script_history: BTreeMap<Script, Vec<GetHistoryRes>>, script_history: BTreeMap<Script, Vec<GetHistoryRes>>,
subscriptions: HashMap<(Txid, Script), Subscription>, subscriptions: HashMap<(Txid, Script), Subscription>,
} }
impl Client { impl Client {
fn new(electrum: bdk::electrum_client::Client, interval: Duration) -> Result<Self> { 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 let latest_block = electrum
.block_headers_subscribe() .block_headers_subscribe()
.context("Failed to subscribe to header notifications")?; .context("Failed to subscribe to header notifications")?;
Ok(Self { Ok(Self {
electrum, electrum,
latest_block: BlockHeight::try_from(latest_block)?, latest_block_height: BlockHeight::try_from(latest_block)?,
last_ping: Instant::now(), last_sync: Instant::now(),
interval, sync_interval: interval,
script_history: Default::default(), script_history: Default::default(),
subscriptions: Default::default(), subscriptions: Default::default(),
}) })
} }
/// Ping the electrum server unless we already did within the set interval. fn update_state(&mut self) -> Result<()> {
/// let now = Instant::now();
/// Returns a boolean indicating whether we actually pinged the server. if now < self.last_sync + self.sync_interval {
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 {
return Ok(()); return Ok(());
} }
self.drain_blockheight_notifications()?; self.last_sync = now;
self.update_latest_block()?;
self.update_script_histories()?; self.update_script_histories()?;
Ok(()) Ok(())
@ -596,7 +600,7 @@ impl Client {
self.script_history.insert(script.clone(), vec![]); self.script_history.insert(script.clone(), vec![]);
} }
self.drain_notifications()?; self.update_state()?;
let history = self.script_history.entry(script).or_default(); let history = self.script_history.entry(script).or_default();
@ -618,7 +622,7 @@ impl Client {
Ok(ScriptStatus::Confirmed( Ok(ScriptStatus::Confirmed(
Confirmed::from_inclusion_and_latest_block( Confirmed::from_inclusion_and_latest_block(
u32::try_from(last.height)?, 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<()> { fn update_latest_block(&mut self) -> Result<()> {
let latest_block = std::iter::from_fn(|| self.electrum.block_headers_pop().transpose()) // Fetch the latest block for storing the height.
.last() // We do not act on this subscription after this call, as we cannot rely on
.transpose() // subscription push notifications because eventually the Electrum server will
.context("Failed to pop header notification")?; // 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!( tracing::debug!(
block_height = new_block.height, block_height = u32::from(latest_block_height),
"Got notification for new block" "Got notification for new block"
); );
self.latest_block = BlockHeight::try_from(new_block)?; self.latest_block_height = latest_block_height;
} }
Ok(()) Ok(())
@ -774,7 +784,7 @@ impl fmt::Display for ScriptStatus {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::bitcoin::TxLock; use crate::bitcoin::{PublicKey, TxLock};
use proptest::prelude::*; use proptest::prelude::*;
#[test] #[test]
@ -1004,4 +1014,51 @@ mod tests {
assert!(amount.as_sat() > 0); 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

View File

@ -1,9 +1,10 @@
use crate::asb;
use crate::bitcoin::{CancelTimelock, PunishTimelock}; use crate::bitcoin::{CancelTimelock, PunishTimelock};
use std::cmp::max; use std::cmp::max;
use std::time::Duration; use std::time::Duration;
use time::NumericalStdDurationShort; use time::NumericalStdDurationShort;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct Config { pub struct Config {
pub bitcoin_lock_confirmed_timeout: Duration, pub bitcoin_lock_confirmed_timeout: Duration,
pub bitcoin_finality_confirmations: u32, pub bitcoin_finality_confirmations: u32,
@ -43,13 +44,13 @@ impl GetConfig for Mainnet {
fn get_config() -> Config { fn get_config() -> Config {
Config { Config {
bitcoin_lock_confirmed_timeout: 24.hours(), bitcoin_lock_confirmed_timeout: 24.hours(),
bitcoin_finality_confirmations: 3, bitcoin_finality_confirmations: 2,
bitcoin_avg_block_time: 10.minutes(), bitcoin_avg_block_time: 10.minutes(),
bitcoin_cancel_timelock: CancelTimelock::new(72), bitcoin_cancel_timelock: CancelTimelock::new(72),
bitcoin_punish_timelock: PunishTimelock::new(72), bitcoin_punish_timelock: PunishTimelock::new(72),
bitcoin_network: bitcoin::Network::Bitcoin, bitcoin_network: bitcoin::Network::Bitcoin,
monero_avg_block_time: 2.minutes(), monero_avg_block_time: 2.minutes(),
monero_finality_confirmations: 15, monero_finality_confirmations: 10,
monero_network: monero::Network::Mainnet, monero_network: monero::Network::Mainnet,
} }
} }
@ -59,8 +60,8 @@ impl GetConfig for Testnet {
fn get_config() -> Config { fn get_config() -> Config {
Config { Config {
bitcoin_lock_confirmed_timeout: 12.hours(), bitcoin_lock_confirmed_timeout: 12.hours(),
bitcoin_finality_confirmations: 1, bitcoin_finality_confirmations: 2,
bitcoin_avg_block_time: 5.minutes(), bitcoin_avg_block_time: 10.minutes(),
bitcoin_cancel_timelock: CancelTimelock::new(12), bitcoin_cancel_timelock: CancelTimelock::new(12),
bitcoin_punish_timelock: PunishTimelock::new(6), bitcoin_punish_timelock: PunishTimelock::new(6),
bitcoin_network: bitcoin::Network::Testnet, 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)) 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -1,7 +1,8 @@
pub mod wallet; pub mod wallet;
mod wallet_rpc; 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 curve25519_dalek::scalar::Scalar;
pub use wallet::Wallet; pub use wallet::Wallet;
pub use wallet_rpc::{WalletRpc, WalletRpcProcess}; pub use wallet_rpc::{WalletRpc, WalletRpcProcess};
@ -19,6 +20,15 @@ use std::str::FromStr;
pub const PICONERO_OFFSET: u64 = 1_000_000_000_000; 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 { pub fn private_key_from_secp256k1_scalar(scalar: bitcoin::Scalar) -> PrivateKey {
let mut bytes = scalar.to_bytes(); let mut bytes = scalar.to_bytes();

View File

@ -115,7 +115,7 @@ impl WalletRpc {
Ok(monero_wallet_rpc) 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") let port = tokio::net::TcpListener::bind("127.0.0.1:0")
.await? .await?
.local_addr()? .local_addr()?
@ -126,17 +126,25 @@ impl WalletRpc {
"Starting monero-wallet-rpc on" "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()) let mut child = Command::new(self.exec_path())
.env("LANG", "en_AU.UTF-8") .env("LANG", "en_AU.UTF-8")
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.kill_on_drop(true) .kill_on_drop(true)
.arg(match network { .args(network_flag)
Network::Mainnet => "--mainnet", .arg("--daemon-address")
Network::Stagenet => "--stagenet", .arg(daemon_address)
Network::Testnet => "--testnet",
})
.arg("--daemon-host")
.arg(daemon_host)
.arg("--rpc-bind-port") .arg("--rpc-bind-port")
.arg(format!("{}", port)) .arg(format!("{}", port))
.arg("--disable-rpc-login") .arg("--disable-rpc-login")

View File

@ -32,6 +32,7 @@ impl ProtocolName for SpotPriceProtocol {
pub struct Request { pub struct Request {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
pub btc: bitcoin::Amount, pub btc: bitcoin::Amount,
pub blockchain_network: BlockchainNetwork,
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
@ -59,11 +60,23 @@ pub enum Error {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")] #[serde(with = "::bitcoin::util::amount::serde::as_sat")]
buy: bitcoin::Amount, buy: bitcoin::Amount,
}, },
BlockchainNetworkMismatch {
cli: BlockchainNetwork,
asb: BlockchainNetwork,
},
/// To be used for errors that cannot be explained on the CLI side (e.g. /// To be used for errors that cannot be explained on the CLI side (e.g.
/// rate update problems on the seller side) /// rate update problems on the seller side)
Other, 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -103,6 +116,21 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!(error, serialized); 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 error = r#"{"Error":"Other"}"#.to_string();
let serialized = serde_json::to_string(&Response::Error(Error::Other)).unwrap(); let serialized = serde_json::to_string(&Response::Error(Error::Other)).unwrap();
assert_eq!(error, serialized); assert_eq!(error, serialized);

View File

@ -2,12 +2,13 @@ use crate::network::transport;
use crate::protocol::alice::event_loop::LatestRate; use crate::protocol::alice::event_loop::LatestRate;
use crate::protocol::{alice, bob}; use crate::protocol::{alice, bob};
use crate::seed::Seed; use crate::seed::Seed;
use crate::{monero, tor}; use crate::{env, monero, tor};
use anyhow::Result; use anyhow::Result;
use libp2p::swarm::{NetworkBehaviour, SwarmBuilder}; use libp2p::swarm::{NetworkBehaviour, SwarmBuilder};
use libp2p::{PeerId, Swarm}; use libp2p::{PeerId, Swarm};
use std::fmt::Debug; use std::fmt::Debug;
#[allow(clippy::too_many_arguments)]
pub fn alice<LR>( pub fn alice<LR>(
seed: &Seed, seed: &Seed,
balance: monero::Amount, balance: monero::Amount,
@ -16,6 +17,7 @@ pub fn alice<LR>(
max_buy: bitcoin::Amount, max_buy: bitcoin::Amount,
latest_rate: LR, latest_rate: LR,
resume_only: bool, resume_only: bool,
env_config: env::Config,
) -> Result<Swarm<alice::Behaviour<LR>>> ) -> Result<Swarm<alice::Behaviour<LR>>>
where where
LR: LatestRate + Send + 'static + Debug, LR: LatestRate + Send + 'static + Debug,
@ -29,6 +31,7 @@ where
max_buy, max_buy,
latest_rate, latest_rate,
resume_only, resume_only,
env_config,
), ),
) )
} }

View File

@ -1,8 +1,8 @@
use crate::monero;
use crate::network::quote::BidQuote; use crate::network::quote::BidQuote;
use crate::network::{encrypted_signature, quote, transfer_proof}; use crate::network::{encrypted_signature, quote, transfer_proof};
use crate::protocol::alice::event_loop::LatestRate; use crate::protocol::alice::event_loop::LatestRate;
use crate::protocol::alice::{execution_setup, spot_price, State3}; use crate::protocol::alice::{execution_setup, spot_price, State3};
use crate::{env, monero};
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use libp2p::request_response::{RequestId, ResponseChannel}; use libp2p::request_response::{RequestId, ResponseChannel};
use libp2p::{NetworkBehaviour, PeerId}; use libp2p::{NetworkBehaviour, PeerId};
@ -88,6 +88,7 @@ where
max_buy: bitcoin::Amount, max_buy: bitcoin::Amount,
latest_rate: LR, latest_rate: LR,
resume_only: bool, resume_only: bool,
env_config: env::Config,
) -> Self { ) -> Self {
Self { Self {
quote: quote::alice(), quote: quote::alice(),
@ -96,6 +97,7 @@ where
lock_fee, lock_fee,
min_buy, min_buy,
max_buy, max_buy,
env_config,
latest_rate, latest_rate,
resume_only, resume_only,
), ),

View File

@ -211,7 +211,8 @@ where
match error { match error {
Error::ResumeOnlyMode Error::ResumeOnlyMode
| Error::AmountBelowMinimum { .. } | Error::AmountBelowMinimum { .. }
| Error::AmountAboveMaximum { .. } => { | Error::AmountAboveMaximum { .. }
| Error::BlockchainNetworkMismatch { .. } => {
tracing::warn!(%peer, "Ignoring spot price request because: {}", error); tracing::warn!(%peer, "Ignoring spot price request because: {}", error);
} }
Error::BalanceTooLow { .. } Error::BalanceTooLow { .. }

View File

@ -1,9 +1,9 @@
use crate::monero;
use crate::network::cbor_request_response::CborCodec; use crate::network::cbor_request_response::CborCodec;
use crate::network::spot_price; 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;
use crate::protocol::alice::event_loop::LatestRate; use crate::protocol::alice::event_loop::LatestRate;
use crate::{env, monero};
use libp2p::request_response::{ use libp2p::request_response::{
ProtocolSupport, RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, ProtocolSupport, RequestResponseConfig, RequestResponseEvent, RequestResponseMessage,
ResponseChannel, ResponseChannel,
@ -48,6 +48,8 @@ where
#[behaviour(ignore)] #[behaviour(ignore)]
max_buy: bitcoin::Amount, max_buy: bitcoin::Amount,
#[behaviour(ignore)] #[behaviour(ignore)]
env_config: env::Config,
#[behaviour(ignore)]
latest_rate: LR, latest_rate: LR,
#[behaviour(ignore)] #[behaviour(ignore)]
resume_only: bool, resume_only: bool,
@ -66,6 +68,7 @@ where
lock_fee: monero::Amount, lock_fee: monero::Amount,
min_buy: bitcoin::Amount, min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount, max_buy: bitcoin::Amount,
env_config: env::Config,
latest_rate: LR, latest_rate: LR,
resume_only: bool, resume_only: bool,
) -> Self { ) -> Self {
@ -80,6 +83,7 @@ where
lock_fee, lock_fee,
min_buy, min_buy,
max_buy, max_buy,
env_config,
latest_rate, latest_rate,
resume_only, 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 { if self.resume_only {
self.decline(peer, channel, Error::ResumeOnlyMode); self.decline(peer, channel, Error::ResumeOnlyMode);
return; return;
@ -246,12 +263,15 @@ pub enum Error {
balance: monero::Amount, balance: monero::Amount,
buy: bitcoin::Amount, buy: bitcoin::Amount,
}, },
#[error("Failed to fetch latest rate")] #[error("Failed to fetch latest rate")]
LatestRateFetchFailed(#[source] Box<dyn std::error::Error + Send + 'static>), LatestRateFetchFailed(#[source] Box<dyn std::error::Error + Send + 'static>),
#[error("Failed to calculate quote: {0}")] #[error("Failed to calculate quote: {0}")]
SellQuoteCalculationFailed(#[source] anyhow::Error), 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 { impl Error {
@ -267,6 +287,12 @@ impl Error {
buy: *buy, buy: *buy,
}, },
Error::BalanceTooLow { buy, .. } => spot_price::Error::BalanceTooLow { 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(_) => { Error::LatestRateFetchFailed(_) | Error::SellQuoteCalculationFailed(_) => {
spot_price::Error::Other spot_price::Error::Other
} }
@ -278,6 +304,7 @@ impl Error {
mod tests { mod tests {
use super::*; use super::*;
use crate::asb::Rate; use crate::asb::Rate;
use crate::env::GetConfig;
use crate::monero; use crate::monero;
use crate::network::test::{await_events_or_timeout, connect, new_swarm}; use crate::network::test::{await_events_or_timeout, connect, new_swarm};
use crate::protocol::{alice, bob}; use crate::protocol::{alice, bob};
@ -294,6 +321,7 @@ mod tests {
max_buy: bitcoin::Amount::from_btc(0.01).unwrap(), max_buy: bitcoin::Amount::from_btc(0.01).unwrap(),
rate: TestRate::default(), // 0.01 rate: TestRate::default(), // 0.01
resume_only: false, 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 btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
let expected_xmr = monero::Amount::from_monero(1.0).unwrap(); let expected_xmr = monero::Amount::from_monero(1.0).unwrap();
let request = spot_price::Request { btc: btc_to_swap }; test.construct_and_send_request(btc_to_swap);
test.send_request(request);
test.assert_price((btc_to_swap, expected_xmr), expected_xmr) test.assert_price((btc_to_swap, expected_xmr), expected_xmr)
.await; .await;
} }
@ -321,9 +347,7 @@ mod tests {
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
let request = spot_price::Request { btc: btc_to_swap }; test.construct_and_send_request(btc_to_swap);
test.send_request(request);
test.assert_error( test.assert_error(
alice::spot_price::Error::BalanceTooLow { alice::spot_price::Error::BalanceTooLow {
balance: monero::Amount::ZERO, balance: monero::Amount::ZERO,
@ -341,9 +365,7 @@ mod tests {
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
let expected_xmr = monero::Amount::from_monero(1.0).unwrap(); let expected_xmr = monero::Amount::from_monero(1.0).unwrap();
let request = spot_price::Request { btc: btc_to_swap }; test.construct_and_send_request(btc_to_swap);
test.send_request(request);
test.assert_price((btc_to_swap, expected_xmr), expected_xmr) test.assert_price((btc_to_swap, expected_xmr), expected_xmr)
.await; .await;
@ -351,9 +373,7 @@ mod tests {
.behaviour_mut() .behaviour_mut()
.update_balance(monero::Amount::ZERO); .update_balance(monero::Amount::ZERO);
let request = spot_price::Request { btc: btc_to_swap }; test.construct_and_send_request(btc_to_swap);
test.send_request(request);
test.assert_error( test.assert_error(
alice::spot_price::Error::BalanceTooLow { alice::spot_price::Error::BalanceTooLow {
balance: monero::Amount::ZERO, balance: monero::Amount::ZERO,
@ -376,10 +396,7 @@ mod tests {
.await; .await;
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
test.construct_and_send_request(btc_to_swap);
let request = spot_price::Request { btc: btc_to_swap };
test.send_request(request);
test.assert_error( test.assert_error(
alice::spot_price::Error::BalanceTooLow { alice::spot_price::Error::BalanceTooLow {
balance, balance,
@ -398,10 +415,7 @@ mod tests {
SpotPriceTest::setup(AliceBehaviourValues::default().with_min_buy(min_buy)).await; SpotPriceTest::setup(AliceBehaviourValues::default().with_min_buy(min_buy)).await;
let btc_to_swap = bitcoin::Amount::from_btc(0.0001).unwrap(); let btc_to_swap = bitcoin::Amount::from_btc(0.0001).unwrap();
test.construct_and_send_request(btc_to_swap);
let request = spot_price::Request { btc: btc_to_swap };
test.send_request(request);
test.assert_error( test.assert_error(
alice::spot_price::Error::AmountBelowMinimum { alice::spot_price::Error::AmountBelowMinimum {
buy: btc_to_swap, buy: btc_to_swap,
@ -424,9 +438,7 @@ mod tests {
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
let request = spot_price::Request { btc: btc_to_swap }; test.construct_and_send_request(btc_to_swap);
test.send_request(request);
test.assert_error( test.assert_error(
alice::spot_price::Error::AmountAboveMaximum { alice::spot_price::Error::AmountAboveMaximum {
buy: btc_to_swap, buy: btc_to_swap,
@ -446,10 +458,7 @@ mod tests {
SpotPriceTest::setup(AliceBehaviourValues::default().with_resume_only(true)).await; SpotPriceTest::setup(AliceBehaviourValues::default().with_resume_only(true)).await;
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
test.construct_and_send_request(btc_to_swap);
let request = spot_price::Request { btc: btc_to_swap };
test.send_request(request);
test.assert_error( test.assert_error(
alice::spot_price::Error::ResumeOnlyMode, alice::spot_price::Error::ResumeOnlyMode,
bob::spot_price::Error::NoSwapsAccepted, bob::spot_price::Error::NoSwapsAccepted,
@ -464,10 +473,7 @@ mod tests {
.await; .await;
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
test.construct_and_send_request(btc_to_swap);
let request = spot_price::Request { btc: btc_to_swap };
test.send_request(request);
test.assert_error( test.assert_error(
alice::spot_price::Error::LatestRateFetchFailed(Box::new(TestRateError {})), alice::spot_price::Error::LatestRateFetchFailed(Box::new(TestRateError {})),
bob::spot_price::Error::Other, bob::spot_price::Error::Other,
@ -484,9 +490,7 @@ mod tests {
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap(); let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
let request = spot_price::Request { btc: btc_to_swap }; test.construct_and_send_request(btc_to_swap);
test.send_request(request);
test.assert_error( test.assert_error(
alice::spot_price::Error::SellQuoteCalculationFailed(anyhow!( alice::spot_price::Error::SellQuoteCalculationFailed(anyhow!(
"Error text irrelevant, won't be checked here" "Error text irrelevant, won't be checked here"
@ -496,6 +500,79 @@ mod tests {
.await; .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 { struct SpotPriceTest {
alice_swarm: Swarm<alice::spot_price::Behaviour<TestRate>>, alice_swarm: Swarm<alice::spot_price::Behaviour<TestRate>>,
bob_swarm: Swarm<spot_price::Behaviour>, bob_swarm: Swarm<spot_price::Behaviour>,
@ -511,6 +588,7 @@ mod tests {
values.lock_fee, values.lock_fee,
values.min_buy, values.min_buy,
values.max_buy, values.max_buy,
values.env_config,
values.rate.clone(), values.rate.clone(),
values.resume_only, 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) { pub fn send_request(&mut self, spot_price_request: spot_price::Request) {
self.bob_swarm self.bob_swarm
.behaviour_mut() .behaviour_mut()
@ -588,6 +677,19 @@ mod tests {
assert_eq!(balance1, balance2); assert_eq!(balance1, balance2);
assert_eq!(buy1, buy2); 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 { .. },
alice::spot_price::Error::AmountBelowMinimum { .. }, alice::spot_price::Error::AmountBelowMinimum { .. },
@ -640,6 +742,7 @@ mod tests {
pub max_buy: bitcoin::Amount, pub max_buy: bitcoin::Amount,
pub rate: TestRate, // 0.01 pub rate: TestRate, // 0.01
pub resume_only: bool, pub resume_only: bool,
pub env_config: env::Config,
} }
impl AliceBehaviourValues { impl AliceBehaviourValues {
@ -672,6 +775,11 @@ mod tests {
self.rate = rate; self.rate = rate;
self self
} }
pub fn with_env_config(mut self, env_config: env::Config) -> AliceBehaviourValues {
self.env_config = env_config;
self
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View File

@ -1,10 +1,10 @@
use crate::bitcoin::EncryptedSignature; use crate::bitcoin::EncryptedSignature;
use crate::network::quote::BidQuote; 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::network::{encrypted_signature, spot_price};
use crate::protocol::bob; use crate::protocol::bob;
use crate::protocol::bob::{Behaviour, OutEvent, State0, State2}; use crate::protocol::bob::{Behaviour, OutEvent, State0, State2};
use crate::{bitcoin, monero}; use crate::{bitcoin, env, monero};
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use futures::future::{BoxFuture, OptionFuture}; use futures::future::{BoxFuture, OptionFuture};
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
@ -55,6 +55,7 @@ impl EventLoop {
swarm: Swarm<Behaviour>, swarm: Swarm<Behaviour>,
alice_peer_id: PeerId, alice_peer_id: PeerId,
bitcoin_wallet: Arc<bitcoin::Wallet>, bitcoin_wallet: Arc<bitcoin::Wallet>,
env_config: env::Config,
) -> Result<(Self, EventLoopHandle)> { ) -> Result<(Self, EventLoopHandle)> {
let execution_setup = bmrng::channel_with_timeout(1, Duration::from_secs(30)); let execution_setup = bmrng::channel_with_timeout(1, Duration::from_secs(30));
let transfer_proof = 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, encrypted_signature: encrypted_signature.0,
spot_price: spot_price.0, spot_price: spot_price.0,
quote: quote.0, quote: quote.0,
env_config,
}; };
Ok((event_loop, handle)) Ok((event_loop, handle))
@ -242,6 +244,7 @@ pub struct EventLoopHandle {
encrypted_signature: bmrng::RequestSender<EncryptedSignature, ()>, encrypted_signature: bmrng::RequestSender<EncryptedSignature, ()>,
spot_price: bmrng::RequestSender<spot_price::Request, spot_price::Response>, spot_price: bmrng::RequestSender<spot_price::Request, spot_price::Response>,
quote: bmrng::RequestSender<(), BidQuote>, quote: bmrng::RequestSender<(), BidQuote>,
env_config: env::Config,
} }
impl EventLoopHandle { impl EventLoopHandle {
@ -265,7 +268,13 @@ impl EventLoopHandle {
pub async fn request_spot_price(&mut self, btc: bitcoin::Amount) -> Result<monero::Amount> { pub async fn request_spot_price(&mut self, btc: bitcoin::Amount) -> Result<monero::Amount> {
let response = self let response = self
.spot_price .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?; .await?;
match response { match response {

View File

@ -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")] #[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 }, 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. /// To be used for errors that cannot be explained on the CLI side (e.g.
/// rate update problems on the seller side) /// rate update problems on the seller side)
#[error("Seller encountered a problem, please try again later.")] #[error("Seller encountered a problem, please try again later.")]
@ -71,6 +77,9 @@ impl From<spot_price::Error> for Error {
Error::AmountAboveMaximum { max, buy } Error::AmountAboveMaximum { max, buy }
} }
spot_price::Error::BalanceTooLow { buy } => Error::BalanceTooLow { 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, spot_price::Error::Other => Error::Other,
} }
} }

View File

@ -239,6 +239,7 @@ async fn start_alice(
max_buy, max_buy,
latest_rate, latest_rate,
resume_only, resume_only,
env_config,
) )
.unwrap(); .unwrap();
swarm.listen_on(listen_address).unwrap(); swarm.listen_on(listen_address).unwrap();
@ -458,6 +459,7 @@ impl BobParams {
swarm, swarm,
self.alice_peer_id, self.alice_peer_id,
self.bitcoin_wallet.clone(), self.bitcoin_wallet.clone(),
self.env_config,
) )
} }
} }