Network check upon spot price request

This commit is contained in:
Daniel Karzel 2021-05-11 22:22:59 +10:00
parent 02974811ad
commit af60d3bb54
No known key found for this signature in database
GPG Key ID: 30C3FC2E438ADB6E
15 changed files with 245 additions and 76 deletions

View File

@ -1,6 +1,5 @@
use crate::env::{Mainnet, Testnet}; 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::monero;
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};
use config::ConfigError; use config::ConfigError;
@ -121,6 +120,8 @@ 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, pub network: bitcoin::Network,
} }
@ -128,6 +129,8 @@ pub struct Bitcoin {
#[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, pub network: monero::Network,
} }
@ -335,7 +338,7 @@ mod tests {
monero: Monero { monero: Monero {
wallet_rpc_url: defaults.monero_wallet_rpc_url, wallet_rpc_url: defaults.monero_wallet_rpc_url,
network: monero::Network::Testnet, network: monero::Network::Stagenet,
}, },
tor: Default::default(), tor: Default::default(),
maker: Maker { maker: Maker {

View File

@ -151,6 +151,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

@ -82,8 +82,13 @@ async fn main() -> Result<()> {
.add_address(seller_peer_id, seller_addr); .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, seller_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());
@ -151,7 +156,7 @@ async fn main() -> Result<()> {
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")?;
if monero_receive_address.network != env_config.monero_network.into() { if monero_receive_address.network != env_config.monero_network {
bail!("The given monero address is on network {:?}, expected address of network {:?}.", monero_receive_address.network, env_config.monero_network) bail!("The given monero address is on network {:?}, expected address of network {:?}.", monero_receive_address.network, env_config.monero_network)
} }
@ -176,8 +181,13 @@ async fn main() -> Result<()> {
.behaviour_mut() .behaviour_mut()
.add_address(seller_peer_id, seller_addr); .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, seller_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(
@ -294,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.into(), monero_daemon_address.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

@ -1,5 +1,4 @@
use crate::bitcoin::{CancelTimelock, PunishTimelock}; use crate::bitcoin::{CancelTimelock, PunishTimelock};
use crate::monero;
use std::cmp::max; use std::cmp::max;
use std::time::Duration; use std::time::Duration;
use time::NumericalStdDurationShort; use time::NumericalStdDurationShort;

View File

@ -1,6 +1,7 @@
pub mod wallet; pub mod wallet;
mod wallet_rpc; mod wallet_rpc;
pub use ::monero::network::Network;
pub use ::monero::{Address, PrivateKey, PublicKey}; 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;
@ -19,33 +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(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub enum Network { #[serde(remote = "Network")]
#[allow(non_camel_case_types)]
pub enum network {
Mainnet, Mainnet,
Stagenet, Stagenet,
Testnet, Testnet,
} }
impl From<monero::Network> for Network {
fn from(network: monero::Network) -> Self {
match network {
monero::Network::Mainnet => Self::Mainnet,
monero::Network::Stagenet => Self::Stagenet,
monero::Network::Testnet => Self::Testnet,
}
}
}
impl From<Network> for monero::Network {
fn from(network: Network) -> Self {
match network {
Network::Mainnet => monero::Network::Mainnet,
Network::Stagenet => monero::Network::Stagenet,
Network::Testnet => monero::Network::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

@ -47,7 +47,7 @@ impl Wallet {
monero::Address::from_str(client.get_address(0).await?.address.as_str())?; monero::Address::from_str(client.get_address(0).await?.address.as_str())?;
Ok(Self { Ok(Self {
inner: Mutex::new(client), inner: Mutex::new(client),
network: env_config.monero_network.into(), network: env_config.monero_network,
name, name,
main_address, main_address,
sync_interval: env_config.monero_sync_interval(), sync_interval: env_config.monero_sync_interval(),

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,
) )
} }
} }