mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
Merge #265
265: Replace quote with spot-price protocol r=thomaseizinger a=thomaseizinger 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. Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
This commit is contained in:
commit
d1363d130c
@ -1,5 +1,6 @@
|
||||
pub mod peer_tracker;
|
||||
pub mod request_response;
|
||||
pub mod spot_price;
|
||||
pub mod transport;
|
||||
|
||||
use futures::prelude::*;
|
||||
|
@ -13,21 +13,12 @@ pub const TIMEOUT: u64 = 3600; // One hour.
|
||||
/// Message receive buffer.
|
||||
pub const BUF_SIZE: usize = 1024 * 1024;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Swap;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct TransferProofProtocol;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct EncryptedSignatureProtocol;
|
||||
|
||||
impl ProtocolName for Swap {
|
||||
fn protocol_name(&self) -> &[u8] {
|
||||
b"/comit/xmr/btc/swap/1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolName for TransferProofProtocol {
|
||||
fn protocol_name(&self) -> &[u8] {
|
||||
b"/comit/xmr/btc/transfer_proof/1.0.0"
|
||||
|
66
swap/src/network/spot_price.rs
Normal file
66
swap/src/network/spot_price.rs
Normal 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(),
|
||||
)
|
||||
}
|
@ -8,7 +8,6 @@ pub use self::{
|
||||
behaviour::{Behaviour, OutEvent},
|
||||
event_loop::{EventLoop, EventLoopHandle},
|
||||
execution_setup::Message1,
|
||||
quote_response::*,
|
||||
state::*,
|
||||
swap::{run, run_until},
|
||||
transfer_proof::TransferProof,
|
||||
@ -19,7 +18,6 @@ mod behaviour;
|
||||
mod encrypted_signature;
|
||||
pub mod event_loop;
|
||||
mod execution_setup;
|
||||
mod quote_response;
|
||||
pub mod state;
|
||||
mod steps;
|
||||
pub mod swap;
|
||||
|
@ -1,24 +1,35 @@
|
||||
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::{
|
||||
alice::{
|
||||
encrypted_signature, execution_setup, quote_response, transfer_proof, QuoteResponse,
|
||||
State0, State3, TransferProof,
|
||||
encrypted_signature, execution_setup, transfer_proof, State0, State3, TransferProof,
|
||||
},
|
||||
bob::{EncryptedSignature, QuoteRequest},
|
||||
bob::EncryptedSignature,
|
||||
},
|
||||
};
|
||||
use anyhow::{Error, Result};
|
||||
use libp2p::{request_response::ResponseChannel, NetworkBehaviour, PeerId};
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use libp2p::{
|
||||
request_response::{RequestResponseMessage, ResponseChannel},
|
||||
NetworkBehaviour, PeerId,
|
||||
};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use tracing::debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OutEvent {
|
||||
ConnectionEstablished(PeerId),
|
||||
QuoteRequest {
|
||||
msg: QuoteRequest,
|
||||
channel: ResponseChannel<QuoteResponse>,
|
||||
bob_peer_id: PeerId,
|
||||
SpotPriceRequested {
|
||||
msg: SpotPriceRequest,
|
||||
channel: ResponseChannel<SpotPriceResponse>,
|
||||
peer: PeerId,
|
||||
},
|
||||
ExecutionSetupDone {
|
||||
bob_peer_id: PeerId,
|
||||
@ -43,21 +54,37 @@ impl From<peer_tracker::OutEvent> for OutEvent {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<quote_response::OutEvent> for OutEvent {
|
||||
fn from(event: quote_response::OutEvent) -> Self {
|
||||
use crate::protocol::alice::quote_response::OutEvent::*;
|
||||
impl From<spot_price::OutEvent> for OutEvent {
|
||||
fn from(event: spot_price::OutEvent) -> Self {
|
||||
match event {
|
||||
MsgReceived {
|
||||
msg,
|
||||
channel,
|
||||
bob_peer_id,
|
||||
} => OutEvent::QuoteRequest {
|
||||
msg,
|
||||
channel,
|
||||
bob_peer_id,
|
||||
},
|
||||
ResponseSent => OutEvent::ResponseSent,
|
||||
Failure(err) => OutEvent::Failure(err.context("Quote Request/Response failure")),
|
||||
spot_price::OutEvent::Message {
|
||||
peer,
|
||||
message:
|
||||
RequestResponseMessage::Request {
|
||||
channel,
|
||||
request: msg,
|
||||
..
|
||||
},
|
||||
} => OutEvent::SpotPriceRequested { msg, channel, peer },
|
||||
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.
|
||||
#[derive(NetworkBehaviour, Default)]
|
||||
#[derive(NetworkBehaviour)]
|
||||
#[behaviour(out_event = "OutEvent", event_process = false)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Behaviour {
|
||||
pt: PeerTracker,
|
||||
quote_response: quote_response::Behaviour,
|
||||
spot_price: spot_price::Behaviour,
|
||||
execution_setup: execution_setup::Behaviour,
|
||||
transfer_proof: transfer_proof::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 {
|
||||
pub fn send_quote_response(
|
||||
pub fn send_spot_price(
|
||||
&mut self,
|
||||
channel: ResponseChannel<QuoteResponse>,
|
||||
quote_response: QuoteResponse,
|
||||
channel: ResponseChannel<SpotPriceResponse>,
|
||||
response: SpotPriceResponse,
|
||||
) -> 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(())
|
||||
}
|
||||
|
||||
pub fn start_execution_setup(&mut self, bob_peer_id: PeerId, state0: State0) {
|
||||
self.execution_setup.run(bob_peer_id, state0);
|
||||
pub async fn start_execution_setup(
|
||||
&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.
|
||||
|
@ -5,25 +5,21 @@ use crate::{
|
||||
execution_params::ExecutionParams,
|
||||
monero,
|
||||
monero::BalanceTooLow,
|
||||
network::{transport, TokioExecutor},
|
||||
network::{spot_price::SpotPriceResponse, transport, TokioExecutor},
|
||||
protocol::{
|
||||
alice,
|
||||
alice::{
|
||||
AliceState, Behaviour, OutEvent, QuoteResponse, State0, State3, Swap, TransferProof,
|
||||
},
|
||||
bob::{EncryptedSignature, QuoteRequest},
|
||||
alice::{AliceState, Behaviour, OutEvent, State3, Swap, TransferProof},
|
||||
bob::EncryptedSignature,
|
||||
},
|
||||
seed::Seed,
|
||||
};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use futures::future::RemoteHandle;
|
||||
use libp2p::{
|
||||
core::Multiaddr, futures::FutureExt, request_response::ResponseChannel, PeerId, Swarm,
|
||||
};
|
||||
use libp2p::{core::Multiaddr, futures::FutureExt, PeerId, Swarm};
|
||||
use rand::rngs::OsRng;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{broadcast, mpsc, mpsc::error::SendError};
|
||||
use tracing::{debug, error, info, trace};
|
||||
use tracing::{debug, error, trace};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
@ -166,9 +162,30 @@ where
|
||||
OutEvent::ConnectionEstablished(alice) => {
|
||||
debug!("Connection Established with {}", alice);
|
||||
}
|
||||
OutEvent::QuoteRequest { msg, channel, bob_peer_id } => {
|
||||
if let Err(error) = self.handle_quote_request(msg, channel, bob_peer_id, self.monero_wallet.clone()).await {
|
||||
error!("Failed to handle quote request: {:#}", error);
|
||||
OutEvent::SpotPriceRequested { msg, channel, peer } => {
|
||||
let btc = msg.btc;
|
||||
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} => {
|
||||
@ -199,65 +216,34 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_quote_request(
|
||||
async fn handle_spot_price_request(
|
||||
&mut self,
|
||||
quote_request: QuoteRequest,
|
||||
channel: ResponseChannel<QuoteResponse>,
|
||||
bob_peer_id: PeerId,
|
||||
btc: bitcoin::Amount,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
) -> Result<()> {
|
||||
// 1. Check if acceptable request
|
||||
// 2. Send response
|
||||
|
||||
) -> Result<monero::Amount> {
|
||||
let rate = self
|
||||
.rate_service
|
||||
.latest_rate()
|
||||
.context("Failed to get latest rate")?;
|
||||
|
||||
let btc_amount = quote_request.btc_amount;
|
||||
|
||||
if btc_amount > self.max_buy {
|
||||
if btc > self.max_buy {
|
||||
bail!(MaximumBuyAmountExceeded {
|
||||
actual: btc_amount,
|
||||
actual: btc,
|
||||
max: self.max_buy
|
||||
})
|
||||
}
|
||||
|
||||
let xmr_balance = monero_wallet.get_balance().await?;
|
||||
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 {
|
||||
balance: xmr_balance
|
||||
})
|
||||
}
|
||||
|
||||
let quote_response = QuoteResponse { xmr_amount };
|
||||
|
||||
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(())
|
||||
Ok(xmr)
|
||||
}
|
||||
|
||||
async fn handle_execution_setup_done(
|
||||
|
@ -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,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,22 @@
|
||||
//! Run an XMR/BTC swap in the role of Bob.
|
||||
//! Bob holds BTC and wishes receive XMR.
|
||||
use crate::{
|
||||
bitcoin,
|
||||
database::Database,
|
||||
execution_params::ExecutionParams,
|
||||
monero,
|
||||
network::peer_tracker::{self, PeerTracker},
|
||||
protocol::{alice, alice::TransferProof, bob},
|
||||
network::{
|
||||
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 tracing::debug;
|
||||
use uuid::Uuid;
|
||||
@ -18,19 +25,15 @@ pub use self::{
|
||||
cancel::cancel,
|
||||
encrypted_signature::EncryptedSignature,
|
||||
event_loop::{EventLoop, EventLoopHandle},
|
||||
quote_request::*,
|
||||
refund::refund,
|
||||
state::*,
|
||||
swap::{run, run_until},
|
||||
};
|
||||
pub use execution_setup::{Message0, Message2, Message4};
|
||||
use libp2p::request_response::ResponseChannel;
|
||||
|
||||
pub mod cancel;
|
||||
mod encrypted_signature;
|
||||
pub mod event_loop;
|
||||
mod execution_setup;
|
||||
mod quote_request;
|
||||
pub mod refund;
|
||||
pub mod state;
|
||||
pub mod swap;
|
||||
@ -119,7 +122,7 @@ impl Builder {
|
||||
#[derive(Debug)]
|
||||
pub enum OutEvent {
|
||||
ConnectionEstablished(PeerId),
|
||||
QuoteResponse(alice::QuoteResponse),
|
||||
SpotPriceReceived(SpotPriceResponse),
|
||||
ExecutionSetupDone(Result<Box<State2>>),
|
||||
TransferProof {
|
||||
msg: Box<TransferProof>,
|
||||
@ -140,12 +143,34 @@ impl From<peer_tracker::OutEvent> for OutEvent {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<quote_request::OutEvent> for OutEvent {
|
||||
fn from(event: quote_request::OutEvent) -> Self {
|
||||
use quote_request::OutEvent::*;
|
||||
impl From<spot_price::OutEvent> for OutEvent {
|
||||
fn from(event: spot_price::OutEvent) -> Self {
|
||||
match event {
|
||||
MsgReceived(quote_response) => OutEvent::QuoteResponse(quote_response),
|
||||
Failure(err) => OutEvent::CommunicationError(err.context("Failure with Quote Request")),
|
||||
spot_price::OutEvent::Message {
|
||||
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
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -187,21 +212,32 @@ impl From<encrypted_signature::OutEvent> for OutEvent {
|
||||
}
|
||||
|
||||
/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Bob.
|
||||
#[derive(NetworkBehaviour, Default)]
|
||||
#[derive(NetworkBehaviour)]
|
||||
#[behaviour(out_event = "OutEvent", event_process = false)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Behaviour {
|
||||
pt: PeerTracker,
|
||||
quote_request: quote_request::Behaviour,
|
||||
spot_price: spot_price::Behaviour,
|
||||
execution_setup: execution_setup::Behaviour,
|
||||
transfer_proof: transfer_proof::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 {
|
||||
/// Sends a quote request to Alice to retrieve the rate.
|
||||
pub fn send_quote_request(&mut self, alice: PeerId, quote_request: QuoteRequest) {
|
||||
let _ = self.quote_request.send(alice, quote_request);
|
||||
pub fn request_spot_price(&mut self, alice: PeerId, request: SpotPriceRequest) {
|
||||
let _ = self.spot_price.send_request(&alice, request);
|
||||
}
|
||||
|
||||
pub fn start_execution_setup(
|
||||
|
@ -1,10 +1,14 @@
|
||||
use crate::{
|
||||
bitcoin,
|
||||
bitcoin::EncryptedSignature,
|
||||
network::{transport, TokioExecutor},
|
||||
monero,
|
||||
network::{
|
||||
spot_price::{SpotPriceRequest, SpotPriceResponse},
|
||||
transport, TokioExecutor,
|
||||
},
|
||||
protocol::{
|
||||
alice::{QuoteResponse, TransferProof},
|
||||
bob::{Behaviour, OutEvent, QuoteRequest, State0, State2},
|
||||
alice::TransferProof,
|
||||
bob::{Behaviour, OutEvent, State0, State2},
|
||||
},
|
||||
};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
@ -35,24 +39,17 @@ impl<T> Default for Channels<T> {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoopHandle {
|
||||
recv_quote_response: Receiver<QuoteResponse>,
|
||||
recv_spot_price: Receiver<SpotPriceResponse>,
|
||||
start_execution_setup: Sender<State0>,
|
||||
done_execution_setup: Receiver<Result<State2>>,
|
||||
recv_transfer_proof: Receiver<TransferProof>,
|
||||
conn_established: Receiver<PeerId>,
|
||||
dial_alice: Sender<()>,
|
||||
send_quote_request: Sender<QuoteRequest>,
|
||||
request_spot_price: Sender<SpotPriceRequest>,
|
||||
send_encrypted_signature: Sender<EncryptedSignature>,
|
||||
}
|
||||
|
||||
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> {
|
||||
let _ = self.start_execution_setup.send(state0).await?;
|
||||
|
||||
@ -82,9 +79,19 @@ impl EventLoopHandle {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_quote_request(&mut self, quote_request: QuoteRequest) -> Result<()> {
|
||||
let _ = self.send_quote_request.send(quote_request).await?;
|
||||
Ok(())
|
||||
pub async fn request_spot_price(&mut self, btc: bitcoin::Amount) -> Result<monero::Amount> {
|
||||
let _ = self
|
||||
.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(
|
||||
@ -102,13 +109,13 @@ pub struct EventLoop {
|
||||
swarm: libp2p::Swarm<Behaviour>,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
alice_peer_id: PeerId,
|
||||
recv_quote_response: Sender<QuoteResponse>,
|
||||
request_spot_price: Receiver<SpotPriceRequest>,
|
||||
recv_spot_price: Sender<SpotPriceResponse>,
|
||||
start_execution_setup: Receiver<State0>,
|
||||
done_execution_setup: Sender<Result<State2>>,
|
||||
recv_transfer_proof: Sender<TransferProof>,
|
||||
dial_alice: Receiver<()>,
|
||||
conn_established: Sender<PeerId>,
|
||||
send_quote_request: Receiver<QuoteRequest>,
|
||||
send_encrypted_signature: Receiver<EncryptedSignature>,
|
||||
}
|
||||
|
||||
@ -147,24 +154,24 @@ impl EventLoop {
|
||||
swarm,
|
||||
alice_peer_id,
|
||||
bitcoin_wallet,
|
||||
recv_quote_response: quote_response.sender,
|
||||
recv_spot_price: quote_response.sender,
|
||||
start_execution_setup: start_execution_setup.receiver,
|
||||
done_execution_setup: done_execution_setup.sender,
|
||||
recv_transfer_proof: recv_transfer_proof.sender,
|
||||
conn_established: conn_established.sender,
|
||||
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,
|
||||
};
|
||||
|
||||
let handle = EventLoopHandle {
|
||||
recv_quote_response: quote_response.receiver,
|
||||
recv_spot_price: quote_response.receiver,
|
||||
start_execution_setup: start_execution_setup.sender,
|
||||
done_execution_setup: done_execution_setup.receiver,
|
||||
recv_transfer_proof: recv_transfer_proof.receiver,
|
||||
conn_established: conn_established.receiver,
|
||||
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,
|
||||
};
|
||||
|
||||
@ -179,8 +186,8 @@ impl EventLoop {
|
||||
OutEvent::ConnectionEstablished(peer_id) => {
|
||||
let _ = self.conn_established.send(peer_id).await;
|
||||
}
|
||||
OutEvent::QuoteResponse(msg) => {
|
||||
let _ = self.recv_quote_response.send(msg).await;
|
||||
OutEvent::SpotPriceReceived(msg) => {
|
||||
let _ = self.recv_spot_price.send(msg).await;
|
||||
},
|
||||
OutEvent::ExecutionSetupDone(res) => {
|
||||
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 {
|
||||
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() => {
|
||||
|
@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ use crate::{
|
||||
execution_params::ExecutionParams,
|
||||
monero,
|
||||
monero::InsufficientFunds,
|
||||
protocol::bob::{self, event_loop::EventLoopHandle, state::*, QuoteRequest},
|
||||
protocol::bob::{self, event_loop::EventLoopHandle, state::*},
|
||||
};
|
||||
use anyhow::{bail, Result};
|
||||
use async_recursion::async_recursion;
|
||||
@ -72,7 +72,7 @@ async fn run_until_internal(
|
||||
|
||||
event_loop_handle.dial().await?;
|
||||
|
||||
let state2 = request_quote_and_setup(
|
||||
let state2 = request_price_and_setup(
|
||||
btc_amount,
|
||||
&mut event_loop_handle,
|
||||
execution_params,
|
||||
@ -394,24 +394,20 @@ async fn run_until_internal(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn request_quote_and_setup(
|
||||
btc_amount: bitcoin::Amount,
|
||||
pub async fn request_price_and_setup(
|
||||
btc: bitcoin::Amount,
|
||||
event_loop_handle: &mut EventLoopHandle,
|
||||
execution_params: ExecutionParams,
|
||||
bitcoin_refund_address: bitcoin::Address,
|
||||
) -> Result<bob::state::State2> {
|
||||
event_loop_handle
|
||||
.send_quote_request(QuoteRequest { btc_amount })
|
||||
.await?;
|
||||
let xmr = event_loop_handle.request_spot_price(btc).await?;
|
||||
|
||||
let xmr_amount = event_loop_handle.recv_quote_response().await?.xmr_amount;
|
||||
|
||||
tracing::info!("Quote for {} is {}", btc_amount, xmr_amount);
|
||||
tracing::info!("Spot price for {} is {}", btc, xmr);
|
||||
|
||||
let state0 = State0::new(
|
||||
&mut OsRng,
|
||||
btc_amount,
|
||||
xmr_amount,
|
||||
btc,
|
||||
xmr,
|
||||
execution_params.bitcoin_cancel_timelock,
|
||||
execution_params.bitcoin_punish_timelock,
|
||||
bitcoin_refund_address,
|
||||
|
Loading…
Reference in New Issue
Block a user