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
|
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
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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);
|
||||||
|
@ -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 {
|
||||||
|
@ -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(
|
||||||
|
@ -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,
|
||||||
|
@ -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
@ -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::*;
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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);
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
|
@ -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 { .. }
|
||||||
|
@ -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)]
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user