Replace quote with spot-price protocol

This is essentially functionally equivalent but includes some
cleanups by removing a layer of abstraction: `spot_price::Behaviour`
is now just a type-alias for a request-response behaviour.
This commit is contained in:
Thomas Eizinger 2021-03-03 15:45:43 +11:00
parent 2440964385
commit 7042ed9441
No known key found for this signature in database
GPG Key ID: 651AC83A6C6C8B96
11 changed files with 293 additions and 347 deletions

View File

@ -1,5 +1,6 @@
pub mod peer_tracker; pub mod peer_tracker;
pub mod request_response; pub mod request_response;
pub mod spot_price;
pub mod transport; pub mod transport;
use futures::prelude::*; use futures::prelude::*;

View File

@ -13,21 +13,12 @@ pub const TIMEOUT: u64 = 3600; // One hour.
/// Message receive buffer. /// Message receive buffer.
pub const BUF_SIZE: usize = 1024 * 1024; pub const BUF_SIZE: usize = 1024 * 1024;
#[derive(Debug, Clone, Copy, Default)]
pub struct Swap;
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct TransferProofProtocol; pub struct TransferProofProtocol;
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct EncryptedSignatureProtocol; pub struct EncryptedSignatureProtocol;
impl ProtocolName for Swap {
fn protocol_name(&self) -> &[u8] {
b"/comit/xmr/btc/swap/1.0.0"
}
}
impl ProtocolName for TransferProofProtocol { impl ProtocolName for TransferProofProtocol {
fn protocol_name(&self) -> &[u8] { fn protocol_name(&self) -> &[u8] {
b"/comit/xmr/btc/transfer_proof/1.0.0" b"/comit/xmr/btc/transfer_proof/1.0.0"

View File

@ -0,0 +1,66 @@
use crate::{bitcoin, monero, network::request_response::CborCodec};
use libp2p::{
core::ProtocolName,
request_response::{
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
},
};
use serde::{Deserialize, Serialize};
pub type OutEvent = RequestResponseEvent<SpotPriceRequest, SpotPriceResponse>;
/// 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] {
b"/comit/xmr/btc/spot-price/1.0.0"
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SpotPriceRequest {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
pub btc: bitcoin::Amount,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SpotPriceResponse {
pub xmr: monero::Amount,
}
pub type Behaviour =
RequestResponse<CborCodec<SpotPriceProtocol, SpotPriceRequest, SpotPriceResponse>>;
/// Constructs a new instance of the `spot-price` behaviour to be used by Alice.
///
/// Alice only supports inbound connections, i.e. providing spot prices for BTC
/// in XMR.
pub fn alice() -> Behaviour {
Behaviour::new(
CborCodec::default(),
vec![(SpotPriceProtocol, ProtocolSupport::Inbound)],
RequestResponseConfig::default(),
)
}
/// 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() -> Behaviour {
Behaviour::new(
CborCodec::default(),
vec![(SpotPriceProtocol, ProtocolSupport::Outbound)],
RequestResponseConfig::default(),
)
}

View File

@ -8,7 +8,6 @@ pub use self::{
behaviour::{Behaviour, OutEvent}, behaviour::{Behaviour, OutEvent},
event_loop::{EventLoop, EventLoopHandle}, event_loop::{EventLoop, EventLoopHandle},
execution_setup::Message1, execution_setup::Message1,
quote_response::*,
state::*, state::*,
swap::{run, run_until}, swap::{run, run_until},
transfer_proof::TransferProof, transfer_proof::TransferProof,
@ -19,7 +18,6 @@ mod behaviour;
mod encrypted_signature; mod encrypted_signature;
pub mod event_loop; pub mod event_loop;
mod execution_setup; mod execution_setup;
mod quote_response;
pub mod state; pub mod state;
mod steps; mod steps;
pub mod swap; pub mod swap;

View File

@ -1,24 +1,35 @@
use crate::{ use crate::{
network::{peer_tracker, peer_tracker::PeerTracker}, bitcoin,
execution_params::ExecutionParams,
monero,
network::{
peer_tracker,
peer_tracker::PeerTracker,
spot_price,
spot_price::{SpotPriceRequest, SpotPriceResponse},
},
protocol::{ protocol::{
alice::{ alice::{
encrypted_signature, execution_setup, quote_response, transfer_proof, QuoteResponse, encrypted_signature, execution_setup, transfer_proof, State0, State3, TransferProof,
State0, State3, TransferProof,
}, },
bob::{EncryptedSignature, QuoteRequest}, bob::EncryptedSignature,
}, },
}; };
use anyhow::{Error, Result}; use anyhow::{anyhow, Error, Result};
use libp2p::{request_response::ResponseChannel, NetworkBehaviour, PeerId}; use libp2p::{
request_response::{RequestResponseMessage, ResponseChannel},
NetworkBehaviour, PeerId,
};
use rand::{CryptoRng, RngCore};
use tracing::debug; use tracing::debug;
#[derive(Debug)] #[derive(Debug)]
pub enum OutEvent { pub enum OutEvent {
ConnectionEstablished(PeerId), ConnectionEstablished(PeerId),
QuoteRequest { SpotPriceRequested {
msg: QuoteRequest, msg: SpotPriceRequest,
channel: ResponseChannel<QuoteResponse>, channel: ResponseChannel<SpotPriceResponse>,
bob_peer_id: PeerId, peer: PeerId,
}, },
ExecutionSetupDone { ExecutionSetupDone {
bob_peer_id: PeerId, bob_peer_id: PeerId,
@ -43,21 +54,37 @@ impl From<peer_tracker::OutEvent> for OutEvent {
} }
} }
impl From<quote_response::OutEvent> for OutEvent { impl From<spot_price::OutEvent> for OutEvent {
fn from(event: quote_response::OutEvent) -> Self { fn from(event: spot_price::OutEvent) -> Self {
use crate::protocol::alice::quote_response::OutEvent::*;
match event { match event {
MsgReceived { spot_price::OutEvent::Message {
msg, peer,
message:
RequestResponseMessage::Request {
channel, channel,
bob_peer_id, request: msg,
} => OutEvent::QuoteRequest { ..
msg,
channel,
bob_peer_id,
}, },
ResponseSent => OutEvent::ResponseSent, } => OutEvent::SpotPriceRequested { msg, channel, peer },
Failure(err) => OutEvent::Failure(err.context("Quote Request/Response failure")), spot_price::OutEvent::Message {
message: RequestResponseMessage::Response { .. },
..
} => OutEvent::Failure(anyhow!(
"Alice is only meant to hand out spot prices, not receive them"
)),
spot_price::OutEvent::ResponseSent { .. } => OutEvent::ResponseSent,
spot_price::OutEvent::InboundFailure { peer, error, .. } => OutEvent::Failure(anyhow!(
"spot_price protocol with peer {} failed due to {:?}",
peer,
error
)),
spot_price::OutEvent::OutboundFailure { peer, error, .. } => {
OutEvent::Failure(anyhow!(
"spot_price protocol with peer {} failed due to {:?}",
peer,
error
))
}
} }
} }
} }
@ -103,29 +130,62 @@ impl From<encrypted_signature::OutEvent> for OutEvent {
} }
/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Alice. /// A `NetworkBehaviour` that represents an XMR/BTC swap node as Alice.
#[derive(NetworkBehaviour, Default)] #[derive(NetworkBehaviour)]
#[behaviour(out_event = "OutEvent", event_process = false)] #[behaviour(out_event = "OutEvent", event_process = false)]
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Behaviour { pub struct Behaviour {
pt: PeerTracker, pt: PeerTracker,
quote_response: quote_response::Behaviour, spot_price: spot_price::Behaviour,
execution_setup: execution_setup::Behaviour, execution_setup: execution_setup::Behaviour,
transfer_proof: transfer_proof::Behaviour, transfer_proof: transfer_proof::Behaviour,
encrypted_signature: encrypted_signature::Behaviour, encrypted_signature: encrypted_signature::Behaviour,
} }
impl Default for Behaviour {
fn default() -> Self {
Self {
pt: Default::default(),
spot_price: spot_price::alice(),
execution_setup: Default::default(),
transfer_proof: Default::default(),
encrypted_signature: Default::default(),
}
}
}
impl Behaviour { impl Behaviour {
pub fn send_quote_response( pub fn send_spot_price(
&mut self, &mut self,
channel: ResponseChannel<QuoteResponse>, channel: ResponseChannel<SpotPriceResponse>,
quote_response: QuoteResponse, response: SpotPriceResponse,
) -> Result<()> { ) -> Result<()> {
self.quote_response.send(channel, quote_response)?; self.spot_price
.send_response(channel, response)
.map_err(|_| anyhow!("failed to respond with spot price"))?;
Ok(()) Ok(())
} }
pub fn start_execution_setup(&mut self, bob_peer_id: PeerId, state0: State0) { pub async fn start_execution_setup(
self.execution_setup.run(bob_peer_id, state0); &mut self,
peer: PeerId,
btc: bitcoin::Amount,
xmr: monero::Amount,
execution_params: ExecutionParams,
bitcoin_wallet: &bitcoin::Wallet,
rng: &mut (impl RngCore + CryptoRng),
) -> Result<()> {
let state0 = State0::new(btc, xmr, execution_params, bitcoin_wallet, rng).await?;
tracing::info!(
%peer,
"Starting execution setup to sell {} for {}",
xmr, btc,
);
self.execution_setup.run(peer, state0);
Ok(())
} }
/// Send Transfer Proof to Bob. /// Send Transfer Proof to Bob.

View File

@ -5,25 +5,21 @@ use crate::{
execution_params::ExecutionParams, execution_params::ExecutionParams,
monero, monero,
monero::BalanceTooLow, monero::BalanceTooLow,
network::{transport, TokioExecutor}, network::{spot_price::SpotPriceResponse, transport, TokioExecutor},
protocol::{ protocol::{
alice, alice,
alice::{ alice::{AliceState, Behaviour, OutEvent, State3, Swap, TransferProof},
AliceState, Behaviour, OutEvent, QuoteResponse, State0, State3, Swap, TransferProof, bob::EncryptedSignature,
},
bob::{EncryptedSignature, QuoteRequest},
}, },
seed::Seed, seed::Seed,
}; };
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use futures::future::RemoteHandle; use futures::future::RemoteHandle;
use libp2p::{ use libp2p::{core::Multiaddr, futures::FutureExt, PeerId, Swarm};
core::Multiaddr, futures::FutureExt, request_response::ResponseChannel, PeerId, Swarm,
};
use rand::rngs::OsRng; use rand::rngs::OsRng;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::{broadcast, mpsc, mpsc::error::SendError}; use tokio::sync::{broadcast, mpsc, mpsc::error::SendError};
use tracing::{debug, error, info, trace}; use tracing::{debug, error, trace};
use uuid::Uuid; use uuid::Uuid;
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
@ -166,9 +162,30 @@ where
OutEvent::ConnectionEstablished(alice) => { OutEvent::ConnectionEstablished(alice) => {
debug!("Connection Established with {}", alice); debug!("Connection Established with {}", alice);
} }
OutEvent::QuoteRequest { msg, channel, bob_peer_id } => { OutEvent::SpotPriceRequested { msg, channel, peer } => {
if let Err(error) = self.handle_quote_request(msg, channel, bob_peer_id, self.monero_wallet.clone()).await { let btc = msg.btc;
error!("Failed to handle quote request: {:#}", error); let xmr = match self.handle_spot_price_request(btc, self.monero_wallet.clone()).await {
Ok(xmr) => xmr,
Err(e) => {
tracing::warn!(%peer, "failed to produce spot price for {}: {:#}", btc, e);
continue;
}
};
match self.swarm.send_spot_price(channel, SpotPriceResponse { xmr }) {
Ok(_) => {},
Err(e) => {
// if we can't respond, the peer probably just disconnected so it is not a huge deal, only log this on debug
debug!(%peer, "failed to respond with spot price: {:#}", e);
continue;
}
}
match self.swarm.start_execution_setup(peer, btc, xmr, self.execution_params, self.bitcoin_wallet.as_ref(), &mut OsRng).await {
Ok(_) => {},
Err(e) => {
tracing::warn!(%peer, "failed to start execution setup: {:#}", e);
}
} }
} }
OutEvent::ExecutionSetupDone{bob_peer_id, state3} => { OutEvent::ExecutionSetupDone{bob_peer_id, state3} => {
@ -199,65 +216,34 @@ where
} }
} }
async fn handle_quote_request( async fn handle_spot_price_request(
&mut self, &mut self,
quote_request: QuoteRequest, btc: bitcoin::Amount,
channel: ResponseChannel<QuoteResponse>,
bob_peer_id: PeerId,
monero_wallet: Arc<monero::Wallet>, monero_wallet: Arc<monero::Wallet>,
) -> Result<()> { ) -> Result<monero::Amount> {
// 1. Check if acceptable request
// 2. Send response
let rate = self let rate = self
.rate_service .rate_service
.latest_rate() .latest_rate()
.context("Failed to get latest rate")?; .context("Failed to get latest rate")?;
let btc_amount = quote_request.btc_amount; if btc > self.max_buy {
if btc_amount > self.max_buy {
bail!(MaximumBuyAmountExceeded { bail!(MaximumBuyAmountExceeded {
actual: btc_amount, actual: btc,
max: self.max_buy max: self.max_buy
}) })
} }
let xmr_balance = monero_wallet.get_balance().await?; let xmr_balance = monero_wallet.get_balance().await?;
let xmr_lock_fees = monero_wallet.static_tx_fee_estimate(); let xmr_lock_fees = monero_wallet.static_tx_fee_estimate();
let xmr_amount = rate.sell_quote(btc_amount)?; let xmr = rate.sell_quote(btc)?;
if xmr_balance < xmr_amount + xmr_lock_fees { if xmr_balance < xmr + xmr_lock_fees {
bail!(BalanceTooLow { bail!(BalanceTooLow {
balance: xmr_balance balance: xmr_balance
}) })
} }
let quote_response = QuoteResponse { xmr_amount }; Ok(xmr)
self.swarm
.send_quote_response(channel, quote_response)
.context("Failed to send quote response")?;
// 3. Start setup execution
let state0 = State0::new(
btc_amount,
xmr_amount,
self.execution_params,
self.bitcoin_wallet.as_ref(),
&mut OsRng,
)
.await?;
info!(
"Starting execution setup to sell {} for {} (rate of {}) with {}",
xmr_amount, btc_amount, rate, bob_peer_id
);
self.swarm.start_execution_setup(bob_peer_id, state0);
// Continues once the execution setup protocol is done
Ok(())
} }
async fn handle_execution_setup_done( async fn handle_execution_setup_done(

View File

@ -1,109 +0,0 @@
use crate::{
monero,
network::request_response::{CborCodec, Swap, TIMEOUT},
protocol::bob::QuoteRequest,
};
use anyhow::{anyhow, Error, Result};
use libp2p::{
request_response::{
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
RequestResponseMessage, ResponseChannel,
},
NetworkBehaviour, PeerId,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use tracing::debug;
#[derive(Debug)]
pub enum OutEvent {
MsgReceived {
msg: QuoteRequest,
channel: ResponseChannel<QuoteResponse>,
bob_peer_id: PeerId,
},
ResponseSent,
Failure(Error),
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct QuoteResponse {
pub xmr_amount: monero::Amount,
}
impl From<RequestResponseEvent<QuoteRequest, QuoteResponse>> for OutEvent {
fn from(event: RequestResponseEvent<QuoteRequest, QuoteResponse>) -> Self {
match event {
RequestResponseEvent::Message {
peer,
message:
RequestResponseMessage::Request {
request, channel, ..
},
..
} => {
debug!("Received quote request from {}", peer);
OutEvent::MsgReceived {
msg: request,
channel,
bob_peer_id: peer,
}
}
RequestResponseEvent::Message {
message: RequestResponseMessage::Response { .. },
..
} => OutEvent::Failure(anyhow!("Alice should not get a Response")),
RequestResponseEvent::InboundFailure { error, .. } => {
OutEvent::Failure(anyhow!("Inbound failure: {:?}", error))
}
RequestResponseEvent::OutboundFailure { error, .. } => {
OutEvent::Failure(anyhow!("Outbound failure: {:?}", error))
}
RequestResponseEvent::ResponseSent { peer, .. } => {
tracing::debug!("successfully sent quote response to {}", peer);
OutEvent::ResponseSent
}
}
}
}
/// A `NetworkBehaviour` that represents negotiate a swap using Swap
/// request/response.
#[derive(NetworkBehaviour)]
#[behaviour(out_event = "OutEvent", event_process = false)]
#[allow(missing_debug_implementations)]
pub struct Behaviour {
rr: RequestResponse<CborCodec<Swap, QuoteRequest, QuoteResponse>>,
}
impl Behaviour {
/// Alice always sends her messages as a response to a request from Bob.
pub fn send(
&mut self,
channel: ResponseChannel<QuoteResponse>,
msg: QuoteResponse,
) -> Result<()> {
self.rr
.send_response(channel, msg)
.map_err(|_| anyhow!("failed to send quote response"))?;
Ok(())
}
}
impl Default for Behaviour {
fn default() -> Self {
let timeout = Duration::from_secs(TIMEOUT);
let mut config = RequestResponseConfig::default();
config.set_request_timeout(timeout);
Self {
rr: RequestResponse::new(
CborCodec::default(),
vec![(Swap, ProtocolSupport::Inbound)],
config,
),
}
}
}

View File

@ -1,15 +1,22 @@
//! Run an XMR/BTC swap in the role of Bob.
//! Bob holds BTC and wishes receive XMR.
use crate::{ use crate::{
bitcoin, bitcoin,
database::Database, database::Database,
execution_params::ExecutionParams, execution_params::ExecutionParams,
monero, monero,
network::peer_tracker::{self, PeerTracker}, network::{
protocol::{alice, alice::TransferProof, bob}, peer_tracker::{self, PeerTracker},
spot_price,
spot_price::{SpotPriceRequest, SpotPriceResponse},
},
protocol::{alice::TransferProof, bob},
};
use anyhow::{anyhow, Error, Result};
pub use execution_setup::{Message0, Message2, Message4};
use libp2p::{
core::Multiaddr,
request_response::{RequestResponseMessage, ResponseChannel},
NetworkBehaviour, PeerId,
}; };
use anyhow::{Error, Result};
use libp2p::{core::Multiaddr, NetworkBehaviour, PeerId};
use std::sync::Arc; use std::sync::Arc;
use tracing::debug; use tracing::debug;
use uuid::Uuid; use uuid::Uuid;
@ -18,19 +25,15 @@ pub use self::{
cancel::cancel, cancel::cancel,
encrypted_signature::EncryptedSignature, encrypted_signature::EncryptedSignature,
event_loop::{EventLoop, EventLoopHandle}, event_loop::{EventLoop, EventLoopHandle},
quote_request::*,
refund::refund, refund::refund,
state::*, state::*,
swap::{run, run_until}, swap::{run, run_until},
}; };
pub use execution_setup::{Message0, Message2, Message4};
use libp2p::request_response::ResponseChannel;
pub mod cancel; pub mod cancel;
mod encrypted_signature; mod encrypted_signature;
pub mod event_loop; pub mod event_loop;
mod execution_setup; mod execution_setup;
mod quote_request;
pub mod refund; pub mod refund;
pub mod state; pub mod state;
pub mod swap; pub mod swap;
@ -113,7 +116,7 @@ impl Builder {
#[derive(Debug)] #[derive(Debug)]
pub enum OutEvent { pub enum OutEvent {
ConnectionEstablished(PeerId), ConnectionEstablished(PeerId),
QuoteResponse(alice::QuoteResponse), SpotPriceReceived(SpotPriceResponse),
ExecutionSetupDone(Result<Box<State2>>), ExecutionSetupDone(Result<Box<State2>>),
TransferProof { TransferProof {
msg: Box<TransferProof>, msg: Box<TransferProof>,
@ -134,12 +137,34 @@ impl From<peer_tracker::OutEvent> for OutEvent {
} }
} }
impl From<quote_request::OutEvent> for OutEvent { impl From<spot_price::OutEvent> for OutEvent {
fn from(event: quote_request::OutEvent) -> Self { fn from(event: spot_price::OutEvent) -> Self {
use quote_request::OutEvent::*;
match event { match event {
MsgReceived(quote_response) => OutEvent::QuoteResponse(quote_response), spot_price::OutEvent::Message {
Failure(err) => OutEvent::CommunicationError(err.context("Failure with Quote Request")), message: RequestResponseMessage::Response { response, .. },
..
} => OutEvent::SpotPriceReceived(response),
spot_price::OutEvent::Message {
message: RequestResponseMessage::Request { .. },
..
} => OutEvent::CommunicationError(anyhow!(
"Bob is only meant to receive spot prices, not hand them out"
)),
spot_price::OutEvent::ResponseSent { .. } => OutEvent::ResponseSent,
spot_price::OutEvent::InboundFailure { peer, error, .. } => {
OutEvent::CommunicationError(anyhow!(
"spot_price protocol with peer {} failed due to {:?}",
peer,
error
))
}
spot_price::OutEvent::OutboundFailure { peer, error, .. } => {
OutEvent::CommunicationError(anyhow!(
"spot_price protocol with peer {} failed due to {:?}",
peer,
error
))
}
} }
} }
} }
@ -181,21 +206,32 @@ impl From<encrypted_signature::OutEvent> for OutEvent {
} }
/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Bob. /// A `NetworkBehaviour` that represents an XMR/BTC swap node as Bob.
#[derive(NetworkBehaviour, Default)] #[derive(NetworkBehaviour)]
#[behaviour(out_event = "OutEvent", event_process = false)] #[behaviour(out_event = "OutEvent", event_process = false)]
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Behaviour { pub struct Behaviour {
pt: PeerTracker, pt: PeerTracker,
quote_request: quote_request::Behaviour, spot_price: spot_price::Behaviour,
execution_setup: execution_setup::Behaviour, execution_setup: execution_setup::Behaviour,
transfer_proof: transfer_proof::Behaviour, transfer_proof: transfer_proof::Behaviour,
encrypted_signature: encrypted_signature::Behaviour, encrypted_signature: encrypted_signature::Behaviour,
} }
impl Default for Behaviour {
fn default() -> Self {
Self {
pt: Default::default(),
spot_price: spot_price::bob(),
execution_setup: Default::default(),
transfer_proof: Default::default(),
encrypted_signature: Default::default(),
}
}
}
impl Behaviour { impl Behaviour {
/// Sends a quote request to Alice to retrieve the rate. pub fn request_spot_price(&mut self, alice: PeerId, request: SpotPriceRequest) {
pub fn send_quote_request(&mut self, alice: PeerId, quote_request: QuoteRequest) { let _ = self.spot_price.send_request(&alice, request);
let _ = self.quote_request.send(alice, quote_request);
} }
pub fn start_execution_setup( pub fn start_execution_setup(

View File

@ -1,10 +1,14 @@
use crate::{ use crate::{
bitcoin, bitcoin,
bitcoin::EncryptedSignature, bitcoin::EncryptedSignature,
network::{transport, TokioExecutor}, monero,
network::{
spot_price::{SpotPriceRequest, SpotPriceResponse},
transport, TokioExecutor,
},
protocol::{ protocol::{
alice::{QuoteResponse, TransferProof}, alice::TransferProof,
bob::{Behaviour, OutEvent, QuoteRequest, State0, State2}, bob::{Behaviour, OutEvent, State0, State2},
}, },
}; };
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
@ -35,24 +39,17 @@ impl<T> Default for Channels<T> {
#[derive(Debug)] #[derive(Debug)]
pub struct EventLoopHandle { pub struct EventLoopHandle {
recv_quote_response: Receiver<QuoteResponse>, recv_spot_price: Receiver<SpotPriceResponse>,
start_execution_setup: Sender<State0>, start_execution_setup: Sender<State0>,
done_execution_setup: Receiver<Result<State2>>, done_execution_setup: Receiver<Result<State2>>,
recv_transfer_proof: Receiver<TransferProof>, recv_transfer_proof: Receiver<TransferProof>,
conn_established: Receiver<PeerId>, conn_established: Receiver<PeerId>,
dial_alice: Sender<()>, dial_alice: Sender<()>,
send_quote_request: Sender<QuoteRequest>, request_spot_price: Sender<SpotPriceRequest>,
send_encrypted_signature: Sender<EncryptedSignature>, send_encrypted_signature: Sender<EncryptedSignature>,
} }
impl EventLoopHandle { impl EventLoopHandle {
pub async fn recv_quote_response(&mut self) -> Result<QuoteResponse> {
self.recv_quote_response
.recv()
.await
.ok_or_else(|| anyhow!("Failed to receive quote response from Alice"))
}
pub async fn execution_setup(&mut self, state0: State0) -> Result<State2> { pub async fn execution_setup(&mut self, state0: State0) -> Result<State2> {
let _ = self.start_execution_setup.send(state0).await?; let _ = self.start_execution_setup.send(state0).await?;
@ -82,9 +79,19 @@ impl EventLoopHandle {
Ok(()) Ok(())
} }
pub async fn send_quote_request(&mut self, quote_request: QuoteRequest) -> Result<()> { pub async fn request_spot_price(&mut self, btc: bitcoin::Amount) -> Result<monero::Amount> {
let _ = self.send_quote_request.send(quote_request).await?; let _ = self
Ok(()) .request_spot_price
.send(SpotPriceRequest { btc })
.await?;
let response = self
.recv_spot_price
.recv()
.await
.ok_or_else(|| anyhow!("Failed to receive spot price from Alice"))?;
Ok(response.xmr)
} }
pub async fn send_encrypted_signature( pub async fn send_encrypted_signature(
@ -102,13 +109,13 @@ pub struct EventLoop {
swarm: libp2p::Swarm<Behaviour>, swarm: libp2p::Swarm<Behaviour>,
bitcoin_wallet: Arc<bitcoin::Wallet>, bitcoin_wallet: Arc<bitcoin::Wallet>,
alice_peer_id: PeerId, alice_peer_id: PeerId,
recv_quote_response: Sender<QuoteResponse>, request_spot_price: Receiver<SpotPriceRequest>,
recv_spot_price: Sender<SpotPriceResponse>,
start_execution_setup: Receiver<State0>, start_execution_setup: Receiver<State0>,
done_execution_setup: Sender<Result<State2>>, done_execution_setup: Sender<Result<State2>>,
recv_transfer_proof: Sender<TransferProof>, recv_transfer_proof: Sender<TransferProof>,
dial_alice: Receiver<()>, dial_alice: Receiver<()>,
conn_established: Sender<PeerId>, conn_established: Sender<PeerId>,
send_quote_request: Receiver<QuoteRequest>,
send_encrypted_signature: Receiver<EncryptedSignature>, send_encrypted_signature: Receiver<EncryptedSignature>,
} }
@ -147,24 +154,24 @@ impl EventLoop {
swarm, swarm,
alice_peer_id, alice_peer_id,
bitcoin_wallet, bitcoin_wallet,
recv_quote_response: quote_response.sender, recv_spot_price: quote_response.sender,
start_execution_setup: start_execution_setup.receiver, start_execution_setup: start_execution_setup.receiver,
done_execution_setup: done_execution_setup.sender, done_execution_setup: done_execution_setup.sender,
recv_transfer_proof: recv_transfer_proof.sender, recv_transfer_proof: recv_transfer_proof.sender,
conn_established: conn_established.sender, conn_established: conn_established.sender,
dial_alice: dial_alice.receiver, dial_alice: dial_alice.receiver,
send_quote_request: send_quote_request.receiver, request_spot_price: send_quote_request.receiver,
send_encrypted_signature: send_encrypted_signature.receiver, send_encrypted_signature: send_encrypted_signature.receiver,
}; };
let handle = EventLoopHandle { let handle = EventLoopHandle {
recv_quote_response: quote_response.receiver, recv_spot_price: quote_response.receiver,
start_execution_setup: start_execution_setup.sender, start_execution_setup: start_execution_setup.sender,
done_execution_setup: done_execution_setup.receiver, done_execution_setup: done_execution_setup.receiver,
recv_transfer_proof: recv_transfer_proof.receiver, recv_transfer_proof: recv_transfer_proof.receiver,
conn_established: conn_established.receiver, conn_established: conn_established.receiver,
dial_alice: dial_alice.sender, dial_alice: dial_alice.sender,
send_quote_request: send_quote_request.sender, request_spot_price: send_quote_request.sender,
send_encrypted_signature: send_encrypted_signature.sender, send_encrypted_signature: send_encrypted_signature.sender,
}; };
@ -179,8 +186,8 @@ impl EventLoop {
OutEvent::ConnectionEstablished(peer_id) => { OutEvent::ConnectionEstablished(peer_id) => {
let _ = self.conn_established.send(peer_id).await; let _ = self.conn_established.send(peer_id).await;
} }
OutEvent::QuoteResponse(msg) => { OutEvent::SpotPriceReceived(msg) => {
let _ = self.recv_quote_response.send(msg).await; let _ = self.recv_spot_price.send(msg).await;
}, },
OutEvent::ExecutionSetupDone(res) => { OutEvent::ExecutionSetupDone(res) => {
let _ = self.done_execution_setup.send(res.map(|state|*state)).await; let _ = self.done_execution_setup.send(res.map(|state|*state)).await;
@ -213,9 +220,9 @@ impl EventLoop {
} }
} }
}, },
quote_request = self.send_quote_request.recv().fuse() => { quote_request = self.request_spot_price.recv().fuse() => {
if let Some(quote_request) = quote_request { if let Some(quote_request) = quote_request {
self.swarm.send_quote_request(self.alice_peer_id, quote_request); self.swarm.request_spot_price(self.alice_peer_id, quote_request);
} }
}, },
option = self.start_execution_setup.recv().fuse() => { option = self.start_execution_setup.recv().fuse() => {

View File

@ -1,86 +0,0 @@
use crate::{
network::request_response::{CborCodec, Swap, TIMEOUT},
protocol::alice::QuoteResponse,
};
use anyhow::{anyhow, Error, Result};
use libp2p::{
request_response::{
ProtocolSupport, RequestId, RequestResponse, RequestResponseConfig, RequestResponseEvent,
RequestResponseMessage,
},
NetworkBehaviour, PeerId,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use tracing::debug;
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct QuoteRequest {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
pub btc_amount: bitcoin::Amount,
}
#[derive(Debug)]
pub enum OutEvent {
MsgReceived(QuoteResponse),
Failure(Error),
}
/// A `NetworkBehaviour` that represents doing the negotiation of a swap.
#[derive(NetworkBehaviour)]
#[behaviour(out_event = "OutEvent", event_process = false)]
#[allow(missing_debug_implementations)]
pub struct Behaviour {
rr: RequestResponse<CborCodec<Swap, QuoteRequest, QuoteResponse>>,
}
impl Behaviour {
pub fn send(&mut self, alice: PeerId, quote_request: QuoteRequest) -> Result<RequestId> {
debug!("Requesting quote for {}", quote_request.btc_amount);
let id = self.rr.send_request(&alice, quote_request);
Ok(id)
}
}
impl Default for Behaviour {
fn default() -> Self {
let timeout = Duration::from_secs(TIMEOUT);
let mut config = RequestResponseConfig::default();
config.set_request_timeout(timeout);
Self {
rr: RequestResponse::new(
CborCodec::default(),
vec![(Swap, ProtocolSupport::Outbound)],
config,
),
}
}
}
impl From<RequestResponseEvent<QuoteRequest, QuoteResponse>> for OutEvent {
fn from(event: RequestResponseEvent<QuoteRequest, QuoteResponse>) -> Self {
match event {
RequestResponseEvent::Message {
message: RequestResponseMessage::Request { .. },
..
} => OutEvent::Failure(anyhow!("Bob should never get a request from Alice")),
RequestResponseEvent::Message {
message: RequestResponseMessage::Response { response, .. },
..
} => OutEvent::MsgReceived(response),
RequestResponseEvent::InboundFailure { error, .. } => {
OutEvent::Failure(anyhow!("Inbound failure: {:?}", error))
}
RequestResponseEvent::OutboundFailure { error, .. } => {
OutEvent::Failure(anyhow!("Outbound failure: {:?}", error))
}
RequestResponseEvent::ResponseSent { .. } => {
OutEvent::Failure(anyhow!("Bob does not send a quote response to Alice"))
}
}
}
}

View File

@ -5,7 +5,7 @@ use crate::{
execution_params::ExecutionParams, execution_params::ExecutionParams,
monero, monero,
monero::InsufficientFunds, monero::InsufficientFunds,
protocol::bob::{self, event_loop::EventLoopHandle, state::*, QuoteRequest}, protocol::bob::{self, event_loop::EventLoopHandle, state::*},
}; };
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use async_recursion::async_recursion; use async_recursion::async_recursion;
@ -70,7 +70,7 @@ async fn run_until_internal(
event_loop_handle.dial().await?; event_loop_handle.dial().await?;
let state2 = request_quote_and_setup( let state2 = request_price_and_setup(
btc_amount, btc_amount,
&mut event_loop_handle, &mut event_loop_handle,
execution_params, execution_params,
@ -378,24 +378,20 @@ async fn run_until_internal(
} }
} }
pub async fn request_quote_and_setup( pub async fn request_price_and_setup(
btc_amount: bitcoin::Amount, btc: bitcoin::Amount,
event_loop_handle: &mut EventLoopHandle, event_loop_handle: &mut EventLoopHandle,
execution_params: ExecutionParams, execution_params: ExecutionParams,
bitcoin_refund_address: bitcoin::Address, bitcoin_refund_address: bitcoin::Address,
) -> Result<bob::state::State2> { ) -> Result<bob::state::State2> {
event_loop_handle let xmr = event_loop_handle.request_spot_price(btc).await?;
.send_quote_request(QuoteRequest { btc_amount })
.await?;
let xmr_amount = event_loop_handle.recv_quote_response().await?.xmr_amount; tracing::info!("Spot price for {} is {}", btc, xmr);
tracing::info!("Quote for {} is {}", btc_amount, xmr_amount);
let state0 = State0::new( let state0 = State0::new(
&mut OsRng, &mut OsRng,
btc_amount, btc,
xmr_amount, xmr,
execution_params.bitcoin_cancel_timelock, execution_params.bitcoin_cancel_timelock,
execution_params.bitcoin_punish_timelock, execution_params.bitcoin_punish_timelock,
bitcoin_refund_address, bitcoin_refund_address,