586: `swap_setup` instead of `spot_price` and `execution_setup` r=da-kami a=da-kami

Having `spot_price` and `execution_setup` as separate protocols did not bring any advantages, but was problematic because we had to ensure that `execution_setup` would be triggered after `spot_price`. Because of this dependency it is better to combine the protocols into one.

Combining the protocols also allows a refactoring to get rid of the `libp2p-async-await` dependency.

Alice always listens for the `swap_setup` protocol. When Bob opens a substream on that protocol the spot price is communicated, and then all execution setup messages (swap-id and signature exchange).

Co-authored-by: Daniel Karzel <daniel@comit.network>
pull/590/head
bors[bot] 3 years ago committed by GitHub
commit 620ac569f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

32
Cargo.lock generated

@ -1768,26 +1768,6 @@ version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba4aede83fc3617411dc6993bc8c70919750c1c257c6ca6a502aed6e0e2394ae" checksum = "ba4aede83fc3617411dc6993bc8c70919750c1c257c6ca6a502aed6e0e2394ae"
[[package]]
name = "libp2p"
version = "0.37.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08053fbef67cd777049ef7a95ebaca2ece370b4ed7712c3fa404d69a88cb741b"
dependencies = [
"atomic",
"bytes 1.0.1",
"futures",
"lazy_static",
"libp2p-core",
"libp2p-swarm",
"libp2p-swarm-derive",
"parity-multiaddr",
"parking_lot 0.11.1",
"pin-project 1.0.5",
"smallvec",
"wasm-timer",
]
[[package]] [[package]]
name = "libp2p" name = "libp2p"
version = "0.38.0" version = "0.38.0"
@ -1816,15 +1796,6 @@ dependencies = [
"wasm-timer", "wasm-timer",
] ]
[[package]]
name = "libp2p-async-await"
version = "0.1.0"
source = "git+https://github.com/comit-network/rust-libp2p-async-await#50e781b12bbeda7986c0cada090f171f41093144"
dependencies = [
"libp2p 0.37.1",
"log 0.4.14",
]
[[package]] [[package]]
name = "libp2p-core" name = "libp2p-core"
version = "0.28.3" version = "0.28.3"
@ -3946,8 +3917,7 @@ dependencies = [
"get-port", "get-port",
"hyper 0.14.9", "hyper 0.14.9",
"itertools 0.10.1", "itertools 0.10.1",
"libp2p 0.38.0", "libp2p",
"libp2p-async-await",
"miniscript", "miniscript",
"monero", "monero",
"monero-harness", "monero-harness",

@ -30,7 +30,6 @@ ed25519-dalek = "1"
futures = { version = "0.3", default-features = false } futures = { version = "0.3", default-features = false }
itertools = "0.10" itertools = "0.10"
libp2p = { version = "0.38", default-features = false, features = [ "tcp-tokio", "yamux", "mplex", "dns-tokio", "noise", "request-response", "websocket", "ping" ] } libp2p = { version = "0.38", default-features = false, features = [ "tcp-tokio", "yamux", "mplex", "dns-tokio", "noise", "request-response", "websocket", "ping" ] }
libp2p-async-await = { git = "https://github.com/comit-network/rust-libp2p-async-await" }
miniscript = { version = "5", features = [ "serde" ] } miniscript = { version = "5", features = [ "serde" ] }
monero = { version = "0.12", features = [ "serde_support" ] } monero = { version = "0.12", features = [ "serde_support" ] }
monero-rpc = { path = "../monero-rpc" } monero-rpc = { path = "../monero-rpc" }

@ -1,7 +1,18 @@
mod behaviour;
pub mod command; pub mod command;
pub mod config; pub mod config;
mod event_loop;
mod rate; mod rate;
mod recovery;
pub mod tracing; pub mod tracing;
pub mod transport; pub mod transport;
pub use behaviour::{Behaviour, OutEvent};
pub use event_loop::{EventLoop, EventLoopHandle, FixedRate, KrakenRate, LatestRate};
pub use rate::Rate; pub use rate::Rate;
pub use recovery::cancel::cancel;
pub use recovery::punish::punish;
pub use recovery::redeem::{redeem, Finality};
pub use recovery::refund::refund;
pub use recovery::safely_abort::safely_abort;
pub use recovery::{cancel, refund};

@ -1,8 +1,10 @@
use crate::asb::event_loop::LatestRate;
use crate::env;
use crate::network::quote::BidQuote; use crate::network::quote::BidQuote;
use crate::network::swap_setup::alice;
use crate::network::swap_setup::alice::WalletSnapshot;
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::State3;
use crate::protocol::alice::{execution_setup, spot_price, State3};
use crate::{env, monero};
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use libp2p::ping::{Ping, PingEvent}; use libp2p::ping::{Ping, PingEvent};
use libp2p::request_response::{RequestId, ResponseChannel}; use libp2p::request_response::{RequestId, ResponseChannel};
@ -11,24 +13,22 @@ use uuid::Uuid;
#[derive(Debug)] #[derive(Debug)]
pub enum OutEvent { pub enum OutEvent {
SwapRequestDeclined { SwapSetupInitiated {
peer: PeerId, send_wallet_snapshot: bmrng::RequestReceiver<bitcoin::Amount, WalletSnapshot>,
error: spot_price::Error, },
SwapSetupCompleted {
peer_id: PeerId,
swap_id: Uuid,
state3: Box<State3>,
}, },
ExecutionSetupStart { SwapDeclined {
peer: PeerId, peer: PeerId,
btc: bitcoin::Amount, error: alice::Error,
xmr: monero::Amount,
}, },
QuoteRequested { QuoteRequested {
channel: ResponseChannel<BidQuote>, channel: ResponseChannel<BidQuote>,
peer: PeerId, peer: PeerId,
}, },
ExecutionSetupDone {
bob_peer_id: PeerId,
swap_id: Uuid,
state3: Box<State3>,
},
TransferProofAcknowledged { TransferProofAcknowledged {
peer: PeerId, peer: PeerId,
id: RequestId, id: RequestId,
@ -72,8 +72,7 @@ where
LR: LatestRate + Send + 'static, LR: LatestRate + Send + 'static,
{ {
pub quote: quote::Behaviour, pub quote: quote::Behaviour,
pub spot_price: spot_price::Behaviour<LR>, pub swap_setup: alice::Behaviour<LR>,
pub execution_setup: execution_setup::Behaviour,
pub transfer_proof: transfer_proof::Behaviour, pub transfer_proof: transfer_proof::Behaviour,
pub encrypted_signature: encrypted_signature::Behaviour, pub encrypted_signature: encrypted_signature::Behaviour,
@ -88,8 +87,6 @@ where
LR: LatestRate + Send + 'static, LR: LatestRate + Send + 'static,
{ {
pub fn new( pub fn new(
balance: monero::Amount,
lock_fee: monero::Amount,
min_buy: bitcoin::Amount, min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount, max_buy: bitcoin::Amount,
latest_rate: LR, latest_rate: LR,
@ -97,17 +94,14 @@ where
env_config: env::Config, env_config: env::Config,
) -> Self { ) -> Self {
Self { Self {
quote: quote::alice(), quote: quote::asb(),
spot_price: spot_price::Behaviour::new( swap_setup: alice::Behaviour::new(
balance,
lock_fee,
min_buy, min_buy,
max_buy, max_buy,
env_config, env_config,
latest_rate, latest_rate,
resume_only, resume_only,
), ),
execution_setup: Default::default(),
transfer_proof: transfer_proof::alice(), transfer_proof: transfer_proof::alice(),
encrypted_signature: encrypted_signature::alice(), encrypted_signature: encrypted_signature::alice(),
ping: Ping::default(), ping: Ping::default(),

@ -1,9 +1,11 @@
use crate::asb::behaviour::{Behaviour, OutEvent};
use crate::asb::Rate; use crate::asb::Rate;
use crate::database::Database; use crate::database::Database;
use crate::env::Config; use crate::env::Config;
use crate::network::quote::BidQuote; use crate::network::quote::BidQuote;
use crate::network::swap_setup::alice::WalletSnapshot;
use crate::network::transfer_proof; use crate::network::transfer_proof;
use crate::protocol::alice::{AliceState, Behaviour, OutEvent, State0, State3, Swap}; use crate::protocol::alice::{AliceState, State3, Swap};
use crate::{bitcoin, kraken, monero}; use crate::{bitcoin, kraken, monero};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use futures::future; use futures::future;
@ -12,7 +14,6 @@ use futures::stream::{FuturesUnordered, StreamExt};
use libp2p::request_response::{RequestId, ResponseChannel}; use libp2p::request_response::{RequestId, ResponseChannel};
use libp2p::swarm::SwarmEvent; use libp2p::swarm::SwarmEvent;
use libp2p::{PeerId, Swarm}; use libp2p::{PeerId, Swarm};
use rand::rngs::OsRng;
use rust_decimal::Decimal; use rust_decimal::Decimal;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::Infallible; use std::convert::Infallible;
@ -34,7 +35,7 @@ type OutgoingTransferProof =
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct EventLoop<LR> pub struct EventLoop<LR>
where where
LR: LatestRate + Send + 'static + Debug, LR: LatestRate + Send + 'static + Debug + Clone,
{ {
swarm: libp2p::Swarm<Behaviour<LR>>, swarm: libp2p::Swarm<Behaviour<LR>>,
env_config: Config, env_config: Config,
@ -64,7 +65,7 @@ where
impl<LR> EventLoop<LR> impl<LR> EventLoop<LR>
where where
LR: LatestRate + Send + 'static + Debug, LR: LatestRate + Send + 'static + Debug + Clone,
{ {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
@ -150,77 +151,34 @@ where
tokio::select! { tokio::select! {
swarm_event = self.swarm.next_event() => { swarm_event = self.swarm.next_event() => {
match swarm_event { match swarm_event {
SwarmEvent::Behaviour(OutEvent::ExecutionSetupStart { peer, btc, xmr }) => { SwarmEvent::Behaviour(OutEvent::SwapSetupInitiated { mut send_wallet_snapshot }) => {
let tx_redeem_fee = self.bitcoin_wallet
.estimate_fee(bitcoin::TxRedeem::weight(), btc)
.await;
let tx_punish_fee = self.bitcoin_wallet
.estimate_fee(bitcoin::TxPunish::weight(), btc)
.await;
let redeem_address = self.bitcoin_wallet.new_address().await;
let punish_address = self.bitcoin_wallet.new_address().await;
let (redeem_address, punish_address) = match (
redeem_address,
punish_address,
) {
(Ok(redeem_address), Ok(punish_address)) => {
(redeem_address, punish_address)
}
_ => {
tracing::error!(%peer, "Failed to get new address during execution setup");
continue;
}
};
let (tx_redeem_fee, tx_punish_fee) = match ( let (btc, responder) = match send_wallet_snapshot.recv().await {
tx_redeem_fee, Ok((btc, responder)) => (btc, responder),
tx_punish_fee, Err(error) => {
) { tracing::error!("Swap request will be ignored because of a failure when requesting information for the wallet snapshot: {:#}", error);
(Ok(tx_redeem_fee), Ok(tx_punish_fee)) => {
(tx_redeem_fee, tx_punish_fee)
}
_ => {
tracing::error!(%peer, "Failed to calculate transaction fees during execution setup");
continue; continue;
} }
}; };
let state0 = match State0::new( let wallet_snapshot = match WalletSnapshot::capture(&self.bitcoin_wallet, &self.monero_wallet, btc).await {
btc, Ok(wallet_snapshot) => wallet_snapshot,
xmr,
self.env_config,
redeem_address,
punish_address,
tx_redeem_fee,
tx_punish_fee,
&mut OsRng
) {
Ok(state) => state,
Err(error) => { Err(error) => {
tracing::warn!(%peer, "Failed to make State0 for execution setup. Error {:#}", error); tracing::error!("Swap request will be ignored because we were unable to create wallet snapshot for swap: {:#}", error);
continue; continue;
} }
}; };
self.swarm.behaviour_mut().execution_setup.run(peer, state0); // Ignore result, we should never hit this because the receiver will alive as long as the connection is.
let _ = responder.respond(wallet_snapshot);
} }
SwarmEvent::Behaviour(OutEvent::SwapRequestDeclined { peer, error }) => { SwarmEvent::Behaviour(OutEvent::SwapSetupCompleted{peer_id, swap_id, state3}) => {
let _ = self.handle_execution_setup_done(peer_id, swap_id, *state3).await;
}
SwarmEvent::Behaviour(OutEvent::SwapDeclined { peer, error }) => {
tracing::warn!(%peer, "Ignoring spot price request because: {}", error); tracing::warn!(%peer, "Ignoring spot price request because: {}", error);
} }
SwarmEvent::Behaviour(OutEvent::QuoteRequested { channel, peer }) => { SwarmEvent::Behaviour(OutEvent::QuoteRequested { channel, peer }) => {
// TODO: Move the spot-price update into dedicated update stream to decouple it from quote requests
let current_balance = self.monero_wallet.get_balance().await;
match current_balance {
Ok(balance) => {
self.swarm.behaviour_mut().spot_price.update_balance(balance);
}
Err(e) => {
tracing::error!("Failed to fetch Monero balance: {:#}", e);
}
}
let quote = match self.make_quote(self.min_buy, self.max_buy).await { let quote = match self.make_quote(self.min_buy, self.max_buy).await {
Ok(quote) => quote, Ok(quote) => quote,
Err(error) => { Err(error) => {
@ -233,9 +191,6 @@ where
tracing::debug!(%peer, "Failed to respond with quote"); tracing::debug!(%peer, "Failed to respond with quote");
} }
} }
SwarmEvent::Behaviour(OutEvent::ExecutionSetupDone{bob_peer_id, swap_id, state3}) => {
let _ = self.handle_execution_setup_done(bob_peer_id, swap_id, *state3).await;
}
SwarmEvent::Behaviour(OutEvent::TransferProofAcknowledged { peer, id }) => { SwarmEvent::Behaviour(OutEvent::TransferProofAcknowledged { peer, id }) => {
tracing::debug!(%peer, "Bob acknowledged transfer proof"); tracing::debug!(%peer, "Bob acknowledged transfer proof");
if let Some(responder) = self.inflight_transfer_proofs.remove(&id) { if let Some(responder) = self.inflight_transfer_proofs.remove(&id) {

@ -26,12 +26,11 @@ use swap::asb::command::{parse_args, Arguments, Command};
use swap::asb::config::{ use swap::asb::config::{
initial_setup, query_user_for_initial_config, read_config, Config, ConfigNotInitialized, initial_setup, query_user_for_initial_config, read_config, Config, ConfigNotInitialized,
}; };
use swap::asb::{cancel, punish, redeem, refund, safely_abort, EventLoop, Finality, KrakenRate};
use swap::database::Database; use swap::database::Database;
use swap::monero::Amount; use swap::monero::Amount;
use swap::network::swarm; use swap::network::swarm;
use swap::protocol::alice; use swap::protocol::alice::run;
use swap::protocol::alice::event_loop::KrakenRate;
use swap::protocol::alice::{redeem, run, EventLoop};
use swap::seed::Seed; use swap::seed::Seed;
use swap::tor::AuthenticatedClient; use swap::tor::AuthenticatedClient;
use swap::{asb, bitcoin, kraken, monero, tor}; use swap::{asb, bitcoin, kraken, monero, tor};
@ -144,13 +143,9 @@ async fn main() -> Result<()> {
} }
}; };
let current_balance = monero_wallet.get_balance().await?;
let lock_fee = monero_wallet.static_tx_fee_estimate();
let kraken_rate = KrakenRate::new(config.maker.ask_spread, kraken_price_updates); let kraken_rate = KrakenRate::new(config.maker.ask_spread, kraken_price_updates);
let mut swarm = swarm::asb( let mut swarm = swarm::asb(
&seed, &seed,
current_balance,
lock_fee,
config.maker.min_buy_btc, config.maker.min_buy_btc,
config.maker.max_buy_btc, config.maker.max_buy_btc,
kraken_rate.clone(), kraken_rate.clone(),
@ -241,7 +236,7 @@ async fn main() -> Result<()> {
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
let (txid, _) = let (txid, _) =
alice::cancel(swap_id, Arc::new(bitcoin_wallet), Arc::new(db), force).await??; cancel(swap_id, Arc::new(bitcoin_wallet), Arc::new(db), force).await??;
tracing::info!("Cancel transaction successfully published with id {}", txid); tracing::info!("Cancel transaction successfully published with id {}", txid);
} }
@ -249,7 +244,7 @@ async fn main() -> Result<()> {
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
let monero_wallet = init_monero_wallet(&config, env_config).await?; let monero_wallet = init_monero_wallet(&config, env_config).await?;
alice::refund( refund(
swap_id, swap_id,
Arc::new(bitcoin_wallet), Arc::new(bitcoin_wallet),
Arc::new(monero_wallet), Arc::new(monero_wallet),
@ -264,12 +259,12 @@ async fn main() -> Result<()> {
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
let (txid, _) = let (txid, _) =
alice::punish(swap_id, Arc::new(bitcoin_wallet), Arc::new(db), force).await??; punish(swap_id, Arc::new(bitcoin_wallet), Arc::new(db), force).await??;
tracing::info!("Punish transaction successfully published with id {}", txid); tracing::info!("Punish transaction successfully published with id {}", txid);
} }
Command::SafelyAbort { swap_id } => { Command::SafelyAbort { swap_id } => {
alice::safely_abort(swap_id, Arc::new(db)).await?; safely_abort(swap_id, Arc::new(db)).await?;
tracing::info!("Swap safely aborted"); tracing::info!("Swap safely aborted");
} }
@ -280,12 +275,12 @@ async fn main() -> Result<()> {
} => { } => {
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
let (txid, _) = alice::redeem( let (txid, _) = redeem(
swap_id, swap_id,
Arc::new(bitcoin_wallet), Arc::new(bitcoin_wallet),
Arc::new(db), Arc::new(db),
force, force,
redeem::Finality::from_bool(do_not_await_finality), Finality::from_bool(do_not_await_finality),
) )
.await?; .await?;

@ -24,12 +24,13 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use swap::bitcoin::TxLock; use swap::bitcoin::TxLock;
use swap::cli::command::{parse_args_and_apply_defaults, Arguments, Command, ParseResult}; use swap::cli::command::{parse_args_and_apply_defaults, Arguments, Command, ParseResult};
use swap::cli::EventLoop;
use swap::database::Database; use swap::database::Database;
use swap::env::Config; 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::Swap;
use swap::seed::Seed; use swap::seed::Seed;
use swap::{bitcoin, cli, monero}; use swap::{bitcoin, cli, monero};
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
@ -85,20 +86,22 @@ async fn main() -> Result<()> {
init_monero_wallet(data_dir, monero_daemon_address, 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::cli(&seed, seller_peer_id, tor_socks5_port).await?; let mut swarm = swarm::cli(
&seed,
seller_peer_id,
tor_socks5_port,
env_config,
bitcoin_wallet.clone(),
)
.await?;
swarm swarm
.behaviour_mut() .behaviour_mut()
.add_address(seller_peer_id, seller_addr); .add_address(seller_peer_id, seller_addr);
tracing::debug!(peer_id = %swarm.local_peer_id(), "Network layer initialized"); tracing::debug!(peer_id = %swarm.local_peer_id(), "Network layer initialized");
let (event_loop, mut event_loop_handle) = EventLoop::new( let (event_loop, mut event_loop_handle) =
swap_id, EventLoop::new(swap_id, swarm, seller_peer_id, env_config)?;
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());
@ -185,20 +188,22 @@ async fn main() -> Result<()> {
let seller_peer_id = db.get_peer_id(swap_id)?; let seller_peer_id = db.get_peer_id(swap_id)?;
let mut swarm = swarm::cli(&seed, seller_peer_id, tor_socks5_port).await?; let mut swarm = swarm::cli(
&seed,
seller_peer_id,
tor_socks5_port,
env_config,
bitcoin_wallet.clone(),
)
.await?;
let our_peer_id = swarm.local_peer_id(); let our_peer_id = swarm.local_peer_id();
tracing::debug!(peer_id = %our_peer_id, "Initializing network module"); tracing::debug!(peer_id = %our_peer_id, "Initializing network module");
swarm swarm
.behaviour_mut() .behaviour_mut()
.add_address(seller_peer_id, seller_addr); .add_address(seller_peer_id, seller_addr);
let (event_loop, event_loop_handle) = EventLoop::new( let (event_loop, event_loop_handle) =
swap_id, EventLoop::new(swap_id, swarm, seller_peer_id, env_config)?;
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(
@ -241,13 +246,13 @@ async fn main() -> Result<()> {
) )
.await?; .await?;
let cancel = bob::cancel(swap_id, Arc::new(bitcoin_wallet), db, force).await?; let cancel = cli::cancel(swap_id, Arc::new(bitcoin_wallet), db, force).await?;
match cancel { match cancel {
Ok((txid, _)) => { Ok((txid, _)) => {
debug!("Cancel transaction successfully published with id {}", txid) debug!("Cancel transaction successfully published with id {}", txid)
} }
Err(bob::cancel::Error::CancelTimelockNotExpiredYet) => error!( Err(cli::cancel::Error::CancelTimelockNotExpiredYet) => error!(
"The Cancel Transaction cannot be published yet, because the timelock has not expired. Please try again later" "The Cancel Transaction cannot be published yet, because the timelock has not expired. Please try again later"
), ),
} }
@ -273,7 +278,7 @@ async fn main() -> Result<()> {
) )
.await?; .await?;
bob::refund(swap_id, Arc::new(bitcoin_wallet), db, force).await??; cli::refund(swap_id, Arc::new(bitcoin_wallet), db, force).await??;
} }
}; };
Ok(()) Ok(())
@ -424,12 +429,15 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use crate::determine_btc_to_swap;
use ::bitcoin::Amount;
use std::sync::Mutex; use std::sync::Mutex;
use ::bitcoin::Amount;
use tracing::subscriber; use tracing::subscriber;
use crate::determine_btc_to_swap;
use super::*;
struct MaxGiveable { struct MaxGiveable {
amounts: Vec<Amount>, amounts: Vec<Amount>,
call_counter: usize, call_counter: usize,

@ -344,8 +344,7 @@ mod tests {
tx_redeem_fee, tx_redeem_fee,
tx_punish_fee, tx_punish_fee,
&mut OsRng, &mut OsRng,
) );
.unwrap();
let bob_state0 = bob::State0::new( let bob_state0 = bob::State0::new(
Uuid::new_v4(), Uuid::new_v4(),

@ -1,3 +1,12 @@
mod behaviour;
pub mod cancel;
pub mod command; pub mod command;
mod event_loop;
pub mod refund;
pub mod tracing; pub mod tracing;
pub mod transport; pub mod transport;
pub use behaviour::{Behaviour, OutEvent};
pub use cancel::cancel;
pub use event_loop::{EventLoop, EventLoopHandle};
pub use refund::refund;

@ -1,12 +1,14 @@
use crate::network::quote::BidQuote; use crate::network::quote::BidQuote;
use crate::network::{encrypted_signature, quote, redial, spot_price, transfer_proof}; use crate::network::swap_setup::bob;
use crate::protocol::bob; use crate::network::{encrypted_signature, quote, redial, transfer_proof};
use crate::protocol::bob::{execution_setup, State2}; use crate::protocol::bob::State2;
use crate::{bitcoin, env};
use anyhow::{anyhow, Error, Result}; use anyhow::{anyhow, Error, Result};
use libp2p::core::Multiaddr; use libp2p::core::Multiaddr;
use libp2p::ping::{Ping, PingEvent}; use libp2p::ping::{Ping, PingEvent};
use libp2p::request_response::{RequestId, ResponseChannel}; use libp2p::request_response::{RequestId, ResponseChannel};
use libp2p::{NetworkBehaviour, PeerId}; use libp2p::{NetworkBehaviour, PeerId};
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
#[derive(Debug)] #[derive(Debug)]
@ -15,11 +17,7 @@ pub enum OutEvent {
id: RequestId, id: RequestId,
response: BidQuote, response: BidQuote,
}, },
SpotPriceReceived { SwapSetupCompleted(Box<Result<State2>>),
id: RequestId,
response: spot_price::Response,
},
ExecutionSetupDone(Box<Result<State2>>),
TransferProofReceived { TransferProofReceived {
msg: Box<transfer_proof::Request>, msg: Box<transfer_proof::Request>,
channel: ResponseChannel<()>, channel: ResponseChannel<()>,
@ -62,8 +60,7 @@ impl OutEvent {
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Behaviour { pub struct Behaviour {
pub quote: quote::Behaviour, pub quote: quote::Behaviour,
pub spot_price: spot_price::Behaviour, pub swap_setup: bob::Behaviour,
pub execution_setup: execution_setup::Behaviour,
pub transfer_proof: transfer_proof::Behaviour, pub transfer_proof: transfer_proof::Behaviour,
pub encrypted_signature: encrypted_signature::Behaviour, pub encrypted_signature: encrypted_signature::Behaviour,
pub redial: redial::Behaviour, pub redial: redial::Behaviour,
@ -75,11 +72,14 @@ pub struct Behaviour {
} }
impl Behaviour { impl Behaviour {
pub fn new(alice: PeerId) -> Self { pub fn new(
alice: PeerId,
env_config: env::Config,
bitcoin_wallet: Arc<bitcoin::Wallet>,
) -> Self {
Self { Self {
quote: quote::bob(), quote: quote::cli(),
spot_price: bob::spot_price::bob(), swap_setup: bob::Behaviour::new(env_config, bitcoin_wallet),
execution_setup: Default::default(),
transfer_proof: transfer_proof::bob(), transfer_proof: transfer_proof::bob(),
encrypted_signature: encrypted_signature::bob(), encrypted_signature: encrypted_signature::bob(),
redial: redial::Behaviour::new(alice, Duration::from_secs(2)), redial: redial::Behaviour::new(alice, Duration::from_secs(2)),
@ -90,7 +90,6 @@ impl Behaviour {
/// Add a known address for the given peer /// Add a known address for the given peer
pub fn add_address(&mut self, peer_id: PeerId, address: Multiaddr) { pub fn add_address(&mut self, peer_id: PeerId, address: Multiaddr) {
self.quote.add_address(&peer_id, address.clone()); self.quote.add_address(&peer_id, address.clone());
self.spot_price.add_address(&peer_id, address.clone());
self.transfer_proof.add_address(&peer_id, address.clone()); self.transfer_proof.add_address(&peer_id, address.clone());
self.encrypted_signature.add_address(&peer_id, address); self.encrypted_signature.add_address(&peer_id, address);
} }

@ -26,7 +26,7 @@ pub async fn cancel(
BobState::EncSigSent(state4) => state4.cancel(), BobState::EncSigSent(state4) => state4.cancel(),
BobState::CancelTimelockExpired(state6) => state6, BobState::CancelTimelockExpired(state6) => state6,
BobState::Started { .. } BobState::Started { .. }
| BobState::ExecutionSetupDone(_) | BobState::SwapSetupCompleted(_)
| BobState::BtcRedeemed(_) | BobState::BtcRedeemed(_)
| BobState::BtcCancelled(_) | BobState::BtcCancelled(_)
| BobState::BtcRefunded(_) | BobState::BtcRefunded(_)

@ -1,18 +1,17 @@
use crate::bitcoin::EncryptedSignature; use crate::bitcoin::EncryptedSignature;
use crate::cli::behaviour::{Behaviour, OutEvent};
use crate::network::encrypted_signature;
use crate::network::quote::BidQuote; use crate::network::quote::BidQuote;
use crate::network::spot_price::{BlockchainNetwork, Response}; use crate::network::swap_setup::bob::NewSwap;
use crate::network::{encrypted_signature, spot_price}; use crate::protocol::bob::State2;
use crate::protocol::bob; use crate::{env, monero};
use crate::protocol::bob::{Behaviour, OutEvent, State0, State2}; use anyhow::{Context, Result};
use crate::{bitcoin, env, monero};
use anyhow::{bail, Context, Result};
use futures::future::{BoxFuture, OptionFuture}; use futures::future::{BoxFuture, OptionFuture};
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use libp2p::request_response::{RequestId, ResponseChannel}; use libp2p::request_response::{RequestId, ResponseChannel};
use libp2p::swarm::SwarmEvent; use libp2p::swarm::SwarmEvent;
use libp2p::{PeerId, Swarm}; use libp2p::{PeerId, Swarm};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use uuid::Uuid; use uuid::Uuid;
@ -20,22 +19,19 @@ use uuid::Uuid;
pub struct EventLoop { pub struct EventLoop {
swap_id: Uuid, swap_id: Uuid,
swarm: libp2p::Swarm<Behaviour>, swarm: libp2p::Swarm<Behaviour>,
bitcoin_wallet: Arc<bitcoin::Wallet>,
alice_peer_id: PeerId, alice_peer_id: PeerId,
// these streams represents outgoing requests that we have to make // these streams represents outgoing requests that we have to make
quote_requests: bmrng::RequestReceiverStream<(), BidQuote>, quote_requests: bmrng::RequestReceiverStream<(), BidQuote>,
spot_price_requests: bmrng::RequestReceiverStream<spot_price::Request, spot_price::Response>,
encrypted_signatures: bmrng::RequestReceiverStream<EncryptedSignature, ()>, encrypted_signatures: bmrng::RequestReceiverStream<EncryptedSignature, ()>,
execution_setup_requests: bmrng::RequestReceiverStream<State0, Result<State2>>, swap_setup_requests: bmrng::RequestReceiverStream<NewSwap, Result<State2>>,
// these represents requests that are currently in-flight. // these represents requests that are currently in-flight.
// once we get a response to a matching [`RequestId`], we will use the responder to relay the // once we get a response to a matching [`RequestId`], we will use the responder to relay the
// response. // response.
inflight_spot_price_requests: HashMap<RequestId, bmrng::Responder<spot_price::Response>>,
inflight_quote_requests: HashMap<RequestId, bmrng::Responder<BidQuote>>, inflight_quote_requests: HashMap<RequestId, bmrng::Responder<BidQuote>>,
inflight_encrypted_signature_requests: HashMap<RequestId, bmrng::Responder<()>>, inflight_encrypted_signature_requests: HashMap<RequestId, bmrng::Responder<()>>,
inflight_execution_setup: Option<bmrng::Responder<Result<State2>>>, inflight_swap_setup: Option<bmrng::Responder<Result<State2>>>,
/// The sender we will use to relay incoming transfer proofs. /// The sender we will use to relay incoming transfer proofs.
transfer_proof: bmrng::RequestSender<monero::TransferProof, ()>, transfer_proof: bmrng::RequestSender<monero::TransferProof, ()>,
@ -54,37 +50,31 @@ impl EventLoop {
swap_id: Uuid, swap_id: Uuid,
swarm: Swarm<Behaviour>, swarm: Swarm<Behaviour>,
alice_peer_id: PeerId, alice_peer_id: PeerId,
bitcoin_wallet: Arc<bitcoin::Wallet>,
env_config: env::Config, env_config: env::Config,
) -> Result<(Self, EventLoopHandle)> { ) -> Result<(Self, EventLoopHandle)> {
let execution_setup = bmrng::channel_with_timeout(1, Duration::from_secs(60)); let execution_setup = bmrng::channel_with_timeout(1, Duration::from_secs(60));
let transfer_proof = bmrng::channel_with_timeout(1, Duration::from_secs(60)); let transfer_proof = bmrng::channel_with_timeout(1, Duration::from_secs(60));
let encrypted_signature = bmrng::channel_with_timeout(1, Duration::from_secs(60)); let encrypted_signature = bmrng::channel_with_timeout(1, Duration::from_secs(60));
let spot_price = bmrng::channel_with_timeout(1, Duration::from_secs(60));
let quote = bmrng::channel_with_timeout(1, Duration::from_secs(60)); let quote = bmrng::channel_with_timeout(1, Duration::from_secs(60));
let event_loop = EventLoop { let event_loop = EventLoop {
swap_id, swap_id,
swarm, swarm,
alice_peer_id, alice_peer_id,
bitcoin_wallet, swap_setup_requests: execution_setup.1.into(),
execution_setup_requests: execution_setup.1.into(),
transfer_proof: transfer_proof.0, transfer_proof: transfer_proof.0,
encrypted_signatures: encrypted_signature.1.into(), encrypted_signatures: encrypted_signature.1.into(),
spot_price_requests: spot_price.1.into(),
quote_requests: quote.1.into(), quote_requests: quote.1.into(),
inflight_spot_price_requests: HashMap::default(),
inflight_quote_requests: HashMap::default(), inflight_quote_requests: HashMap::default(),
inflight_execution_setup: None, inflight_swap_setup: None,
inflight_encrypted_signature_requests: HashMap::default(), inflight_encrypted_signature_requests: HashMap::default(),
pending_transfer_proof: OptionFuture::from(None), pending_transfer_proof: OptionFuture::from(None),
}; };
let handle = EventLoopHandle { let handle = EventLoopHandle {
execution_setup: execution_setup.0, swap_setup: execution_setup.0,
transfer_proof: transfer_proof.1, transfer_proof: transfer_proof.1,
encrypted_signature: encrypted_signature.0, encrypted_signature: encrypted_signature.0,
spot_price: spot_price.0,
quote: quote.0, quote: quote.0,
env_config, env_config,
}; };
@ -106,18 +96,13 @@ impl EventLoop {
tokio::select! { tokio::select! {
swarm_event = self.swarm.next_event().fuse() => { swarm_event = self.swarm.next_event().fuse() => {
match swarm_event { match swarm_event {
SwarmEvent::Behaviour(OutEvent::SpotPriceReceived { id, response }) => {
if let Some(responder) = self.inflight_spot_price_requests.remove(&id) {
let _ = responder.respond(response);
}
}
SwarmEvent::Behaviour(OutEvent::QuoteReceived { id, response }) => { SwarmEvent::Behaviour(OutEvent::QuoteReceived { id, response }) => {
if let Some(responder) = self.inflight_quote_requests.remove(&id) { if let Some(responder) = self.inflight_quote_requests.remove(&id) {
let _ = responder.respond(response); let _ = responder.respond(response);
} }
} }
SwarmEvent::Behaviour(OutEvent::ExecutionSetupDone(response)) => { SwarmEvent::Behaviour(OutEvent::SwapSetupCompleted(response)) => {
if let Some(responder) = self.inflight_execution_setup.take() { if let Some(responder) = self.inflight_swap_setup.take() {
let _ = responder.respond(*response); let _ = responder.respond(*response);
} }
} }
@ -197,17 +182,13 @@ impl EventLoop {
// Handle to-be-sent requests for all our network protocols. // Handle to-be-sent requests for all our network protocols.
// Use `self.is_connected_to_alice` as a guard to "buffer" requests until we are connected. // Use `self.is_connected_to_alice` as a guard to "buffer" requests until we are connected.
Some((request, responder)) = self.spot_price_requests.next().fuse(), if self.is_connected_to_alice() => {
let id = self.swarm.behaviour_mut().spot_price.send_request(&self.alice_peer_id, request);
self.inflight_spot_price_requests.insert(id, responder);
},
Some(((), responder)) = self.quote_requests.next().fuse(), if self.is_connected_to_alice() => { Some(((), responder)) = self.quote_requests.next().fuse(), if self.is_connected_to_alice() => {
let id = self.swarm.behaviour_mut().quote.send_request(&self.alice_peer_id, ()); let id = self.swarm.behaviour_mut().quote.send_request(&self.alice_peer_id, ());
self.inflight_quote_requests.insert(id, responder); self.inflight_quote_requests.insert(id, responder);
}, },
Some((request, responder)) = self.execution_setup_requests.next().fuse(), if self.is_connected_to_alice() => { Some((swap, responder)) = self.swap_setup_requests.next().fuse(), if self.is_connected_to_alice() => {
self.swarm.behaviour_mut().execution_setup.run(self.alice_peer_id, request, self.bitcoin_wallet.clone()); self.swarm.behaviour_mut().swap_setup.start(self.alice_peer_id, swap).await;
self.inflight_execution_setup = Some(responder); self.inflight_swap_setup = Some(responder);
}, },
Some((tx_redeem_encsig, responder)) = self.encrypted_signatures.next().fuse(), if self.is_connected_to_alice() => { Some((tx_redeem_encsig, responder)) = self.encrypted_signatures.next().fuse(), if self.is_connected_to_alice() => {
let request = encrypted_signature::Request { let request = encrypted_signature::Request {
@ -235,17 +216,16 @@ impl EventLoop {
#[derive(Debug)] #[derive(Debug)]
pub struct EventLoopHandle { pub struct EventLoopHandle {
execution_setup: bmrng::RequestSender<State0, Result<State2>>, swap_setup: bmrng::RequestSender<NewSwap, Result<State2>>,
transfer_proof: bmrng::RequestReceiver<monero::TransferProof, ()>, transfer_proof: bmrng::RequestReceiver<monero::TransferProof, ()>,
encrypted_signature: bmrng::RequestSender<EncryptedSignature, ()>, encrypted_signature: bmrng::RequestSender<EncryptedSignature, ()>,
spot_price: bmrng::RequestSender<spot_price::Request, spot_price::Response>,
quote: bmrng::RequestSender<(), BidQuote>, quote: bmrng::RequestSender<(), BidQuote>,
env_config: env::Config, env_config: env::Config,
} }
impl EventLoopHandle { impl EventLoopHandle {
pub async fn execution_setup(&mut self, state0: State0) -> Result<State2> { pub async fn setup_swap(&mut self, swap: NewSwap) -> Result<State2> {
self.execution_setup.send_receive(state0).await? self.swap_setup.send_receive(swap).await?
} }
pub async fn recv_transfer_proof(&mut self) -> Result<monero::TransferProof> { pub async fn recv_transfer_proof(&mut self) -> Result<monero::TransferProof> {
@ -261,27 +241,6 @@ impl EventLoopHandle {
Ok(transfer_proof) Ok(transfer_proof)
} }
pub async fn request_spot_price(&mut self, btc: bitcoin::Amount) -> Result<monero::Amount> {
let response = self
.spot_price
.send_receive(spot_price::Request {
btc,
blockchain_network: BlockchainNetwork {
bitcoin: self.env_config.bitcoin_network,
monero: self.env_config.monero_network,
},
})
.await?;
match response {
Response::Xmr(xmr) => Ok(xmr),
Response::Error(error) => {
let error: bob::spot_price::Error = error.into();
bail!(error);
}
}
}
pub async fn request_quote(&mut self) -> Result<BidQuote> { pub async fn request_quote(&mut self) -> Result<BidQuote> {
Ok(self.quote.send_receive(()).await?) Ok(self.quote.send_receive(()).await?)
} }

@ -26,7 +26,7 @@ pub async fn refund(
BobState::CancelTimelockExpired(state6) => state6, BobState::CancelTimelockExpired(state6) => state6,
BobState::BtcCancelled(state6) => state6, BobState::BtcCancelled(state6) => state6,
BobState::Started { .. } BobState::Started { .. }
| BobState::ExecutionSetupDone(_) | BobState::SwapSetupCompleted(_)
| BobState::BtcRedeemed(_) | BobState::BtcRedeemed(_)
| BobState::BtcRefunded(_) | BobState::BtcRefunded(_)
| BobState::XmrRedeemed { .. } | BobState::XmrRedeemed { .. }

@ -46,7 +46,7 @@ impl From<BobState> for Bob {
fn from(bob_state: BobState) -> Self { fn from(bob_state: BobState) -> Self {
match bob_state { match bob_state {
BobState::Started { btc_amount } => Bob::Started { btc_amount }, BobState::Started { btc_amount } => Bob::Started { btc_amount },
BobState::ExecutionSetupDone(state2) => Bob::ExecutionSetupDone { state2 }, BobState::SwapSetupCompleted(state2) => Bob::ExecutionSetupDone { state2 },
BobState::BtcLocked(state3) => Bob::BtcLocked { state3 }, BobState::BtcLocked(state3) => Bob::BtcLocked { state3 },
BobState::XmrLockProofReceived { BobState::XmrLockProofReceived {
state, state,
@ -78,7 +78,7 @@ impl From<Bob> for BobState {
fn from(db_state: Bob) -> Self { fn from(db_state: Bob) -> Self {
match db_state { match db_state {
Bob::Started { btc_amount } => BobState::Started { btc_amount }, Bob::Started { btc_amount } => BobState::Started { btc_amount },
Bob::ExecutionSetupDone { state2 } => BobState::ExecutionSetupDone(state2), Bob::ExecutionSetupDone { state2 } => BobState::SwapSetupCompleted(state2),
Bob::BtcLocked { state3 } => BobState::BtcLocked(state3), Bob::BtcLocked { state3 } => BobState::BtcLocked(state3),
Bob::XmrLockProofReceived { Bob::XmrLockProofReceived {
state, state,

@ -81,6 +81,9 @@ pub struct PublicViewKey(PublicKey);
#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, PartialOrd)] #[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, PartialOrd)]
pub struct Amount(u64); pub struct Amount(u64);
// Median tx fees on Monero as found here: https://www.monero.how/monero-transaction-fees, XMR 0.000_015 * 2 (to be on the safe side)
pub const MONERO_FEE: Amount = Amount::from_piconero(30000000);
impl Amount { impl Amount {
pub const ZERO: Self = Self(0); pub const ZERO: Self = Self(0);
pub const ONE_XMR: Self = Self(PICONERO_OFFSET); pub const ONE_XMR: Self = Self(PICONERO_OFFSET);
@ -88,7 +91,7 @@ impl Amount {
/// piconeros. /// piconeros.
/// ///
/// A piconero (a.k.a atomic unit) is equal to 1e-12 XMR. /// A piconero (a.k.a atomic unit) is equal to 1e-12 XMR.
pub fn from_piconero(amount: u64) -> Self { pub const fn from_piconero(amount: u64) -> Self {
Amount(amount) Amount(amount)
} }

@ -276,11 +276,6 @@ impl Wallet {
pub async fn refresh(&self) -> Result<Refreshed> { pub async fn refresh(&self) -> Result<Refreshed> {
Ok(self.inner.lock().await.refresh().await?) Ok(self.inner.lock().await.refresh().await?)
} }
pub fn static_tx_fee_estimate(&self) -> Amount {
// Median tx fees on Monero as found here: https://www.monero.how/monero-transaction-fees, 0.000_015 * 2 (to be on the safe side)
Amount::from_monero(0.000_03f64).expect("static fee to be convertible without problems")
}
} }
#[derive(Debug)] #[derive(Debug)]

@ -1,11 +1,13 @@
mod impl_from_rr_event; mod impl_from_rr_event;
pub mod alice;
pub mod bob;
pub mod cbor_request_response; pub mod cbor_request_response;
pub mod encrypted_signature; pub mod encrypted_signature;
pub mod json_pull_codec; pub mod json_pull_codec;
pub mod quote; pub mod quote;
pub mod redial; pub mod redial;
pub mod spot_price; pub mod swap_setup;
pub mod swarm; pub mod swarm;
pub mod tor_transport; pub mod tor_transport;
pub mod transfer_proof; pub mod transfer_proof;

@ -1,5 +1,5 @@
use crate::network::cbor_request_response::CborCodec; use crate::network::cbor_request_response::CborCodec;
use crate::protocol::{alice, bob}; use crate::{asb, cli};
use libp2p::core::ProtocolName; use libp2p::core::ProtocolName;
use libp2p::request_response::{ use libp2p::request_response::{
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent, ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
@ -46,7 +46,7 @@ pub fn bob() -> Behaviour {
) )
} }
impl From<(PeerId, Message)> for alice::OutEvent { impl From<(PeerId, Message)> for asb::OutEvent {
fn from((peer, message): (PeerId, Message)) -> Self { fn from((peer, message): (PeerId, Message)) -> Self {
match message { match message {
Message::Request { Message::Request {
@ -60,9 +60,9 @@ impl From<(PeerId, Message)> for alice::OutEvent {
} }
} }
} }
crate::impl_from_rr_event!(OutEvent, alice::OutEvent, PROTOCOL); crate::impl_from_rr_event!(OutEvent, asb::OutEvent, PROTOCOL);
impl From<(PeerId, Message)> for bob::OutEvent { impl From<(PeerId, Message)> for cli::OutEvent {
fn from((peer, message): (PeerId, Message)) -> Self { fn from((peer, message): (PeerId, Message)) -> Self {
match message { match message {
Message::Request { .. } => Self::unexpected_request(peer), Message::Request { .. } => Self::unexpected_request(peer),
@ -72,4 +72,4 @@ impl From<(PeerId, Message)> for bob::OutEvent {
} }
} }
} }
crate::impl_from_rr_event!(OutEvent, bob::OutEvent, PROTOCOL); crate::impl_from_rr_event!(OutEvent, cli::OutEvent, PROTOCOL);

@ -1,6 +1,5 @@
use crate::bitcoin;
use crate::network::json_pull_codec::JsonPullCodec; use crate::network::json_pull_codec::JsonPullCodec;
use crate::protocol::{alice, bob}; use crate::{asb, bitcoin, cli};
use libp2p::core::ProtocolName; use libp2p::core::ProtocolName;
use libp2p::request_response::{ use libp2p::request_response::{
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent, ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
@ -38,10 +37,11 @@ pub struct BidQuote {
pub max_quantity: bitcoin::Amount, pub max_quantity: bitcoin::Amount,
} }
/// Constructs a new instance of the `quote` behaviour to be used by Alice. /// Constructs a new instance of the `quote` behaviour to be used by the ASB.
/// ///
/// Alice only supports inbound connections, i.e. handing out quotes. /// The ASB is always listening and only supports inbound connections, i.e.
pub fn alice() -> Behaviour { /// handing out quotes.
pub fn asb() -> Behaviour {
Behaviour::new( Behaviour::new(
JsonPullCodec::default(), JsonPullCodec::default(),
vec![(BidQuoteProtocol, ProtocolSupport::Inbound)], vec![(BidQuoteProtocol, ProtocolSupport::Inbound)],
@ -49,10 +49,11 @@ pub fn alice() -> Behaviour {
) )
} }
/// Constructs a new instance of the `quote` behaviour to be used by Bob. /// Constructs a new instance of the `quote` behaviour to be used by the CLI.
/// ///
/// Bob only supports outbound connections, i.e. requesting quotes. /// The CLI is always dialing and only supports outbound connections, i.e.
pub fn bob() -> Behaviour { /// requesting quotes.
pub fn cli() -> Behaviour {
Behaviour::new( Behaviour::new(
JsonPullCodec::default(), JsonPullCodec::default(),
vec![(BidQuoteProtocol, ProtocolSupport::Outbound)], vec![(BidQuoteProtocol, ProtocolSupport::Outbound)],
@ -60,7 +61,7 @@ pub fn bob() -> Behaviour {
) )
} }
impl From<(PeerId, Message)> for alice::OutEvent { impl From<(PeerId, Message)> for asb::OutEvent {
fn from((peer, message): (PeerId, Message)) -> Self { fn from((peer, message): (PeerId, Message)) -> Self {
match message { match message {
Message::Request { channel, .. } => Self::QuoteRequested { channel, peer }, Message::Request { channel, .. } => Self::QuoteRequested { channel, peer },
@ -68,9 +69,9 @@ impl From<(PeerId, Message)> for alice::OutEvent {
} }
} }
} }
crate::impl_from_rr_event!(OutEvent, alice::OutEvent, PROTOCOL); crate::impl_from_rr_event!(OutEvent, asb::OutEvent, PROTOCOL);
impl From<(PeerId, Message)> for bob::OutEvent { impl From<(PeerId, Message)> for cli::OutEvent {
fn from((peer, message): (PeerId, Message)) -> Self { fn from((peer, message): (PeerId, Message)) -> Self {
match message { match message {
Message::Request { .. } => Self::unexpected_request(peer), Message::Request { .. } => Self::unexpected_request(peer),
@ -84,4 +85,4 @@ impl From<(PeerId, Message)> for bob::OutEvent {
} }
} }
} }
crate::impl_from_rr_event!(OutEvent, bob::OutEvent, PROTOCOL); crate::impl_from_rr_event!(OutEvent, cli::OutEvent, PROTOCOL);

@ -1,4 +1,4 @@
use crate::protocol::bob; use crate::cli;
use backoff::backoff::Backoff; use backoff::backoff::Backoff;
use backoff::ExponentialBackoff; use backoff::ExponentialBackoff;
use futures::future::FutureExt; use futures::future::FutureExt;
@ -119,11 +119,11 @@ impl NetworkBehaviour for Behaviour {
} }
} }
impl From<OutEvent> for bob::OutEvent { impl From<OutEvent> for cli::OutEvent {
fn from(event: OutEvent) -> Self { fn from(event: OutEvent) -> Self {
match event { match event {
OutEvent::AllAttemptsExhausted { peer } => { OutEvent::AllAttemptsExhausted { peer } => {
bob::OutEvent::AllRedialAttemptsExhausted { peer } cli::OutEvent::AllRedialAttemptsExhausted { peer }
} }
} }
} }

@ -1,138 +0,0 @@
use crate::monero;
use crate::network::cbor_request_response::CborCodec;
use libp2p::core::ProtocolName;
use libp2p::request_response::{RequestResponse, RequestResponseEvent, RequestResponseMessage};
use serde::{Deserialize, Serialize};
pub const PROTOCOL: &str = "/comit/xmr/btc/spot-price/1.0.0";
pub type OutEvent = RequestResponseEvent<Request, Response>;
pub type Message = RequestResponseMessage<Request, Response>;
pub type Behaviour = RequestResponse<CborCodec<SpotPriceProtocol, Request, Response>>;
/// The spot price protocol allows parties to **initiate** a trade by requesting
/// a spot price.
///
/// A spot price is binding for both parties, i.e. after the spot-price protocol
/// completes, both parties are expected to follow up with the `execution-setup`
/// protocol.
///
/// If a party wishes to only inquire about the current price, they should use
/// the `quote` protocol instead.
#[derive(Debug, Clone, Copy, Default)]
pub struct SpotPriceProtocol;
impl ProtocolName for SpotPriceProtocol {
fn protocol_name(&self) -> &[u8] {
PROTOCOL.as_bytes()
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Request {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
pub btc: bitcoin::Amount,
pub blockchain_network: BlockchainNetwork,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Response {
Xmr(monero::Amount),
Error(Error),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Error {
NoSwapsAccepted,
AmountBelowMinimum {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
min: bitcoin::Amount,
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
buy: bitcoin::Amount,
},
AmountAboveMaximum {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
max: bitcoin::Amount,
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
buy: bitcoin::Amount,
},
BalanceTooLow {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
buy: bitcoin::Amount,
},
BlockchainNetworkMismatch {
cli: BlockchainNetwork,
asb: BlockchainNetwork,
},
/// To be used for errors that cannot be explained on the CLI side (e.g.
/// rate update problems on the seller side)
Other,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
pub struct BlockchainNetwork {
#[serde(with = "crate::bitcoin::network")]
pub bitcoin: bitcoin::Network,
#[serde(with = "crate::monero::network")]
pub monero: monero::Network,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::monero;
#[test]
fn snapshot_test_serialize() {
let amount = monero::Amount::from_piconero(100_000u64);
let xmr = r#"{"Xmr":100000}"#.to_string();
let serialized = serde_json::to_string(&Response::Xmr(amount)).unwrap();
assert_eq!(xmr, serialized);
let error = r#"{"Error":"NoSwapsAccepted"}"#.to_string();
let serialized = serde_json::to_string(&Response::Error(Error::NoSwapsAccepted)).unwrap();
assert_eq!(error, serialized);
let error = r#"{"Error":{"AmountBelowMinimum":{"min":0,"buy":0}}}"#.to_string();
let serialized = serde_json::to_string(&Response::Error(Error::AmountBelowMinimum {
min: Default::default(),
buy: Default::default(),
}))
.unwrap();
assert_eq!(error, serialized);
let error = r#"{"Error":{"AmountAboveMaximum":{"max":0,"buy":0}}}"#.to_string();
let serialized = serde_json::to_string(&Response::Error(Error::AmountAboveMaximum {
max: Default::default(),
buy: Default::default(),
}))
.unwrap();
assert_eq!(error, serialized);
let error = r#"{"Error":{"BalanceTooLow":{"buy":0}}}"#.to_string();
let serialized = serde_json::to_string(&Response::Error(Error::BalanceTooLow {
buy: Default::default(),
}))
.unwrap();
assert_eq!(error, serialized);
let error = r#"{"Error":{"BlockchainNetworkMismatch":{"cli":{"bitcoin":"Mainnet","monero":"Mainnet"},"asb":{"bitcoin":"Testnet","monero":"Stagenet"}}}}"#.to_string();
let serialized =
serde_json::to_string(&Response::Error(Error::BlockchainNetworkMismatch {
cli: BlockchainNetwork {
bitcoin: bitcoin::Network::Bitcoin,
monero: monero::Network::Mainnet,
},
asb: BlockchainNetwork {
bitcoin: bitcoin::Network::Testnet,
monero: monero::Network::Stagenet,
},
}))
.unwrap();
assert_eq!(error, serialized);
let error = r#"{"Error":"Other"}"#.to_string();
let serialized = serde_json::to_string(&Response::Error(Error::Other)).unwrap();
assert_eq!(error, serialized);
}
}

@ -0,0 +1,114 @@
use crate::monero;
use anyhow::{Context, Result};
use libp2p::core::upgrade;
use libp2p::swarm::NegotiatedSubstream;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
pub mod alice;
pub mod bob;
pub const BUF_SIZE: usize = 1024 * 1024;
pub mod protocol {
use futures::future;
use libp2p::core::upgrade::{from_fn, FromFnUpgrade};
use libp2p::core::Endpoint;
use libp2p::swarm::NegotiatedSubstream;
use void::Void;
pub fn new() -> SwapSetup {
from_fn(
b"/comit/xmr/btc/swap_setup/1.0.0",
Box::new(|socket, _| future::ready(Ok(socket))),
)
}
pub type SwapSetup = FromFnUpgrade<
&'static [u8],
Box<
dyn Fn(
NegotiatedSubstream,
Endpoint,
) -> future::Ready<Result<NegotiatedSubstream, Void>>
+ Send
+ 'static,
>,
>;
}
#[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,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SpotPriceRequest {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
pub btc: bitcoin::Amount,
pub blockchain_network: BlockchainNetwork,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum SpotPriceResponse {
Xmr(monero::Amount),
Error(SpotPriceError),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum SpotPriceError {
NoSwapsAccepted,
AmountBelowMinimum {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
min: bitcoin::Amount,
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
buy: bitcoin::Amount,
},
AmountAboveMaximum {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
max: bitcoin::Amount,
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
buy: bitcoin::Amount,
},
BalanceTooLow {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
buy: bitcoin::Amount,
},
BlockchainNetworkMismatch {
cli: BlockchainNetwork,
asb: BlockchainNetwork,
},
/// To be used for errors that cannot be explained on the CLI side (e.g.
/// rate update problems on the seller side)
Other,
}
pub async fn read_cbor_message<T>(substream: &mut NegotiatedSubstream) -> Result<T>
where
T: DeserializeOwned,
{
let bytes = upgrade::read_one(substream, BUF_SIZE)
.await
.context("Failed to read length-prefixed message from stream")?;
let mut de = serde_cbor::Deserializer::from_slice(&bytes);
let message =
T::deserialize(&mut de).context("Failed to deserialize bytes into message using CBOR")?;
Ok(message)
}
pub async fn write_cbor_message<T>(substream: &mut NegotiatedSubstream, message: T) -> Result<()>
where
T: Serialize,
{
let bytes =
serde_cbor::to_vec(&message).context("Failed to serialize message as bytes using CBOR")?;
upgrade::write_with_len_prefix(substream, &bytes)
.await
.context("Failed to write bytes as length-prefixed message")?;
Ok(())
}

@ -0,0 +1,521 @@
use crate::asb::LatestRate;
use crate::network::swap_setup;
use crate::network::swap_setup::{
protocol, BlockchainNetwork, SpotPriceError, SpotPriceRequest, SpotPriceResponse,
};
use crate::protocol::alice::{State0, State3};
use crate::protocol::{Message0, Message2, Message4};
use crate::{asb, bitcoin, env, monero};
use anyhow::{anyhow, Context, Result};
use futures::future::{BoxFuture, OptionFuture};
use futures::{AsyncWriteExt, FutureExt};
use libp2p::core::connection::ConnectionId;
use libp2p::core::upgrade;
use libp2p::swarm::{
KeepAlive, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, PollParameters,
ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol,
};
use libp2p::{Multiaddr, PeerId};
use std::collections::VecDeque;
use std::fmt::Debug;
use std::task::Poll;
use std::time::{Duration, Instant};
use uuid::Uuid;
use void::Void;
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum OutEvent {
Initiated {
send_wallet_snapshot: bmrng::RequestReceiver<bitcoin::Amount, WalletSnapshot>,
},
Completed {
peer_id: PeerId,
swap_id: Uuid,
state3: State3,
},
Error {
peer_id: PeerId,
error: anyhow::Error,
},
}
#[derive(Debug)]
pub struct WalletSnapshot {
balance: monero::Amount,
lock_fee: monero::Amount,
// TODO: Consider using the same address for punish and redeem (they are mutually exclusive, so
// effectively the address will only be used once)
redeem_address: bitcoin::Address,
punish_address: bitcoin::Address,
redeem_fee: bitcoin::Amount,
punish_fee: bitcoin::Amount,
}
impl WalletSnapshot {
pub async fn capture(
bitcoin_wallet: &bitcoin::Wallet,
monero_wallet: &monero::Wallet,
transfer_amount: bitcoin::Amount,
) -> Result<Self> {
let balance = monero_wallet.get_balance().await?;
let redeem_address = bitcoin_wallet.new_address().await?;
let punish_address = bitcoin_wallet.new_address().await?;
let redeem_fee = bitcoin_wallet
.estimate_fee(bitcoin::TxRedeem::weight(), transfer_amount)
.await?;
let punish_fee = bitcoin_wallet
.estimate_fee(bitcoin::TxPunish::weight(), transfer_amount)
.await?;
Ok(Self {
balance,
lock_fee: monero::MONERO_FEE,
redeem_address,
punish_address,
redeem_fee,
punish_fee,
})
}
}
impl From<OutEvent> for asb::OutEvent {
fn from(event: OutEvent) -> Self {
match event {
OutEvent::Initiated {
send_wallet_snapshot,
} => asb::OutEvent::SwapSetupInitiated {
send_wallet_snapshot,
},
OutEvent::Completed {
peer_id: bob_peer_id,
swap_id,
state3,
} => asb::OutEvent::SwapSetupCompleted {
peer_id: bob_peer_id,
swap_id,
state3: Box::new(state3),
},
OutEvent::Error { peer_id, error } => asb::OutEvent::Failure {
peer: peer_id,
error: anyhow!(error),
},
}
}
}
#[allow(missing_debug_implementations)]
pub struct Behaviour<LR> {
events: VecDeque<OutEvent>,
min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount,
env_config: env::Config,
latest_rate: LR,
resume_only: bool,
}
impl<LR> Behaviour<LR> {
pub fn new(
min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount,
env_config: env::Config,
latest_rate: LR,
resume_only: bool,
) -> Self {
Self {
events: Default::default(),
min_buy,
max_buy,
env_config,
latest_rate,
resume_only,
}
}
}
impl<LR> NetworkBehaviour for Behaviour<LR>
where
LR: LatestRate + Send + 'static + Clone,
{
type ProtocolsHandler = Handler<LR>;
type OutEvent = OutEvent;
fn new_handler(&mut self) -> Self::ProtocolsHandler {
Handler::new(
self.min_buy,
self.max_buy,
self.env_config,
self.latest_rate.clone(),
self.resume_only,
)
}
fn addresses_of_peer(&mut self, _: &PeerId) -> Vec<Multiaddr> {
Vec::new()
}
fn inject_connected(&mut self, _: &PeerId) {}
fn inject_disconnected(&mut self, _: &PeerId) {}
fn inject_event(&mut self, peer_id: PeerId, _: ConnectionId, event: HandlerOutEvent) {
match event {
HandlerOutEvent::Initiated(send_wallet_snapshot) => {
self.events.push_back(OutEvent::Initiated {
send_wallet_snapshot,
})
}
HandlerOutEvent::Completed(Ok((swap_id, state3))) => {
self.events.push_back(OutEvent::Completed {
peer_id,
swap_id,
state3,
})
}
HandlerOutEvent::Completed(Err(error)) => {
self.events.push_back(OutEvent::Error { peer_id, error })
}
}
}
fn poll(
&mut self,
_cx: &mut std::task::Context<'_>,
_params: &mut impl PollParameters,
) -> Poll<NetworkBehaviourAction<(), Self::OutEvent>> {
if let Some(event) = self.events.pop_front() {
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event));
}
Poll::Pending
}
}
type InboundStream = BoxFuture<'static, Result<(Uuid, State3)>>;
pub struct Handler<LR> {
inbound_stream: OptionFuture<InboundStream>,
events: VecDeque<HandlerOutEvent>,
min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount,
env_config: env::Config,
latest_rate: LR,
resume_only: bool,
timeout: Duration,
keep_alive: KeepAlive,
}
impl<LR> Handler<LR> {
fn new(
min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount,
env_config: env::Config,
latest_rate: LR,
resume_only: bool,
) -> Self {
Self {
inbound_stream: OptionFuture::from(None),
events: Default::default(),
min_buy,
max_buy,
env_config,
latest_rate,
resume_only,
timeout: Duration::from_secs(120),
keep_alive: KeepAlive::Until(Instant::now() + Duration::from_secs(10)),
}
}
}
#[allow(clippy::large_enum_variant)]
pub enum HandlerOutEvent {
Initiated(bmrng::RequestReceiver<bitcoin::Amount, WalletSnapshot>),
Completed(Result<(Uuid, State3)>),
}
impl<LR> ProtocolsHandler for Handler<LR>
where
LR: LatestRate + Send + 'static,
{
type InEvent = ();
type OutEvent = HandlerOutEvent;
type Error = Error;
type InboundProtocol = protocol::SwapSetup;
type OutboundProtocol = upgrade::DeniedUpgrade;
type InboundOpenInfo = ();
type OutboundOpenInfo = ();
fn listen_protocol(&self) -> SubstreamProtocol<Self::InboundProtocol, Self::InboundOpenInfo> {
SubstreamProtocol::new(protocol::new(), ())
}
fn inject_fully_negotiated_inbound(
&mut self,
mut substream: NegotiatedSubstream,
_: Self::InboundOpenInfo,
) {
self.keep_alive = KeepAlive::Yes;
let (sender, receiver) = bmrng::channel_with_timeout::<bitcoin::Amount, WalletSnapshot>(
1,
Duration::from_secs(5),
);
let resume_only = self.resume_only;
let min_buy = self.min_buy;
let max_buy = self.max_buy;
let latest_rate = self.latest_rate.latest_rate();
let env_config = self.env_config;
let protocol = tokio::time::timeout(self.timeout, async move {
let request = swap_setup::read_cbor_message::<SpotPriceRequest>(&mut substream)
.await
.context("Failed to read spot price request")?;
let wallet_snapshot = sender
.send_receive(request.btc)
.await
.context("Failed to receive wallet snapshot")?;
// wrap all of these into another future so we can `return` from all the
// different blocks
let validate = async {
if resume_only {
return Err(Error::ResumeOnlyMode);
};
let blockchain_network = BlockchainNetwork {
bitcoin: env_config.bitcoin_network,
monero: env_config.monero_network,
};
if request.blockchain_network != blockchain_network {
return Err(Error::BlockchainNetworkMismatch {
cli: request.blockchain_network,
asb: blockchain_network,
});
}
let btc = request.btc;
if btc < min_buy {
return Err(Error::AmountBelowMinimum {
min: min_buy,
buy: btc,
});
}
if btc > max_buy {
return Err(Error::AmountAboveMaximum {
max: max_buy,
buy: btc,
});
}
let rate = latest_rate.map_err(|e| Error::LatestRateFetchFailed(Box::new(e)))?;
let xmr = rate
.sell_quote(btc)
.map_err(Error::SellQuoteCalculationFailed)?;
if wallet_snapshot.balance < xmr + wallet_snapshot.lock_fee {
return Err(Error::BalanceTooLow {
balance: wallet_snapshot.balance,
buy: btc,
});
}
Ok(xmr)
};
let result = validate.await;
swap_setup::write_cbor_message(
&mut substream,
SpotPriceResponse::from_result_ref(&result),
)
.await
.context("Failed to write spot price response")?;
let xmr = result?;
let state0 = State0::new(
request.btc,
xmr,
env_config,
wallet_snapshot.redeem_address,
wallet_snapshot.punish_address,
wallet_snapshot.redeem_fee,
wallet_snapshot.punish_fee,
&mut rand::thread_rng(),
);
let message0 = swap_setup::read_cbor_message::<Message0>(&mut substream)
.await
.context("Failed to read message0")?;
let (swap_id, state1) = state0
.receive(message0)
.context("Failed to transition state0 -> state1 using message0")?;
swap_setup::write_cbor_message(&mut substream, state1.next_message())
.await
.context("Failed to send message1")?;
let message2 = swap_setup::read_cbor_message::<Message2>(&mut substream)
.await
.context("Failed to read message2")?;
let state2 = state1
.receive(message2)
.context("Failed to transition state1 -> state2 using message2")?;
swap_setup::write_cbor_message(&mut substream, state2.next_message())
.await
.context("Failed to send message3")?;
let message4 = swap_setup::read_cbor_message::<Message4>(&mut substream)
.await
.context("Failed to read message4")?;
let state3 = state2
.receive(message4)
.context("Failed to transition state2 -> state3 using message4")?;
substream
.flush()
.await
.context("Failed to flush substream after all messages were sent")?;
substream
.close()
.await
.context("Failed to close substream after all messages were sent")?;
Ok((swap_id, state3))
});
let max_seconds = self.timeout.as_secs();
self.inbound_stream = OptionFuture::from(Some(
async move {
protocol.await.with_context(|| {
format!("Failed to complete execution setup within {}s", max_seconds)
})?
}
.boxed(),
));
self.events.push_back(HandlerOutEvent::Initiated(receiver));
}
fn inject_fully_negotiated_outbound(&mut self, _: Void, _: Self::OutboundOpenInfo) {
unreachable!("Alice does not support outbound in the handler")
}
fn inject_event(&mut self, _: Self::InEvent) {
unreachable!("Alice does not receive events from the Behaviour in the handler")
}
fn inject_dial_upgrade_error(
&mut self,
_: Self::OutboundOpenInfo,
_: ProtocolsHandlerUpgrErr<Void>,
) {
unreachable!("Alice does not dial")
}
fn connection_keep_alive(&self) -> KeepAlive {
self.keep_alive
}
#[allow(clippy::type_complexity)]
fn poll(
&mut self,
cx: &mut std::task::Context<'_>,
) -> Poll<
ProtocolsHandlerEvent<
Self::OutboundProtocol,
Self::OutboundOpenInfo,
Self::OutEvent,
Self::Error,
>,
> {
if let Some(event) = self.events.pop_front() {
return Poll::Ready(ProtocolsHandlerEvent::Custom(event));
}
if let Some(result) = futures::ready!(self.inbound_stream.poll_unpin(cx)) {
self.inbound_stream = OptionFuture::from(None);
return Poll::Ready(ProtocolsHandlerEvent::Custom(HandlerOutEvent::Completed(
result,
)));
}
Poll::Pending
}
}
impl SpotPriceResponse {
pub fn from_result_ref(result: &Result<monero::Amount, Error>) -> Self {
match result {
Ok(amount) => SpotPriceResponse::Xmr(*amount),
Err(error) => SpotPriceResponse::Error(error.to_error_response()),
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("ASB is running in resume-only mode")]
ResumeOnlyMode,
#[error("Amount {buy} below minimum {min}")]
AmountBelowMinimum {
min: bitcoin::Amount,
buy: bitcoin::Amount,
},
#[error("Amount {buy} above maximum {max}")]
AmountAboveMaximum {
max: bitcoin::Amount,
buy: bitcoin::Amount,
},
#[error("Balance {balance} too low to fulfill swapping {buy}")]
BalanceTooLow {
balance: monero::Amount,
buy: bitcoin::Amount,
},
#[error("Failed to fetch latest rate")]
LatestRateFetchFailed(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("Failed to calculate quote")]
SellQuoteCalculationFailed(#[source] anyhow::Error),
#[error("Blockchain networks did not match, we are on {asb:?}, but request from {cli:?}")]
BlockchainNetworkMismatch {
cli: BlockchainNetwork,
asb: BlockchainNetwork,
},
}
impl Error {
pub fn to_error_response(&self) -> SpotPriceError {
match self {
Error::ResumeOnlyMode => SpotPriceError::NoSwapsAccepted,
Error::AmountBelowMinimum { min, buy } => SpotPriceError::AmountBelowMinimum {
min: *min,
buy: *buy,
},
Error::AmountAboveMaximum { max, buy } => SpotPriceError::AmountAboveMaximum {
max: *max,
buy: *buy,
},
Error::BalanceTooLow { buy, .. } => SpotPriceError::BalanceTooLow { buy: *buy },
Error::BlockchainNetworkMismatch { cli, asb } => {
SpotPriceError::BlockchainNetworkMismatch {
cli: *cli,
asb: *asb,
}
}
Error::LatestRateFetchFailed(_) | Error::SellQuoteCalculationFailed(_) => {
SpotPriceError::Other
}
}
}
}

@ -0,0 +1,309 @@
use crate::network::swap_setup::{
protocol, read_cbor_message, write_cbor_message, BlockchainNetwork, SpotPriceError,
SpotPriceRequest, SpotPriceResponse,
};
use crate::protocol::bob::{State0, State2};
use crate::protocol::{Message1, Message3};
use crate::{bitcoin, cli, env, monero};
use anyhow::Result;
use futures::future::{BoxFuture, OptionFuture};
use futures::{AsyncWriteExt, FutureExt};
use libp2p::core::connection::ConnectionId;
use libp2p::core::upgrade;
use libp2p::swarm::{
KeepAlive, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler,
PollParameters, ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr,
SubstreamProtocol,
};
use libp2p::{Multiaddr, PeerId};
use std::collections::VecDeque;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::Duration;
use uuid::Uuid;
use void::Void;
#[allow(missing_debug_implementations)]
pub struct Behaviour {
env_config: env::Config,
bitcoin_wallet: Arc<bitcoin::Wallet>,
new_swaps: VecDeque<(PeerId, NewSwap)>,
completed_swaps: VecDeque<(PeerId, Completed)>,
}
impl Behaviour {
pub fn new(env_config: env::Config, bitcoin_wallet: Arc<bitcoin::Wallet>) -> Self {
Self {
env_config,
bitcoin_wallet,
new_swaps: VecDeque::default(),
completed_swaps: VecDeque::default(),
}
}
pub async fn start(&mut self, alice: PeerId, swap: NewSwap) {
self.new_swaps.push_back((alice, swap))
}
}
impl From<Completed> for cli::OutEvent {
fn from(completed: Completed) -> Self {
cli::OutEvent::SwapSetupCompleted(Box::new(completed.0))
}
}
impl NetworkBehaviour for Behaviour {
type ProtocolsHandler = Handler;
type OutEvent = Completed;
fn new_handler(&mut self) -> Self::ProtocolsHandler {
Handler::new(self.env_config, self.bitcoin_wallet.clone())
}
fn addresses_of_peer(&mut self, _: &PeerId) -> Vec<Multiaddr> {
Vec::new()
}
fn inject_connected(&mut self, _: &PeerId) {}
fn inject_disconnected(&mut self, _: &PeerId) {}
fn inject_event(&mut self, peer: PeerId, _: ConnectionId, completed: Completed) {
self.completed_swaps.push_back((peer, completed));
}
fn poll(
&mut self,
_cx: &mut Context<'_>,
_params: &mut impl PollParameters,
) -> Poll<NetworkBehaviourAction<NewSwap, Self::OutEvent>> {
if let Some((_, event)) = self.completed_swaps.pop_front() {
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event));
}
if let Some((peer, event)) = self.new_swaps.pop_front() {
return Poll::Ready(NetworkBehaviourAction::NotifyHandler {
peer_id: peer,
handler: NotifyHandler::Any,
event,
});
}
Poll::Pending
}
}
type OutboundStream = BoxFuture<'static, Result<State2>>;
pub struct Handler {
outbound_stream: OptionFuture<OutboundStream>,
env_config: env::Config,
timeout: Duration,
new_swaps: VecDeque<NewSwap>,
bitcoin_wallet: Arc<bitcoin::Wallet>,
keep_alive: KeepAlive,
}
impl Handler {
fn new(env_config: env::Config, bitcoin_wallet: Arc<bitcoin::Wallet>) -> Self {
Self {
env_config,
outbound_stream: OptionFuture::from(None),
timeout: Duration::from_secs(120),
new_swaps: VecDeque::default(),
bitcoin_wallet,
keep_alive: KeepAlive::Yes,
}
}
}
#[derive(Debug)]
pub struct NewSwap {
pub swap_id: Uuid,
pub btc: bitcoin::Amount,
pub tx_refund_fee: bitcoin::Amount,
pub tx_cancel_fee: bitcoin::Amount,
pub bitcoin_refund_address: bitcoin::Address,
}
pub struct Completed(Result<State2>);
impl ProtocolsHandler for Handler {
type InEvent = NewSwap;
type OutEvent = Completed;
type Error = Void;
type InboundProtocol = upgrade::DeniedUpgrade;
type OutboundProtocol = protocol::SwapSetup;
type InboundOpenInfo = ();
type OutboundOpenInfo = NewSwap;
fn listen_protocol(&self) -> SubstreamProtocol<Self::InboundProtocol, Self::InboundOpenInfo> {
SubstreamProtocol::new(upgrade::DeniedUpgrade, ())
}
fn inject_fully_negotiated_inbound(&mut self, _: Void, _: Self::InboundOpenInfo) {
unreachable!("Bob does not support inbound substreams")
}
fn inject_fully_negotiated_outbound(
&mut self,
mut substream: NegotiatedSubstream,
info: Self::OutboundOpenInfo,
) {
let bitcoin_wallet = self.bitcoin_wallet.clone();
let env_config = self.env_config;
let protocol = tokio::time::timeout(self.timeout, async move {
write_cbor_message(&mut substream, SpotPriceRequest {
btc: info.btc,
blockchain_network: BlockchainNetwork {
bitcoin: env_config.bitcoin_network,
monero: env_config.monero_network,
},
})
.await?;
let xmr = Result::from(read_cbor_message::<SpotPriceResponse>(&mut substream).await?)?;
let state0 = State0::new(
info.swap_id,
&mut rand::thread_rng(),
info.btc,
xmr,
env_config.bitcoin_cancel_timelock,
env_config.bitcoin_punish_timelock,
info.bitcoin_refund_address,
env_config.monero_finality_confirmations,
info.tx_refund_fee,
info.tx_cancel_fee,
);
write_cbor_message(&mut substream, state0.next_message()).await?;
let message1 = read_cbor_message::<Message1>(&mut substream).await?;
let state1 = state0.receive(bitcoin_wallet.as_ref(), message1).await?;
write_cbor_message(&mut substream, state1.next_message()).await?;
let message3 = read_cbor_message::<Message3>(&mut substream).await?;
let state2 = state1.receive(message3)?;
write_cbor_message(&mut substream, state2.next_message()).await?;
substream.flush().await?;
substream.close().await?;
Ok(state2)
});
let max_seconds = self.timeout.as_secs();
self.outbound_stream = OptionFuture::from(Some(
async move {
protocol.await.map_err(|_| Error::Timeout {
seconds: max_seconds,
})?
}
.boxed(),
));
}
fn inject_event(&mut self, new_swap: Self::InEvent) {
self.new_swaps.push_back(new_swap);
}
fn inject_dial_upgrade_error(
&mut self,
_: Self::OutboundOpenInfo,
_: ProtocolsHandlerUpgrErr<Void>,
) {
}
fn connection_keep_alive(&self) -> KeepAlive {
self.keep_alive
}
#[allow(clippy::type_complexity)]
fn poll(
&mut self,
cx: &mut Context<'_>,
) -> Poll<
ProtocolsHandlerEvent<
Self::OutboundProtocol,
Self::OutboundOpenInfo,
Self::OutEvent,
Self::Error,
>,
> {
if let Some(new_swap) = self.new_swaps.pop_front() {
self.keep_alive = KeepAlive::Yes;
return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest {
protocol: SubstreamProtocol::new(protocol::new(), new_swap),
});
}
if let Some(result) = futures::ready!(self.outbound_stream.poll_unpin(cx)) {
self.outbound_stream = OptionFuture::from(None);
return Poll::Ready(ProtocolsHandlerEvent::Custom(Completed(result)));
}
Poll::Pending
}
}
impl From<SpotPriceResponse> for Result<monero::Amount, Error> {
fn from(response: SpotPriceResponse) -> Self {
match response {
SpotPriceResponse::Xmr(amount) => Ok(amount),
SpotPriceResponse::Error(e) => Err(e.into()),
}
}
}
#[derive(Clone, Debug, thiserror::Error, PartialEq)]
pub enum Error {
#[error("Seller currently does not accept incoming swap requests, please try again later")]
NoSwapsAccepted,
#[error("Seller refused to buy {buy} because the minimum configured buy limit is {min}")]
AmountBelowMinimum {
min: bitcoin::Amount,
buy: bitcoin::Amount,
},
#[error("Seller refused to buy {buy} because the maximum configured buy limit is {max}")]
AmountAboveMaximum {
max: bitcoin::Amount,
buy: bitcoin::Amount,
},
#[error("Seller's XMR balance is currently too low to fulfill the swap request to buy {buy}, please try again later")]
BalanceTooLow { buy: bitcoin::Amount },
#[error("Seller blockchain network {asb:?} setup did not match your blockchain network setup {cli:?}")]
BlockchainNetworkMismatch {
cli: BlockchainNetwork,
asb: BlockchainNetwork,
},
#[error("Failed to complete swap setup within {seconds}s")]
Timeout { seconds: u64 },
/// To be used for errors that cannot be explained on the CLI side (e.g.
/// rate update problems on the seller side)
#[error("Seller encountered a problem, please try again later.")]
Other,
}
impl From<SpotPriceError> for Error {
fn from(error: SpotPriceError) -> Self {
match error {
SpotPriceError::NoSwapsAccepted => Error::NoSwapsAccepted,
SpotPriceError::AmountBelowMinimum { min, buy } => {
Error::AmountBelowMinimum { min, buy }
}
SpotPriceError::AmountAboveMaximum { max, buy } => {
Error::AmountAboveMaximum { max, buy }
}
SpotPriceError::BalanceTooLow { buy } => Error::BalanceTooLow { buy },
SpotPriceError::BlockchainNetworkMismatch { cli, asb } => {
Error::BlockchainNetworkMismatch { cli, asb }
}
SpotPriceError::Other => Error::Other,
}
}
}

@ -1,35 +1,25 @@
use crate::protocol::alice::event_loop::LatestRate; use crate::asb::LatestRate;
use crate::protocol::{alice, bob};
use crate::seed::Seed; use crate::seed::Seed;
use crate::{asb, cli, env, monero, tor}; use crate::{asb, bitcoin, cli, env, tor};
use anyhow::Result; use anyhow::Result;
use libp2p::swarm::SwarmBuilder; use libp2p::swarm::SwarmBuilder;
use libp2p::{PeerId, Swarm}; use libp2p::{PeerId, Swarm};
use std::fmt::Debug; use std::fmt::Debug;
use std::sync::Arc;
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn asb<LR>( pub fn asb<LR>(
seed: &Seed, seed: &Seed,
balance: monero::Amount,
lock_fee: monero::Amount,
min_buy: bitcoin::Amount, min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount, max_buy: bitcoin::Amount,
latest_rate: LR, latest_rate: LR,
resume_only: bool, resume_only: bool,
env_config: env::Config, env_config: env::Config,
) -> Result<Swarm<alice::Behaviour<LR>>> ) -> Result<Swarm<asb::Behaviour<LR>>>
where where
LR: LatestRate + Send + 'static + Debug, LR: LatestRate + Send + 'static + Debug + Clone,
{ {
let behaviour = alice::Behaviour::new( let behaviour = asb::Behaviour::new(min_buy, max_buy, latest_rate, resume_only, env_config);
balance,
lock_fee,
min_buy,
max_buy,
latest_rate,
resume_only,
env_config,
);
let identity = seed.derive_libp2p_identity(); let identity = seed.derive_libp2p_identity();
let transport = asb::transport::new(&identity)?; let transport = asb::transport::new(&identity)?;
@ -48,13 +38,15 @@ pub async fn cli(
seed: &Seed, seed: &Seed,
alice: PeerId, alice: PeerId,
tor_socks5_port: u16, tor_socks5_port: u16,
) -> Result<Swarm<bob::Behaviour>> { env_config: env::Config,
bitcoin_wallet: Arc<bitcoin::Wallet>,
) -> Result<Swarm<cli::Behaviour>> {
let maybe_tor_socks5_port = match tor::Client::new(tor_socks5_port).assert_tor_running().await { let maybe_tor_socks5_port = match tor::Client::new(tor_socks5_port).assert_tor_running().await {
Ok(()) => Some(tor_socks5_port), Ok(()) => Some(tor_socks5_port),
Err(_) => None, Err(_) => None,
}; };
let behaviour = bob::Behaviour::new(alice); let behaviour = cli::Behaviour::new(alice, env_config, bitcoin_wallet);
let identity = seed.derive_libp2p_identity(); let identity = seed.derive_libp2p_identity();
let transport = cli::transport::new(&identity, maybe_tor_socks5_port)?; let transport = cli::transport::new(&identity, maybe_tor_socks5_port)?;

@ -1,6 +1,5 @@
use crate::monero;
use crate::network::cbor_request_response::CborCodec; use crate::network::cbor_request_response::CborCodec;
use crate::protocol::{alice, bob}; use crate::{asb, cli, monero};
use libp2p::core::ProtocolName; use libp2p::core::ProtocolName;
use libp2p::request_response::{ use libp2p::request_response::{
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent, ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
@ -47,7 +46,7 @@ pub fn bob() -> Behaviour {
) )
} }
impl From<(PeerId, Message)> for alice::OutEvent { impl From<(PeerId, Message)> for asb::OutEvent {
fn from((peer, message): (PeerId, Message)) -> Self { fn from((peer, message): (PeerId, Message)) -> Self {
match message { match message {
Message::Request { .. } => Self::unexpected_request(peer), Message::Request { .. } => Self::unexpected_request(peer),
@ -58,9 +57,9 @@ impl From<(PeerId, Message)> for alice::OutEvent {
} }
} }
} }
crate::impl_from_rr_event!(OutEvent, alice::OutEvent, PROTOCOL); crate::impl_from_rr_event!(OutEvent, asb::OutEvent, PROTOCOL);
impl From<(PeerId, Message)> for bob::OutEvent { impl From<(PeerId, Message)> for cli::OutEvent {
fn from((peer, message): (PeerId, Message)) -> Self { fn from((peer, message): (PeerId, Message)) -> Self {
match message { match message {
Message::Request { Message::Request {
@ -74,4 +73,4 @@ impl From<(PeerId, Message)> for bob::OutEvent {
} }
} }
} }
crate::impl_from_rr_event!(OutEvent, bob::OutEvent, PROTOCOL); crate::impl_from_rr_event!(OutEvent, cli::OutEvent, PROTOCOL);

@ -2,32 +2,19 @@
//! Alice holds XMR and wishes receive BTC. //! Alice holds XMR and wishes receive BTC.
use crate::database::Database; use crate::database::Database;
use crate::env::Config; use crate::env::Config;
use crate::{bitcoin, monero}; use crate::{asb, bitcoin, monero};
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid; use uuid::Uuid;
pub use self::behaviour::{Behaviour, OutEvent};
pub use self::event_loop::{EventLoop, EventLoopHandle};
pub use self::recovery::cancel::cancel;
pub use self::recovery::punish::punish;
pub use self::recovery::redeem::redeem;
pub use self::recovery::refund::refund;
pub use self::recovery::safely_abort::safely_abort;
pub use self::recovery::{cancel, punish, redeem, refund, safely_abort};
pub use self::state::*; pub use self::state::*;
pub use self::swap::{run, run_until}; pub use self::swap::{run, run_until};
mod behaviour;
pub mod event_loop;
mod execution_setup;
mod recovery;
mod spot_price;
pub mod state; pub mod state;
pub mod swap; pub mod swap;
pub struct Swap { pub struct Swap {
pub state: AliceState, pub state: AliceState,
pub event_loop_handle: EventLoopHandle, pub event_loop_handle: asb::EventLoopHandle,
pub bitcoin_wallet: Arc<bitcoin::Wallet>, pub bitcoin_wallet: Arc<bitcoin::Wallet>,
pub monero_wallet: Arc<monero::Wallet>, pub monero_wallet: Arc<monero::Wallet>,
pub env_config: Config, pub env_config: Config,

@ -1,109 +0,0 @@
use crate::network::cbor_request_response::BUF_SIZE;
use crate::protocol::alice::{State0, State3};
use crate::protocol::{alice, Message0, Message2, Message4};
use anyhow::{Context, Error};
use libp2p::PeerId;
use libp2p_async_await::BehaviourOutEvent;
use std::time::Duration;
use uuid::Uuid;
#[derive(Debug)]
pub enum OutEvent {
Done {
bob_peer_id: PeerId,
swap_id: Uuid,
state3: State3,
},
Failure {
peer: PeerId,
error: Error,
},
}
impl From<BehaviourOutEvent<(PeerId, (Uuid, State3)), (), Error>> for OutEvent {
fn from(event: BehaviourOutEvent<(PeerId, (Uuid, State3)), (), Error>) -> Self {
match event {
BehaviourOutEvent::Inbound(_, Ok((bob_peer_id, (swap_id, state3)))) => OutEvent::Done {
bob_peer_id,
swap_id,
state3,
},
BehaviourOutEvent::Inbound(peer, Err(e)) => OutEvent::Failure { peer, error: e },
BehaviourOutEvent::Outbound(..) => unreachable!("Alice only supports inbound"),
}
}
}
#[derive(libp2p::NetworkBehaviour)]
#[behaviour(out_event = "OutEvent", event_process = false)]
pub struct Behaviour {
inner: libp2p_async_await::Behaviour<(PeerId, (Uuid, State3)), (), anyhow::Error>,
}
impl Default for Behaviour {
fn default() -> Self {
Self {
inner: libp2p_async_await::Behaviour::new(b"/comit/xmr/btc/execution_setup/1.0.0"),
}
}
}
impl Behaviour {
pub fn run(&mut self, bob: PeerId, state0: State0) {
self.inner.do_protocol_listener(bob, move |mut substream| {
let protocol = async move {
let message0 =
serde_cbor::from_slice::<Message0>(&substream.read_message(BUF_SIZE).await?)
.context("Failed to deserialize message0")?;
let (swap_id, state1) = state0.receive(message0)?;
substream
.write_message(
&serde_cbor::to_vec(&state1.next_message())
.context("Failed to serialize message1")?,
)
.await?;
let message2 =
serde_cbor::from_slice::<Message2>(&substream.read_message(BUF_SIZE).await?)
.context("Failed to deserialize message2")?;
let state2 = state1
.receive(message2)
.context("Failed to receive Message2")?;
substream
.write_message(
&serde_cbor::to_vec(&state2.next_message())
.context("Failed to serialize message3")?,
)
.await?;
let message4 =
serde_cbor::from_slice::<Message4>(&substream.read_message(BUF_SIZE).await?)
.context("Failed to deserialize message4")?;
let state3 = state2.receive(message4)?;
Ok((bob, (swap_id, state3)))
};
async move { tokio::time::timeout(Duration::from_secs(60), protocol).await? }
});
}
}
impl From<OutEvent> for alice::OutEvent {
fn from(event: OutEvent) -> Self {
match event {
OutEvent::Done {
bob_peer_id,
state3,
swap_id,
} => Self::ExecutionSetupDone {
bob_peer_id,
state3: Box::new(state3),
swap_id,
},
OutEvent::Failure { peer, error } => Self::Failure { peer, error },
}
}
}

@ -1,825 +0,0 @@
use crate::network::cbor_request_response::CborCodec;
use crate::network::spot_price;
use crate::network::spot_price::{BlockchainNetwork, SpotPriceProtocol};
use crate::protocol::alice;
use crate::protocol::alice::event_loop::LatestRate;
use crate::{env, monero};
use libp2p::request_response::{
ProtocolSupport, RequestResponseConfig, RequestResponseEvent, RequestResponseMessage,
ResponseChannel,
};
use libp2p::swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters};
use libp2p::{NetworkBehaviour, PeerId};
use std::collections::VecDeque;
use std::fmt::Debug;
use std::task::{Context, Poll};
#[derive(Debug)]
pub enum OutEvent {
ExecutionSetupParams {
peer: PeerId,
btc: bitcoin::Amount,
xmr: monero::Amount,
},
Error {
peer: PeerId,
error: Error,
},
}
#[derive(NetworkBehaviour)]
#[behaviour(out_event = "OutEvent", poll_method = "poll", event_process = true)]
#[allow(missing_debug_implementations)]
pub struct Behaviour<LR>
where
LR: LatestRate + Send + 'static,
{
behaviour: spot_price::Behaviour,
#[behaviour(ignore)]
events: VecDeque<OutEvent>,
#[behaviour(ignore)]
balance: monero::Amount,
#[behaviour(ignore)]
lock_fee: monero::Amount,
#[behaviour(ignore)]
min_buy: bitcoin::Amount,
#[behaviour(ignore)]
max_buy: bitcoin::Amount,
#[behaviour(ignore)]
env_config: env::Config,
#[behaviour(ignore)]
latest_rate: LR,
#[behaviour(ignore)]
resume_only: bool,
}
/// Behaviour that handles spot prices.
/// All the logic how to react to a spot price request is contained here, events
/// reporting the successful handling of a spot price request or a failure are
/// bubbled up to the parent behaviour.
impl<LR> Behaviour<LR>
where
LR: LatestRate + Send + 'static,
{
pub fn new(
balance: monero::Amount,
lock_fee: monero::Amount,
min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount,
env_config: env::Config,
latest_rate: LR,
resume_only: bool,
) -> Self {
Self {
behaviour: spot_price::Behaviour::new(
CborCodec::default(),
vec![(SpotPriceProtocol, ProtocolSupport::Inbound)],
RequestResponseConfig::default(),
),
events: Default::default(),
balance,
lock_fee,
min_buy,
max_buy,
env_config,
latest_rate,
resume_only,
}
}
pub fn update_balance(&mut self, balance: monero::Amount) {
self.balance = balance;
}
fn decline(
&mut self,
peer: PeerId,
channel: ResponseChannel<spot_price::Response>,
error: Error,
) {
if self
.behaviour
.send_response(
channel,
spot_price::Response::Error(error.to_error_response()),
)
.is_err()
{
tracing::debug!(%peer, "Unable to send error response for spot price request");
}
self.events.push_back(OutEvent::Error { peer, error });
}
fn poll<BIE>(
&mut self,
_cx: &mut Context<'_>,
_params: &mut impl PollParameters,
) -> Poll<NetworkBehaviourAction<BIE, OutEvent>> {
if let Some(event) = self.events.pop_front() {
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event));
}
// We trust in libp2p to poll us.
Poll::Pending
}
}
impl<LR> NetworkBehaviourEventProcess<spot_price::OutEvent> for Behaviour<LR>
where
LR: LatestRate + Send + 'static,
{
fn inject_event(&mut self, event: spot_price::OutEvent) {
let (peer, message) = match event {
RequestResponseEvent::Message { peer, message } => (peer, message),
RequestResponseEvent::OutboundFailure { peer, error, .. } => {
tracing::error!(%peer, "Failure sending spot price response: {:#}", error);
return;
}
RequestResponseEvent::InboundFailure { peer, error, .. } => {
tracing::warn!(%peer, "Inbound failure when handling spot price request: {:#}", error);
return;
}
RequestResponseEvent::ResponseSent { peer, .. } => {
tracing::debug!(%peer, "Spot price response sent");
return;
}
};
let (request, channel) = match message {
RequestResponseMessage::Request {
request, channel, ..
} => (request, channel),
RequestResponseMessage::Response { .. } => {
tracing::error!("Unexpected message");
return;
}
};
let blockchain_network = BlockchainNetwork {
bitcoin: self.env_config.bitcoin_network,
monero: self.env_config.monero_network,
};
if request.blockchain_network != blockchain_network {
self.decline(peer, channel, Error::BlockchainNetworkMismatch {
cli: request.blockchain_network,
asb: blockchain_network,
});
return;
}
if self.resume_only {
self.decline(peer, channel, Error::ResumeOnlyMode);
return;
}
let btc = request.btc;
if btc < self.min_buy {
self.decline(peer, channel, Error::AmountBelowMinimum {
min: self.min_buy,
buy: btc,
});
return;
}
if btc > self.max_buy {
self.decline(peer, channel, Error::AmountAboveMaximum {
max: self.max_buy,
buy: btc,
});
return;
}
let rate = match self.latest_rate.latest_rate() {
Ok(rate) => rate,
Err(e) => {
self.decline(peer, channel, Error::LatestRateFetchFailed(Box::new(e)));
return;
}
};
let xmr = match rate.sell_quote(btc) {
Ok(xmr) => xmr,
Err(e) => {
self.decline(peer, channel, Error::SellQuoteCalculationFailed(e));
return;
}
};
let xmr_balance = self.balance;
let xmr_lock_fees = self.lock_fee;
if xmr_balance < xmr + xmr_lock_fees {
self.decline(peer, channel, Error::BalanceTooLow {
balance: xmr_balance,
buy: btc,
});
return;
}
if self
.behaviour
.send_response(channel, spot_price::Response::Xmr(xmr))
.is_err()
{
tracing::error!(%peer, "Failed to send spot price response of {} for {}", xmr, btc)
}
self.events
.push_back(OutEvent::ExecutionSetupParams { peer, btc, xmr });
}
}
impl From<OutEvent> for alice::OutEvent {
fn from(event: OutEvent) -> Self {
match event {
OutEvent::ExecutionSetupParams { peer, btc, xmr } => {
Self::ExecutionSetupStart { peer, btc, xmr }
}
OutEvent::Error { peer, error } => Self::SwapRequestDeclined { peer, error },
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("ASB is running in resume-only mode")]
ResumeOnlyMode,
#[error("Amount {buy} below minimum {min}")]
AmountBelowMinimum {
min: bitcoin::Amount,
buy: bitcoin::Amount,
},
#[error("Amount {buy} above maximum {max}")]
AmountAboveMaximum {
max: bitcoin::Amount,
buy: bitcoin::Amount,
},
#[error("Balance {balance} too low to fulfill swapping {buy}")]
BalanceTooLow {
balance: monero::Amount,
buy: bitcoin::Amount,
},
#[error("Failed to fetch latest rate")]
LatestRateFetchFailed(#[source] Box<dyn std::error::Error + Send + 'static>),
#[error("Failed to calculate quote: {0}")]
SellQuoteCalculationFailed(#[source] anyhow::Error),
#[error("Blockchain networks did not match, we are on {asb:?}, but request from {cli:?}")]
BlockchainNetworkMismatch {
cli: spot_price::BlockchainNetwork,
asb: spot_price::BlockchainNetwork,
},
}
impl Error {
pub fn to_error_response(&self) -> spot_price::Error {
match self {
Error::ResumeOnlyMode => spot_price::Error::NoSwapsAccepted,
Error::AmountBelowMinimum { min, buy } => spot_price::Error::AmountBelowMinimum {
min: *min,
buy: *buy,
},
Error::AmountAboveMaximum { max, buy } => spot_price::Error::AmountAboveMaximum {
max: *max,
buy: *buy,
},
Error::BalanceTooLow { buy, .. } => spot_price::Error::BalanceTooLow { buy: *buy },
Error::BlockchainNetworkMismatch { cli, asb } => {
spot_price::Error::BlockchainNetworkMismatch {
cli: *cli,
asb: *asb,
}
}
Error::LatestRateFetchFailed(_) | Error::SellQuoteCalculationFailed(_) => {
spot_price::Error::Other
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::asb::Rate;
use crate::env::GetConfig;
use crate::monero;
use crate::network::test::{await_events_or_timeout, connect, new_swarm};
use crate::protocol::{alice, bob};
use anyhow::anyhow;
use libp2p::Swarm;
use rust_decimal::Decimal;
impl Default for AliceBehaviourValues {
fn default() -> Self {
Self {
balance: monero::Amount::from_monero(1.0).unwrap(),
lock_fee: monero::Amount::ZERO,
min_buy: bitcoin::Amount::from_btc(0.001).unwrap(),
max_buy: bitcoin::Amount::from_btc(0.01).unwrap(),
rate: TestRate::default(), // 0.01
resume_only: false,
env_config: env::Testnet::get_config(),
}
}
}
#[tokio::test]
async fn given_alice_has_sufficient_balance_then_returns_price() {
let mut test = SpotPriceTest::setup(AliceBehaviourValues::default()).await;
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
let expected_xmr = monero::Amount::from_monero(1.0).unwrap();
test.construct_and_send_request(btc_to_swap);
test.assert_price((btc_to_swap, expected_xmr), expected_xmr)
.await;
}
#[tokio::test]
async fn given_alice_has_insufficient_balance_then_returns_error() {
let mut test = SpotPriceTest::setup(
AliceBehaviourValues::default().with_balance(monero::Amount::ZERO),
)
.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::BalanceTooLow {
balance: monero::Amount::ZERO,
buy: btc_to_swap,
},
bob::spot_price::Error::BalanceTooLow { buy: btc_to_swap },
)
.await;
}
#[tokio::test]
async fn given_alice_has_insufficient_balance_after_balance_update_then_returns_error() {
let mut test = SpotPriceTest::setup(AliceBehaviourValues::default()).await;
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
let expected_xmr = monero::Amount::from_monero(1.0).unwrap();
test.construct_and_send_request(btc_to_swap);
test.assert_price((btc_to_swap, expected_xmr), expected_xmr)
.await;
test.alice_swarm
.behaviour_mut()
.update_balance(monero::Amount::ZERO);
test.construct_and_send_request(btc_to_swap);
test.assert_error(
alice::spot_price::Error::BalanceTooLow {
balance: monero::Amount::ZERO,
buy: btc_to_swap,
},
bob::spot_price::Error::BalanceTooLow { buy: btc_to_swap },
)
.await;
}
#[tokio::test]
async fn given_alice_has_insufficient_balance_because_of_lock_fee_then_returns_error() {
let balance = monero::Amount::from_monero(1.0).unwrap();
let mut test = SpotPriceTest::setup(
AliceBehaviourValues::default()
.with_balance(balance)
.with_lock_fee(monero::Amount::from_piconero(1)),
)
.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::BalanceTooLow {
balance,
buy: btc_to_swap,
},
bob::spot_price::Error::BalanceTooLow { buy: btc_to_swap },
)
.await;
}
#[tokio::test]
async fn given_below_min_buy_then_returns_error() {
let min_buy = bitcoin::Amount::from_btc(0.001).unwrap();
let mut test =
SpotPriceTest::setup(AliceBehaviourValues::default().with_min_buy(min_buy)).await;
let btc_to_swap = bitcoin::Amount::from_btc(0.0001).unwrap();
test.construct_and_send_request(btc_to_swap);
test.assert_error(
alice::spot_price::Error::AmountBelowMinimum {
buy: btc_to_swap,
min: min_buy,
},
bob::spot_price::Error::AmountBelowMinimum {
buy: btc_to_swap,
min: min_buy,
},
)
.await;
}
#[tokio::test]
async fn given_above_max_buy_then_returns_error() {
let max_buy = bitcoin::Amount::from_btc(0.001).unwrap();
let mut test =
SpotPriceTest::setup(AliceBehaviourValues::default().with_max_buy(max_buy)).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::AmountAboveMaximum {
buy: btc_to_swap,
max: max_buy,
},
bob::spot_price::Error::AmountAboveMaximum {
buy: btc_to_swap,
max: max_buy,
},
)
.await;
}
#[tokio::test]
async fn given_alice_in_resume_only_mode_then_returns_error() {
let mut test =
SpotPriceTest::setup(AliceBehaviourValues::default().with_resume_only(true)).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::ResumeOnlyMode,
bob::spot_price::Error::NoSwapsAccepted,
)
.await;
}
#[tokio::test]
async fn given_rate_fetch_problem_then_returns_error() {
let mut test =
SpotPriceTest::setup(AliceBehaviourValues::default().with_rate(TestRate::error_rate()))
.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::LatestRateFetchFailed(Box::new(TestRateError {})),
bob::spot_price::Error::Other,
)
.await;
}
#[tokio::test]
async fn given_rate_calculation_problem_then_returns_error() {
let mut test = SpotPriceTest::setup(
AliceBehaviourValues::default().with_rate(TestRate::from_rate_and_spread(0.0, 0)),
)
.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::SellQuoteCalculationFailed(anyhow!(
"Error text irrelevant, won't be checked here"
)),
bob::spot_price::Error::Other,
)
.await;
}
#[tokio::test]
async fn given_alice_mainnnet_bob_testnet_then_network_mismatch_error() {
let mut test = SpotPriceTest::setup(
AliceBehaviourValues::default().with_env_config(env::Mainnet::get_config()),
)
.await;
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
test.construct_and_send_request(btc_to_swap);
test.assert_error(
alice::spot_price::Error::BlockchainNetworkMismatch {
cli: BlockchainNetwork {
bitcoin: bitcoin::Network::Testnet,
monero: monero::Network::Stagenet,
},
asb: BlockchainNetwork {
bitcoin: bitcoin::Network::Bitcoin,
monero: monero::Network::Mainnet,
},
},
bob::spot_price::Error::BlockchainNetworkMismatch {
cli: BlockchainNetwork {
bitcoin: bitcoin::Network::Testnet,
monero: monero::Network::Stagenet,
},
asb: BlockchainNetwork {
bitcoin: bitcoin::Network::Bitcoin,
monero: monero::Network::Mainnet,
},
},
)
.await;
}
#[tokio::test]
async fn given_alice_testnet_bob_mainnet_then_network_mismatch_error() {
let mut test = SpotPriceTest::setup(AliceBehaviourValues::default()).await;
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
let request = spot_price::Request {
btc: btc_to_swap,
blockchain_network: BlockchainNetwork {
bitcoin: bitcoin::Network::Bitcoin,
monero: monero::Network::Mainnet,
},
};
test.send_request(request);
test.assert_error(
alice::spot_price::Error::BlockchainNetworkMismatch {
cli: BlockchainNetwork {
bitcoin: bitcoin::Network::Bitcoin,
monero: monero::Network::Mainnet,
},
asb: BlockchainNetwork {
bitcoin: bitcoin::Network::Testnet,
monero: monero::Network::Stagenet,
},
},
bob::spot_price::Error::BlockchainNetworkMismatch {
cli: BlockchainNetwork {
bitcoin: bitcoin::Network::Bitcoin,
monero: monero::Network::Mainnet,
},
asb: BlockchainNetwork {
bitcoin: bitcoin::Network::Testnet,
monero: monero::Network::Stagenet,
},
},
)
.await;
}
struct SpotPriceTest {
alice_swarm: Swarm<alice::spot_price::Behaviour<TestRate>>,
bob_swarm: Swarm<spot_price::Behaviour>,
alice_peer_id: PeerId,
}
impl SpotPriceTest {
pub async fn setup(values: AliceBehaviourValues) -> Self {
let (mut alice_swarm, _, alice_peer_id) = new_swarm(|_, _| {
Behaviour::new(
values.balance,
values.lock_fee,
values.min_buy,
values.max_buy,
values.env_config,
values.rate.clone(),
values.resume_only,
)
});
let (mut bob_swarm, ..) = new_swarm(|_, _| bob::spot_price::bob());
connect(&mut alice_swarm, &mut bob_swarm).await;
Self {
alice_swarm,
bob_swarm,
alice_peer_id,
}
}
pub fn construct_and_send_request(&mut self, btc_to_swap: bitcoin::Amount) {
let request = spot_price::Request {
btc: btc_to_swap,
blockchain_network: BlockchainNetwork {
bitcoin: bitcoin::Network::Testnet,
monero: monero::Network::Stagenet,
},
};
self.send_request(request);
}
pub fn send_request(&mut self, spot_price_request: spot_price::Request) {
self.bob_swarm
.behaviour_mut()
.send_request(&self.alice_peer_id, spot_price_request);
}
async fn assert_price(
&mut self,
alice_assert: (bitcoin::Amount, monero::Amount),
bob_assert: monero::Amount,
) {
match await_events_or_timeout(self.alice_swarm.next(), self.bob_swarm.next()).await {
(
alice::spot_price::OutEvent::ExecutionSetupParams { btc, xmr, .. },
spot_price::OutEvent::Message { message, .. },
) => {
assert_eq!(alice_assert, (btc, xmr));
let response = match message {
RequestResponseMessage::Response { response, .. } => response,
_ => panic!("Unexpected message {:?} for Bob", message),
};
match response {
spot_price::Response::Xmr(xmr) => {
assert_eq!(bob_assert, xmr)
}
_ => panic!("Unexpected response {:?} for Bob", response),
}
}
(alice_event, bob_event) => panic!(
"Received unexpected event, alice emitted {:?} and bob emitted {:?}",
alice_event, bob_event
),
}
}
async fn assert_error(
&mut self,
alice_assert: alice::spot_price::Error,
bob_assert: bob::spot_price::Error,
) {
match await_events_or_timeout(self.alice_swarm.next(), self.bob_swarm.next()).await {
(
alice::spot_price::OutEvent::Error { error, .. },
spot_price::OutEvent::Message { message, .. },
) => {
// TODO: Somehow make PartialEq work on Alice's spot_price::Error
match (alice_assert, error) {
(
alice::spot_price::Error::BalanceTooLow {
balance: balance1,
buy: buy1,
},
alice::spot_price::Error::BalanceTooLow {
balance: balance2,
buy: buy2,
},
) => {
assert_eq!(balance1, balance2);
assert_eq!(buy1, buy2);
}
(
alice::spot_price::Error::BlockchainNetworkMismatch {
cli: cli1,
asb: asb1,
},
alice::spot_price::Error::BlockchainNetworkMismatch {
cli: cli2,
asb: asb2,
},
) => {
assert_eq!(cli1, cli2);
assert_eq!(asb1, asb2);
}
(
alice::spot_price::Error::AmountBelowMinimum { .. },
alice::spot_price::Error::AmountBelowMinimum { .. },
)
| (
alice::spot_price::Error::AmountAboveMaximum { .. },
alice::spot_price::Error::AmountAboveMaximum { .. },
)
| (
alice::spot_price::Error::LatestRateFetchFailed(_),
alice::spot_price::Error::LatestRateFetchFailed(_),
)
| (
alice::spot_price::Error::SellQuoteCalculationFailed(_),
alice::spot_price::Error::SellQuoteCalculationFailed(_),
)
| (
alice::spot_price::Error::ResumeOnlyMode,
alice::spot_price::Error::ResumeOnlyMode,
) => {}
(alice_assert, error) => {
panic!("Expected: {:?} Actual: {:?}", alice_assert, error)
}
}
let response = match message {
RequestResponseMessage::Response { response, .. } => response,
_ => panic!("Unexpected message {:?} for Bob", message),
};
match response {
spot_price::Response::Error(error) => {
assert_eq!(bob_assert, error.into())
}
_ => panic!("Unexpected response {:?} for Bob", response),
}
}
(alice_event, bob_event) => panic!(
"Received unexpected event, alice emitted {:?} and bob emitted {:?}",
alice_event, bob_event
),
}
}
}
struct AliceBehaviourValues {
pub balance: monero::Amount,
pub lock_fee: monero::Amount,
pub min_buy: bitcoin::Amount,
pub max_buy: bitcoin::Amount,
pub rate: TestRate, // 0.01
pub resume_only: bool,
pub env_config: env::Config,
}
impl AliceBehaviourValues {
pub fn with_balance(mut self, balance: monero::Amount) -> AliceBehaviourValues {
self.balance = balance;
self
}
pub fn with_lock_fee(mut self, lock_fee: monero::Amount) -> AliceBehaviourValues {
self.lock_fee = lock_fee;
self
}
pub fn with_min_buy(mut self, min_buy: bitcoin::Amount) -> AliceBehaviourValues {
self.min_buy = min_buy;
self
}
pub fn with_max_buy(mut self, max_buy: bitcoin::Amount) -> AliceBehaviourValues {
self.max_buy = max_buy;
self
}
pub fn with_resume_only(mut self, resume_only: bool) -> AliceBehaviourValues {
self.resume_only = resume_only;
self
}
pub fn with_rate(mut self, rate: TestRate) -> AliceBehaviourValues {
self.rate = rate;
self
}
pub fn with_env_config(mut self, env_config: env::Config) -> AliceBehaviourValues {
self.env_config = env_config;
self
}
}
#[derive(Clone, Debug)]
pub enum TestRate {
Rate(Rate),
Err(TestRateError),
}
impl TestRate {
pub const RATE: f64 = 0.01;
pub fn from_rate_and_spread(rate: f64, spread: u64) -> Self {
let ask = bitcoin::Amount::from_btc(rate).expect("Static value should never fail");
let spread = Decimal::from(spread);
Self::Rate(Rate::new(ask, spread))
}
pub fn error_rate() -> Self {
Self::Err(TestRateError {})
}
}
impl Default for TestRate {
fn default() -> Self {
TestRate::from_rate_and_spread(Self::RATE, 0)
}
}
#[derive(Debug, Clone, thiserror::Error)]
#[error("Could not fetch rate")]
pub struct TestRateError {}
impl LatestRate for TestRate {
type Error = TestRateError;
fn latest_rate(&mut self) -> Result<Rate, Self::Error> {
match self {
TestRate::Rate(rate) => Ok(*rate),
TestRate::Err(error) => Err(error.clone()),
}
}
}
}

@ -136,7 +136,7 @@ impl State0 {
tx_redeem_fee: bitcoin::Amount, tx_redeem_fee: bitcoin::Amount,
tx_punish_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount,
rng: &mut R, rng: &mut R,
) -> Result<Self> ) -> Self
where where
R: RngCore + CryptoRng, R: RngCore + CryptoRng,
{ {
@ -146,7 +146,7 @@ impl State0 {
let s_a = monero::Scalar::random(rng); let s_a = monero::Scalar::random(rng);
let (dleq_proof_s_a, (S_a_bitcoin, S_a_monero)) = CROSS_CURVE_PROOF_SYSTEM.prove(&s_a, rng); let (dleq_proof_s_a, (S_a_bitcoin, S_a_monero)) = CROSS_CURVE_PROOF_SYSTEM.prove(&s_a, rng);
Ok(Self { Self {
a, a,
s_a, s_a,
v_a, v_a,
@ -163,7 +163,7 @@ impl State0 {
punish_timelock: env_config.bitcoin_punish_timelock, punish_timelock: env_config.bitcoin_punish_timelock,
tx_redeem_fee, tx_redeem_fee,
tx_punish_fee, tx_punish_fee,
}) }
} }
pub fn receive(self, msg: Message0) -> Result<(Uuid, State1)> { pub fn receive(self, msg: Message0) -> Result<(Uuid, State1)> {

@ -1,8 +1,8 @@
//! Run an XMR/BTC swap in the role of Alice. //! Run an XMR/BTC swap in the role of Alice.
//! Alice holds XMR and wishes receive BTC. //! Alice holds XMR and wishes receive BTC.
use crate::asb::{EventLoopHandle, LatestRate};
use crate::bitcoin::ExpiredTimelocks; use crate::bitcoin::ExpiredTimelocks;
use crate::env::Config; use crate::env::Config;
use crate::protocol::alice::event_loop::{EventLoopHandle, LatestRate};
use crate::protocol::alice::{AliceState, Swap}; use crate::protocol::alice::{AliceState, Swap};
use crate::{bitcoin, database, monero}; use crate::{bitcoin, database, monero};
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};

@ -1,28 +1,20 @@
use crate::database::Database;
use crate::{bitcoin, env, monero};
use anyhow::Result;
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result;
use uuid::Uuid; use uuid::Uuid;
pub use self::behaviour::{Behaviour, OutEvent}; use crate::database::Database;
pub use self::cancel::cancel; use crate::{bitcoin, cli, env, monero};
pub use self::event_loop::{EventLoop, EventLoopHandle};
pub use self::refund::refund;
pub use self::state::*; pub use self::state::*;
pub use self::swap::{run, run_until}; pub use self::swap::{run, run_until};
mod behaviour;
pub mod cancel;
pub mod event_loop;
mod execution_setup;
pub mod refund;
pub mod spot_price;
pub mod state; pub mod state;
pub mod swap; pub mod swap;
pub struct Swap { pub struct Swap {
pub state: BobState, pub state: BobState,
pub event_loop_handle: EventLoopHandle, pub event_loop_handle: cli::EventLoopHandle,
pub db: Database, pub db: Database,
pub bitcoin_wallet: Arc<bitcoin::Wallet>, pub bitcoin_wallet: Arc<bitcoin::Wallet>,
pub monero_wallet: Arc<monero::Wallet>, pub monero_wallet: Arc<monero::Wallet>,
@ -39,7 +31,7 @@ impl Swap {
bitcoin_wallet: Arc<bitcoin::Wallet>, bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>, monero_wallet: Arc<monero::Wallet>,
env_config: env::Config, env_config: env::Config,
event_loop_handle: EventLoopHandle, event_loop_handle: cli::EventLoopHandle,
receive_monero_address: monero::Address, receive_monero_address: monero::Address,
btc_amount: bitcoin::Amount, btc_amount: bitcoin::Amount,
) -> Self { ) -> Self {
@ -61,7 +53,7 @@ impl Swap {
bitcoin_wallet: Arc<bitcoin::Wallet>, bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>, monero_wallet: Arc<monero::Wallet>,
env_config: env::Config, env_config: env::Config,
event_loop_handle: EventLoopHandle, event_loop_handle: cli::EventLoopHandle,
receive_monero_address: monero::Address, receive_monero_address: monero::Address,
) -> Result<Self> { ) -> Result<Self> {
let state = db.get_state(id)?.try_into_bob()?.into(); let state = db.get_state(id)?.try_into_bob()?.into();

@ -1,95 +0,0 @@
use crate::network::cbor_request_response::BUF_SIZE;
use crate::protocol::bob::{State0, State2};
use crate::protocol::{bob, Message1, Message3};
use anyhow::{Context, Error, Result};
use libp2p::PeerId;
use libp2p_async_await::BehaviourOutEvent;
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug)]
pub enum OutEvent {
Done(Result<State2>),
}
impl From<BehaviourOutEvent<(), State2, anyhow::Error>> for OutEvent {
fn from(event: BehaviourOutEvent<(), State2, Error>) -> Self {
match event {
BehaviourOutEvent::Outbound(_, Ok(State2)) => OutEvent::Done(Ok(State2)),
BehaviourOutEvent::Outbound(_, Err(e)) => OutEvent::Done(Err(e)),
BehaviourOutEvent::Inbound(..) => unreachable!("Bob only supports outbound"),
}
}
}
#[derive(libp2p::NetworkBehaviour)]
#[behaviour(out_event = "OutEvent", event_process = false)]
pub struct Behaviour {
inner: libp2p_async_await::Behaviour<(), State2, anyhow::Error>,
}
impl Default for Behaviour {
fn default() -> Self {
Self {
inner: libp2p_async_await::Behaviour::new(b"/comit/xmr/btc/execution_setup/1.0.0"),
}
}
}
impl Behaviour {
pub fn run(
&mut self,
alice: PeerId,
state0: State0,
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
) {
self.inner.do_protocol_dialer(alice, move |mut substream| {
let protocol = async move {
tracing::debug!("Starting execution setup with {}", alice);
substream
.write_message(
&serde_cbor::to_vec(&state0.next_message())
.context("Failed to serialize message0")?,
)
.await?;
let message1 =
serde_cbor::from_slice::<Message1>(&substream.read_message(BUF_SIZE).await?)
.context("Failed to deserialize message1")?;
let state1 = state0.receive(bitcoin_wallet.as_ref(), message1).await?;
substream
.write_message(
&serde_cbor::to_vec(&state1.next_message())
.context("Failed to serialize message2")?,
)
.await?;
let message3 =
serde_cbor::from_slice::<Message3>(&substream.read_message(BUF_SIZE).await?)
.context("Failed to deserialize message3")?;
let state2 = state1.receive(message3)?;
substream
.write_message(
&serde_cbor::to_vec(&state2.next_message())
.context("Failed to serialize message4")?,
)
.await?;
Ok(state2)
};
async move { tokio::time::timeout(Duration::from_secs(60), protocol).await? }
})
}
}
impl From<OutEvent> for bob::OutEvent {
fn from(event: OutEvent) -> Self {
match event {
OutEvent::Done(res) => Self::ExecutionSetupDone(Box::new(res)),
}
}
}

@ -1,86 +0,0 @@
use crate::network::cbor_request_response::CborCodec;
use crate::network::spot_price;
use crate::network::spot_price::SpotPriceProtocol;
use crate::protocol::bob::OutEvent;
use libp2p::request_response::{ProtocolSupport, RequestResponseConfig};
use libp2p::PeerId;
const PROTOCOL: &str = spot_price::PROTOCOL;
pub type SpotPriceOutEvent = spot_price::OutEvent;
/// Constructs a new instance of the `spot-price` behaviour to be used by Bob.
///
/// Bob only supports outbound connections, i.e. requesting a spot price for a
/// given amount of BTC in XMR.
pub fn bob() -> spot_price::Behaviour {
spot_price::Behaviour::new(
CborCodec::default(),
vec![(SpotPriceProtocol, ProtocolSupport::Outbound)],
RequestResponseConfig::default(),
)
}
impl From<(PeerId, spot_price::Message)> for OutEvent {
fn from((peer, message): (PeerId, spot_price::Message)) -> Self {
match message {
spot_price::Message::Request { .. } => Self::unexpected_request(peer),
spot_price::Message::Response {
response,
request_id,
} => Self::SpotPriceReceived {
id: request_id,
response,
},
}
}
}
crate::impl_from_rr_event!(SpotPriceOutEvent, OutEvent, PROTOCOL);
#[derive(Clone, Debug, thiserror::Error, PartialEq)]
pub enum Error {
#[error("Seller currently does not accept incoming swap requests, please try again later")]
NoSwapsAccepted,
#[error("Seller refused to buy {buy} because the minimum configured buy limit is {min}")]
AmountBelowMinimum {
min: bitcoin::Amount,
buy: bitcoin::Amount,
},
#[error("Seller refused to buy {buy} because the maximum configured buy limit is {max}")]
AmountAboveMaximum {
max: bitcoin::Amount,
buy: bitcoin::Amount,
},
#[error("Seller's XMR balance is currently too low to fulfill the swap request to buy {buy}, please try again later")]
BalanceTooLow { buy: bitcoin::Amount },
#[error("Seller blockchain network {asb:?} setup did not match your blockchain network setup {cli:?}")]
BlockchainNetworkMismatch {
cli: spot_price::BlockchainNetwork,
asb: spot_price::BlockchainNetwork,
},
/// To be used for errors that cannot be explained on the CLI side (e.g.
/// rate update problems on the seller side)
#[error("Seller encountered a problem, please try again later.")]
Other,
}
impl From<spot_price::Error> for Error {
fn from(error: spot_price::Error) -> Self {
match error {
spot_price::Error::NoSwapsAccepted => Error::NoSwapsAccepted,
spot_price::Error::AmountBelowMinimum { min, buy } => {
Error::AmountBelowMinimum { min, buy }
}
spot_price::Error::AmountAboveMaximum { max, buy } => {
Error::AmountAboveMaximum { max, buy }
}
spot_price::Error::BalanceTooLow { buy } => Error::BalanceTooLow { buy },
spot_price::Error::BlockchainNetworkMismatch { cli, asb } => {
Error::BlockchainNetworkMismatch { cli, asb }
}
spot_price::Error::Other => Error::Other,
}
}
}

@ -26,7 +26,7 @@ pub enum BobState {
Started { Started {
btc_amount: bitcoin::Amount, btc_amount: bitcoin::Amount,
}, },
ExecutionSetupDone(State2), SwapSetupCompleted(State2),
BtcLocked(State3), BtcLocked(State3),
XmrLockProofReceived { XmrLockProofReceived {
state: State3, state: State3,
@ -52,7 +52,7 @@ impl fmt::Display for BobState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
BobState::Started { .. } => write!(f, "quote has been requested"), BobState::Started { .. } => write!(f, "quote has been requested"),
BobState::ExecutionSetupDone(..) => write!(f, "execution setup done"), BobState::SwapSetupCompleted(..) => write!(f, "execution setup done"),
BobState::BtcLocked(..) => write!(f, "btc is locked"), BobState::BtcLocked(..) => write!(f, "btc is locked"),
BobState::XmrLockProofReceived { .. } => { BobState::XmrLockProofReceived { .. } => {
write!(f, "XMR lock transaction transfer proof received") write!(f, "XMR lock transaction transfer proof received")

@ -1,12 +1,11 @@
use crate::bitcoin::{ExpiredTimelocks, TxCancel, TxRefund}; use crate::bitcoin::{ExpiredTimelocks, TxCancel, TxRefund};
use crate::cli::EventLoopHandle;
use crate::database::Swap; use crate::database::Swap;
use crate::env::Config; use crate::network::swap_setup::bob::NewSwap;
use crate::protocol::bob; use crate::protocol::bob;
use crate::protocol::bob::event_loop::EventLoopHandle;
use crate::protocol::bob::state::*; use crate::protocol::bob::state::*;
use crate::{bitcoin, monero}; use crate::{bitcoin, monero};
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use rand::rngs::OsRng;
use tokio::select; use tokio::select;
use uuid::Uuid; use uuid::Uuid;
@ -38,7 +37,6 @@ pub async fn run_until(
&mut swap.event_loop_handle, &mut swap.event_loop_handle,
swap.bitcoin_wallet.as_ref(), swap.bitcoin_wallet.as_ref(),
swap.monero_wallet.as_ref(), swap.monero_wallet.as_ref(),
&swap.env_config,
swap.receive_monero_address, swap.receive_monero_address,
) )
.await?; .await?;
@ -58,7 +56,6 @@ async fn next_state(
event_loop_handle: &mut EventLoopHandle, event_loop_handle: &mut EventLoopHandle,
bitcoin_wallet: &bitcoin::Wallet, bitcoin_wallet: &bitcoin::Wallet,
monero_wallet: &monero::Wallet, monero_wallet: &monero::Wallet,
env_config: &Config,
receive_monero_address: monero::Address, receive_monero_address: monero::Address,
) -> Result<BobState> { ) -> Result<BobState> {
tracing::trace!(%state, "Advancing state"); tracing::trace!(%state, "Advancing state");
@ -73,20 +70,19 @@ async fn next_state(
.estimate_fee(TxCancel::weight(), btc_amount) .estimate_fee(TxCancel::weight(), btc_amount)
.await?; .await?;
let state2 = request_price_and_setup( let state2 = event_loop_handle
swap_id, .setup_swap(NewSwap {
btc_amount, swap_id,
event_loop_handle, btc: btc_amount,
env_config, tx_refund_fee,
bitcoin_refund_address, tx_cancel_fee,
tx_refund_fee, bitcoin_refund_address,
tx_cancel_fee, })
) .await?;
.await?;
BobState::ExecutionSetupDone(state2) BobState::SwapSetupCompleted(state2)
} }
BobState::ExecutionSetupDone(state2) => { BobState::SwapSetupCompleted(state2) => {
// Alice and Bob have exchanged info // Alice and Bob have exchanged info
let (state3, tx_lock) = state2.lock_btc().await?; let (state3, tx_lock) = state2.lock_btc().await?;
let signed_tx = bitcoin_wallet let signed_tx = bitcoin_wallet
@ -268,34 +264,3 @@ async fn next_state(
BobState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id }, BobState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id },
}) })
} }
pub async fn request_price_and_setup(
swap_id: Uuid,
btc: bitcoin::Amount,
event_loop_handle: &mut EventLoopHandle,
env_config: &Config,
bitcoin_refund_address: bitcoin::Address,
tx_refund_fee: bitcoin::Amount,
tx_cancel_fee: bitcoin::Amount,
) -> Result<bob::state::State2> {
let xmr = event_loop_handle.request_spot_price(btc).await?;
tracing::info!(%btc, %xmr, "Spot price");
let state0 = State0::new(
swap_id,
&mut OsRng,
btc,
xmr,
env_config.bitcoin_cancel_timelock,
env_config.bitcoin_punish_timelock,
bitcoin_refund_address,
env_config.monero_finality_confirmations,
tx_refund_fee,
tx_cancel_fee,
);
let state2 = event_loop_handle.execution_setup(state0).await?;
Ok(state2)
}

@ -3,10 +3,11 @@ pub mod harness;
use harness::alice_run_until::is_xmr_lock_transaction_sent; use harness::alice_run_until::is_xmr_lock_transaction_sent;
use harness::bob_run_until::is_btc_locked; use harness::bob_run_until::is_btc_locked;
use harness::FastCancelConfig; use harness::FastCancelConfig;
use swap::protocol::alice::event_loop::FixedRate; use swap::asb::FixedRate;
use swap::protocol::alice::AliceState; use swap::protocol::alice::AliceState;
use swap::protocol::bob::BobState; use swap::protocol::bob::BobState;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};
use swap::{asb, cli};
#[tokio::test] #[tokio::test]
async fn given_alice_and_bob_manually_refund_after_funds_locked_both_refund() { async fn given_alice_and_bob_manually_refund_after_funds_locked_both_refund() {
@ -50,7 +51,7 @@ async fn given_alice_and_bob_manually_refund_after_funds_locked_both_refund() {
// Bob manually cancels // Bob manually cancels
bob_join_handle.abort(); bob_join_handle.abort();
let (_, state) = let (_, state) =
bob::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false).await??; cli::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false).await??;
assert!(matches!(state, BobState::BtcCancelled { .. })); assert!(matches!(state, BobState::BtcCancelled { .. }));
let (bob_swap, bob_join_handle) = ctx let (bob_swap, bob_join_handle) = ctx
@ -61,7 +62,7 @@ async fn given_alice_and_bob_manually_refund_after_funds_locked_both_refund() {
// Bob manually refunds // Bob manually refunds
bob_join_handle.abort(); bob_join_handle.abort();
let bob_state = let bob_state =
bob::refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false).await??; cli::refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false).await??;
ctx.assert_bob_refunded(bob_state).await; ctx.assert_bob_refunded(bob_state).await;
@ -74,7 +75,7 @@ async fn given_alice_and_bob_manually_refund_after_funds_locked_both_refund() {
AliceState::XmrLockTransactionSent { .. } AliceState::XmrLockTransactionSent { .. }
)); ));
alice::cancel( asb::cancel(
alice_swap.swap_id, alice_swap.swap_id,
alice_swap.bitcoin_wallet, alice_swap.bitcoin_wallet,
alice_swap.db, alice_swap.db,
@ -86,7 +87,7 @@ async fn given_alice_and_bob_manually_refund_after_funds_locked_both_refund() {
ctx.restart_alice().await; ctx.restart_alice().await;
let alice_swap = ctx.alice_next_swap().await; let alice_swap = ctx.alice_next_swap().await;
assert!(matches!(alice_swap.state, AliceState::BtcCancelled { .. })); assert!(matches!(alice_swap.state, AliceState::BtcCancelled { .. }));
let alice_state = alice::refund( let alice_state = asb::refund(
alice_swap.swap_id, alice_swap.swap_id,
alice_swap.bitcoin_wallet, alice_swap.bitcoin_wallet,
alice_swap.monero_wallet, alice_swap.monero_wallet,

@ -3,10 +3,11 @@ pub mod harness;
use harness::alice_run_until::is_xmr_lock_transaction_sent; use harness::alice_run_until::is_xmr_lock_transaction_sent;
use harness::bob_run_until::is_btc_locked; use harness::bob_run_until::is_btc_locked;
use harness::SlowCancelConfig; use harness::SlowCancelConfig;
use swap::protocol::alice::event_loop::FixedRate; use swap::asb::FixedRate;
use swap::protocol::alice::AliceState; use swap::protocol::alice::AliceState;
use swap::protocol::bob::BobState; use swap::protocol::bob::BobState;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};
use swap::{asb, cli};
#[tokio::test] #[tokio::test]
async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors() { async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors() {
@ -37,12 +38,12 @@ async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors()
)); ));
// Bob tries but fails to manually cancel // Bob tries but fails to manually cancel
let result = bob::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false) let result = cli::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false)
.await? .await?
.unwrap_err(); .unwrap_err();
assert!(matches!( assert!(matches!(
result, result,
bob::cancel::Error::CancelTimelockNotExpiredYet cli::cancel::Error::CancelTimelockNotExpiredYet
)); ));
ctx.restart_alice().await; ctx.restart_alice().await;
@ -53,7 +54,7 @@ async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors()
)); ));
// Alice tries but fails manual cancel // Alice tries but fails manual cancel
let result = alice::cancel( let result = asb::cancel(
alice_swap.swap_id, alice_swap.swap_id,
alice_swap.bitcoin_wallet, alice_swap.bitcoin_wallet,
alice_swap.db, alice_swap.db,
@ -63,7 +64,7 @@ async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors()
.unwrap_err(); .unwrap_err();
assert!(matches!( assert!(matches!(
result, result,
alice::cancel::Error::CancelTimelockNotExpiredYet asb::cancel::Error::CancelTimelockNotExpiredYet
)); ));
let (bob_swap, bob_join_handle) = ctx let (bob_swap, bob_join_handle) = ctx
@ -72,10 +73,10 @@ async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors()
assert!(matches!(bob_swap.state, BobState::BtcLocked { .. })); assert!(matches!(bob_swap.state, BobState::BtcLocked { .. }));
// Bob tries but fails to manually refund // Bob tries but fails to manually refund
let result = bob::refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false) let result = cli::refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, false)
.await? .await?
.unwrap_err(); .unwrap_err();
assert!(matches!(result, bob::refund::SwapNotCancelledYet(_))); assert!(matches!(result, cli::refund::SwapNotCancelledYet(_)));
let (bob_swap, _) = ctx let (bob_swap, _) = ctx
.stop_and_resume_bob_from_db(bob_join_handle, swap_id) .stop_and_resume_bob_from_db(bob_join_handle, swap_id)
@ -90,7 +91,7 @@ async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors()
)); ));
// Alice tries but fails manual cancel // Alice tries but fails manual cancel
let result = alice::refund( let result = asb::refund(
alice_swap.swap_id, alice_swap.swap_id,
alice_swap.bitcoin_wallet, alice_swap.bitcoin_wallet,
alice_swap.monero_wallet, alice_swap.monero_wallet,
@ -99,7 +100,7 @@ async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors()
) )
.await? .await?
.unwrap_err(); .unwrap_err();
assert!(matches!(result, alice::refund::Error::SwapNotCancelled)); assert!(matches!(result, asb::refund::Error::SwapNotCancelled));
ctx.restart_alice().await; ctx.restart_alice().await;
let alice_swap = ctx.alice_next_swap().await; let alice_swap = ctx.alice_next_swap().await;

@ -3,10 +3,11 @@ pub mod harness;
use harness::alice_run_until::is_xmr_lock_transaction_sent; use harness::alice_run_until::is_xmr_lock_transaction_sent;
use harness::bob_run_until::is_btc_locked; use harness::bob_run_until::is_btc_locked;
use harness::SlowCancelConfig; use harness::SlowCancelConfig;
use swap::protocol::alice::event_loop::FixedRate; use swap::asb::FixedRate;
use swap::protocol::alice::AliceState; use swap::protocol::alice::AliceState;
use swap::protocol::bob::BobState; use swap::protocol::bob::BobState;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};
use swap::{asb, cli};
#[tokio::test] #[tokio::test]
async fn given_alice_and_bob_manually_force_cancel_when_timelock_not_expired_errors() { async fn given_alice_and_bob_manually_force_cancel_when_timelock_not_expired_errors() {
@ -37,7 +38,7 @@ async fn given_alice_and_bob_manually_force_cancel_when_timelock_not_expired_err
)); ));
// Bob tries but fails to manually cancel // Bob tries but fails to manually cancel
let result = bob::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, true).await; let result = cli::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, true).await;
assert!(matches!(result, Err(_))); assert!(matches!(result, Err(_)));
ctx.restart_alice().await; ctx.restart_alice().await;
@ -48,7 +49,7 @@ async fn given_alice_and_bob_manually_force_cancel_when_timelock_not_expired_err
)); ));
// Alice tries but fails manual cancel // Alice tries but fails manual cancel
let is_outer_err = alice::cancel( let is_outer_err = asb::cancel(
alice_swap.swap_id, alice_swap.swap_id,
alice_swap.bitcoin_wallet, alice_swap.bitcoin_wallet,
alice_swap.db, alice_swap.db,
@ -64,7 +65,7 @@ async fn given_alice_and_bob_manually_force_cancel_when_timelock_not_expired_err
assert!(matches!(bob_swap.state, BobState::BtcLocked { .. })); assert!(matches!(bob_swap.state, BobState::BtcLocked { .. }));
// Bob tries but fails to manually refund // Bob tries but fails to manually refund
let is_outer_err = bob::refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, true) let is_outer_err = cli::refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db, true)
.await .await
.is_err(); .is_err();
assert!(is_outer_err); assert!(is_outer_err);
@ -82,7 +83,7 @@ async fn given_alice_and_bob_manually_force_cancel_when_timelock_not_expired_err
)); ));
// Alice tries but fails manual cancel // Alice tries but fails manual cancel
let refund_tx_not_published_yet = alice::refund( let refund_tx_not_published_yet = asb::refund(
alice_swap.swap_id, alice_swap.swap_id,
alice_swap.bitcoin_wallet, alice_swap.bitcoin_wallet,
alice_swap.monero_wallet, alice_swap.monero_wallet,
@ -93,7 +94,7 @@ async fn given_alice_and_bob_manually_force_cancel_when_timelock_not_expired_err
.unwrap_err(); .unwrap_err();
assert!(matches!( assert!(matches!(
refund_tx_not_published_yet, refund_tx_not_published_yet,
alice::refund::Error::RefundTransactionNotPublishedYet(..) asb::refund::Error::RefundTransactionNotPublishedYet(..)
)); ));
ctx.restart_alice().await; ctx.restart_alice().await;

@ -3,7 +3,8 @@ pub mod harness;
use harness::alice_run_until::is_xmr_lock_transaction_sent; use harness::alice_run_until::is_xmr_lock_transaction_sent;
use harness::bob_run_until::is_btc_locked; use harness::bob_run_until::is_btc_locked;
use harness::FastPunishConfig; use harness::FastPunishConfig;
use swap::protocol::alice::event_loop::FixedRate; use swap::asb;
use swap::asb::FixedRate;
use swap::protocol::alice::AliceState; use swap::protocol::alice::AliceState;
use swap::protocol::bob::BobState; use swap::protocol::bob::BobState;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};
@ -47,7 +48,7 @@ async fn alice_manually_punishes_after_bob_dead() {
ctx.restart_alice().await; ctx.restart_alice().await;
let alice_swap = ctx.alice_next_swap().await; let alice_swap = ctx.alice_next_swap().await;
let (_, alice_state) = alice::cancel( let (_, alice_state) = asb::cancel(
alice_swap.swap_id, alice_swap.swap_id,
alice_swap.bitcoin_wallet, alice_swap.bitcoin_wallet,
alice_swap.db, alice_swap.db,
@ -70,7 +71,7 @@ async fn alice_manually_punishes_after_bob_dead() {
ctx.restart_alice().await; ctx.restart_alice().await;
let alice_swap = ctx.alice_next_swap().await; let alice_swap = ctx.alice_next_swap().await;
let (_, alice_state) = alice::punish( let (_, alice_state) = asb::punish(
alice_swap.swap_id, alice_swap.swap_id,
alice_swap.bitcoin_wallet, alice_swap.bitcoin_wallet,
alice_swap.db, alice_swap.db,

@ -2,8 +2,8 @@ pub mod harness;
use harness::alice_run_until::is_encsig_learned; use harness::alice_run_until::is_encsig_learned;
use harness::SlowCancelConfig; use harness::SlowCancelConfig;
use swap::protocol::alice::event_loop::FixedRate; use swap::asb;
use swap::protocol::alice::redeem::Finality; use swap::asb::{Finality, FixedRate};
use swap::protocol::alice::AliceState; use swap::protocol::alice::AliceState;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};
@ -28,7 +28,7 @@ async fn alice_manually_redeems_after_enc_sig_learned() {
// manual redeem // manual redeem
ctx.restart_alice().await; ctx.restart_alice().await;
let alice_swap = ctx.alice_next_swap().await; let alice_swap = ctx.alice_next_swap().await;
let (_, alice_state) = alice::redeem( let (_, alice_state) = asb::redeem(
alice_swap.swap_id, alice_swap.swap_id,
alice_swap.bitcoin_wallet, alice_swap.bitcoin_wallet,
alice_swap.db, alice_swap.db,

@ -3,7 +3,7 @@ pub mod harness;
use harness::alice_run_until::is_xmr_lock_transaction_sent; use harness::alice_run_until::is_xmr_lock_transaction_sent;
use harness::bob_run_until::is_btc_locked; use harness::bob_run_until::is_btc_locked;
use harness::FastPunishConfig; use harness::FastPunishConfig;
use swap::protocol::alice::event_loop::FixedRate; use swap::asb::FixedRate;
use swap::protocol::alice::AliceState; use swap::protocol::alice::AliceState;
use swap::protocol::bob::BobState; use swap::protocol::bob::BobState;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};

@ -2,7 +2,7 @@ pub mod harness;
use harness::alice_run_until::is_xmr_lock_transaction_sent; use harness::alice_run_until::is_xmr_lock_transaction_sent;
use harness::FastCancelConfig; use harness::FastCancelConfig;
use swap::protocol::alice::event_loop::FixedRate; use swap::asb::FixedRate;
use swap::protocol::alice::AliceState; use swap::protocol::alice::AliceState;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};

@ -2,7 +2,7 @@ pub mod harness;
use harness::bob_run_until::is_xmr_locked; use harness::bob_run_until::is_xmr_locked;
use harness::SlowCancelConfig; use harness::SlowCancelConfig;
use swap::protocol::alice::event_loop::FixedRate; use swap::asb::FixedRate;
use swap::protocol::alice::AliceState; use swap::protocol::alice::AliceState;
use swap::protocol::bob::BobState; use swap::protocol::bob::BobState;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};

@ -2,7 +2,7 @@ pub mod harness;
use harness::bob_run_until::is_btc_locked; use harness::bob_run_until::is_btc_locked;
use harness::SlowCancelConfig; use harness::SlowCancelConfig;
use swap::protocol::alice::event_loop::FixedRate; use swap::asb::FixedRate;
use swap::protocol::alice::AliceState; use swap::protocol::alice::AliceState;
use swap::protocol::bob::BobState; use swap::protocol::bob::BobState;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};

@ -1,7 +1,7 @@
pub mod harness; pub mod harness;
use harness::SlowCancelConfig; use harness::SlowCancelConfig;
use swap::protocol::alice::event_loop::FixedRate; use swap::asb::FixedRate;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};
use tokio::join; use tokio::join;

@ -2,7 +2,7 @@ pub mod harness;
use harness::alice_run_until::is_xmr_lock_transaction_sent; use harness::alice_run_until::is_xmr_lock_transaction_sent;
use harness::SlowCancelConfig; use harness::SlowCancelConfig;
use swap::protocol::alice::event_loop::FixedRate; use swap::asb::FixedRate;
use swap::protocol::alice::AliceState; use swap::protocol::alice::AliceState;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};

@ -2,7 +2,7 @@ pub mod harness;
use harness::bob_run_until::is_xmr_locked; use harness::bob_run_until::is_xmr_locked;
use harness::SlowCancelConfig; use harness::SlowCancelConfig;
use swap::protocol::alice::event_loop::FixedRate; use swap::asb::FixedRate;
use swap::protocol::bob::BobState; use swap::protocol::bob::BobState;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};

@ -2,7 +2,7 @@ pub mod harness;
use harness::bob_run_until::is_xmr_locked; use harness::bob_run_until::is_xmr_locked;
use harness::SlowCancelConfig; use harness::SlowCancelConfig;
use swap::protocol::alice::event_loop::FixedRate; use swap::asb::FixedRate;
use swap::protocol::bob::BobState; use swap::protocol::bob::BobState;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};

@ -14,16 +14,16 @@ use std::fmt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use swap::asb::FixedRate;
use swap::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxPunish, TxRedeem, TxRefund}; use swap::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxPunish, TxRedeem, TxRefund};
use swap::database::Database; use swap::database::Database;
use swap::env::{Config, GetConfig}; use swap::env::{Config, GetConfig};
use swap::network::swarm; use swap::network::swarm;
use swap::protocol::alice::event_loop::FixedRate;
use swap::protocol::alice::{AliceState, Swap}; use swap::protocol::alice::{AliceState, Swap};
use swap::protocol::bob::BobState; use swap::protocol::bob::BobState;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};
use swap::seed::Seed; use swap::seed::Seed;
use swap::{bitcoin, env, monero}; use swap::{asb, bitcoin, cli, env, monero};
use tempfile::tempdir; use tempfile::tempdir;
use testcontainers::clients::Cli; use testcontainers::clients::Cli;
use testcontainers::{Container, Docker, RunArgs}; use testcontainers::{Container, Docker, RunArgs};
@ -224,8 +224,6 @@ async fn start_alice(
) -> (AliceApplicationHandle, Receiver<alice::Swap>) { ) -> (AliceApplicationHandle, Receiver<alice::Swap>) {
let db = Arc::new(Database::open(db_path.as_path()).unwrap()); let db = Arc::new(Database::open(db_path.as_path()).unwrap());
let current_balance = monero_wallet.get_balance().await.unwrap();
let lock_fee = monero_wallet.static_tx_fee_estimate();
let min_buy = bitcoin::Amount::from_sat(u64::MIN); let min_buy = bitcoin::Amount::from_sat(u64::MIN);
let max_buy = bitcoin::Amount::from_sat(u64::MAX); let max_buy = bitcoin::Amount::from_sat(u64::MAX);
let latest_rate = FixedRate::default(); let latest_rate = FixedRate::default();
@ -233,8 +231,6 @@ async fn start_alice(
let mut swarm = swarm::asb( let mut swarm = swarm::asb(
&seed, &seed,
current_balance,
lock_fee,
min_buy, min_buy,
max_buy, max_buy,
latest_rate, latest_rate,
@ -244,7 +240,7 @@ async fn start_alice(
.unwrap(); .unwrap();
swarm.listen_on(listen_address).unwrap(); swarm.listen_on(listen_address).unwrap();
let (event_loop, swap_handle) = alice::EventLoop::new( let (event_loop, swap_handle) = asb::EventLoop::new(
swarm, swarm,
env_config, env_config,
bitcoin_wallet, bitcoin_wallet,
@ -403,7 +399,7 @@ struct BobParams {
} }
impl BobParams { impl BobParams {
pub async fn new_swap_from_db(&self, swap_id: Uuid) -> Result<(bob::Swap, bob::EventLoop)> { pub async fn new_swap_from_db(&self, swap_id: Uuid) -> Result<(bob::Swap, cli::EventLoop)> {
let (event_loop, handle) = self.new_eventloop(swap_id).await?; let (event_loop, handle) = self.new_eventloop(swap_id).await?;
let db = Database::open(&self.db_path)?; let db = Database::open(&self.db_path)?;
@ -423,7 +419,7 @@ impl BobParams {
pub async fn new_swap( pub async fn new_swap(
&self, &self,
btc_amount: bitcoin::Amount, btc_amount: bitcoin::Amount,
) -> Result<(bob::Swap, bob::EventLoop)> { ) -> Result<(bob::Swap, cli::EventLoop)> {
let swap_id = Uuid::new_v4(); let swap_id = Uuid::new_v4();
let (event_loop, handle) = self.new_eventloop(swap_id).await?; let (event_loop, handle) = self.new_eventloop(swap_id).await?;
@ -446,21 +442,22 @@ impl BobParams {
pub async fn new_eventloop( pub async fn new_eventloop(
&self, &self,
swap_id: Uuid, swap_id: Uuid,
) -> Result<(bob::EventLoop, bob::EventLoopHandle)> { ) -> Result<(cli::EventLoop, cli::EventLoopHandle)> {
let tor_socks5_port = get_port() let tor_socks5_port = get_port()
.expect("We don't care about Tor in the tests so we get a free port to disable it."); .expect("We don't care about Tor in the tests so we get a free port to disable it.");
let mut swarm = swarm::cli(&self.seed, self.alice_peer_id, tor_socks5_port).await?; let mut swarm = swarm::cli(
&self.seed,
self.alice_peer_id,
tor_socks5_port,
self.env_config,
self.bitcoin_wallet.clone(),
)
.await?;
swarm swarm
.behaviour_mut() .behaviour_mut()
.add_address(self.alice_peer_id, self.alice_address.clone()); .add_address(self.alice_peer_id, self.alice_address.clone());
bob::EventLoop::new( cli::EventLoop::new(swap_id, swarm, self.alice_peer_id, self.env_config)
swap_id,
swarm,
self.alice_peer_id,
self.bitcoin_wallet.clone(),
self.env_config,
)
} }
} }

@ -2,7 +2,7 @@ pub mod harness;
use harness::bob_run_until::is_btc_locked; use harness::bob_run_until::is_btc_locked;
use harness::FastPunishConfig; use harness::FastPunishConfig;
use swap::protocol::alice::event_loop::FixedRate; use swap::asb::FixedRate;
use swap::protocol::bob::BobState; use swap::protocol::bob::BobState;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};

Loading…
Cancel
Save