mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
Merge #268
268: Introduce `quote` protocol and display it to the user before they fund r=thomaseizinger a=thomaseizinger Fixes #255. Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
This commit is contained in:
commit
88b3321ec5
@ -15,6 +15,8 @@
|
|||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use prettytable::{row, Table};
|
use prettytable::{row, Table};
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
|
use std::cmp::min;
|
||||||
|
use std::future::Future;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@ -24,6 +26,7 @@ use swap::cli::command::{Arguments, Command};
|
|||||||
use swap::cli::config::{read_config, Config};
|
use swap::cli::config::{read_config, Config};
|
||||||
use swap::database::Database;
|
use swap::database::Database;
|
||||||
use swap::execution_params::GetExecutionParams;
|
use swap::execution_params::GetExecutionParams;
|
||||||
|
use swap::network::quote::BidQuote;
|
||||||
use swap::protocol::bob;
|
use swap::protocol::bob;
|
||||||
use swap::protocol::bob::cancel::CancelError;
|
use swap::protocol::bob::cancel::CancelError;
|
||||||
use swap::protocol::bob::{Builder, EventLoop};
|
use swap::protocol::bob::{Builder, EventLoop};
|
||||||
@ -115,33 +118,7 @@ async fn main() -> Result<()> {
|
|||||||
let monero_wallet =
|
let monero_wallet =
|
||||||
init_monero_wallet(monero_network, monero_wallet_rpc_process.endpoint()).await?;
|
init_monero_wallet(monero_network, monero_wallet_rpc_process.endpoint()).await?;
|
||||||
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
||||||
|
let (event_loop, mut event_loop_handle) = EventLoop::new(
|
||||||
let swap_id = Uuid::new_v4();
|
|
||||||
|
|
||||||
// TODO: Also wait for more funds if balance < dust
|
|
||||||
if bitcoin_wallet.balance().await? == Amount::ZERO {
|
|
||||||
info!(
|
|
||||||
"Please deposit BTC to {}",
|
|
||||||
bitcoin_wallet.new_address().await?
|
|
||||||
);
|
|
||||||
|
|
||||||
while bitcoin_wallet.balance().await? == Amount::ZERO {
|
|
||||||
bitcoin_wallet.sync_wallet().await?;
|
|
||||||
|
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("Received {}", bitcoin_wallet.balance().await?);
|
|
||||||
} else {
|
|
||||||
info!(
|
|
||||||
"Still got {} left in wallet, swapping ...",
|
|
||||||
bitcoin_wallet.balance().await?
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let send_bitcoin = bitcoin_wallet.max_giveable(TxLock::script_size()).await?;
|
|
||||||
|
|
||||||
let (event_loop, event_loop_handle) = EventLoop::new(
|
|
||||||
&seed.derive_libp2p_identity(),
|
&seed.derive_libp2p_identity(),
|
||||||
alice_peer_id,
|
alice_peer_id,
|
||||||
alice_addr,
|
alice_addr,
|
||||||
@ -149,9 +126,26 @@ async fn main() -> Result<()> {
|
|||||||
)?;
|
)?;
|
||||||
let handle = tokio::spawn(event_loop.run());
|
let handle = tokio::spawn(event_loop.run());
|
||||||
|
|
||||||
|
let send_bitcoin = determine_btc_to_swap(
|
||||||
|
event_loop_handle.request_quote(),
|
||||||
|
bitcoin_wallet.balance(),
|
||||||
|
bitcoin_wallet.new_address(),
|
||||||
|
async {
|
||||||
|
while bitcoin_wallet.balance().await? == Amount::ZERO {
|
||||||
|
bitcoin_wallet.sync_wallet().await?;
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitcoin_wallet.balance().await
|
||||||
|
},
|
||||||
|
bitcoin_wallet.max_giveable(TxLock::script_size()),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let swap = Builder::new(
|
let swap = Builder::new(
|
||||||
db,
|
db,
|
||||||
swap_id,
|
Uuid::new_v4(),
|
||||||
bitcoin_wallet.clone(),
|
bitcoin_wallet.clone(),
|
||||||
Arc::new(monero_wallet),
|
Arc::new(monero_wallet),
|
||||||
execution_params,
|
execution_params,
|
||||||
@ -311,3 +305,133 @@ async fn init_monero_wallet(
|
|||||||
|
|
||||||
Ok(monero_wallet)
|
Ok(monero_wallet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn determine_btc_to_swap(
|
||||||
|
request_quote: impl Future<Output = Result<BidQuote>>,
|
||||||
|
initial_balance: impl Future<Output = Result<bitcoin::Amount>>,
|
||||||
|
get_new_address: impl Future<Output = Result<bitcoin::Address>>,
|
||||||
|
wait_for_deposit: impl Future<Output = Result<bitcoin::Amount>>,
|
||||||
|
max_giveable: impl Future<Output = Result<bitcoin::Amount>>,
|
||||||
|
) -> Result<bitcoin::Amount> {
|
||||||
|
debug!("Requesting quote");
|
||||||
|
|
||||||
|
let bid_quote = request_quote.await.context("failed to request quote")?;
|
||||||
|
|
||||||
|
info!("Received quote: 1 XMR ~ {}", bid_quote.price);
|
||||||
|
|
||||||
|
// TODO: Also wait for more funds if balance < dust
|
||||||
|
let initial_balance = initial_balance.await?;
|
||||||
|
|
||||||
|
if initial_balance == Amount::ZERO {
|
||||||
|
info!(
|
||||||
|
"Please deposit the BTC you want to swap to {} (max {})",
|
||||||
|
get_new_address.await?,
|
||||||
|
bid_quote.max_quantity
|
||||||
|
);
|
||||||
|
|
||||||
|
let new_balance = wait_for_deposit.await?;
|
||||||
|
|
||||||
|
info!("Received {}", new_balance);
|
||||||
|
} else {
|
||||||
|
info!("Found {} in wallet", initial_balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
let max_giveable = max_giveable.await?;
|
||||||
|
let max_accepted = bid_quote.max_quantity;
|
||||||
|
|
||||||
|
if max_giveable > max_accepted {
|
||||||
|
info!(
|
||||||
|
"Max giveable amount {} exceeds max accepted amount {}!",
|
||||||
|
max_giveable, max_accepted
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(min(max_giveable, max_accepted))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::determine_btc_to_swap;
|
||||||
|
use ::bitcoin::Amount;
|
||||||
|
use tracing::subscriber;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_no_balance_and_transfers_less_than_max_swaps_max_giveable() {
|
||||||
|
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
|
||||||
|
|
||||||
|
let amount = determine_btc_to_swap(
|
||||||
|
async { Ok(quote_with_max(0.01)) },
|
||||||
|
async { Ok(Amount::ZERO) },
|
||||||
|
get_dummy_address(),
|
||||||
|
async { Ok(Amount::from_btc(0.0001)?) },
|
||||||
|
async { Ok(Amount::from_btc(0.00009)?) },
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(amount, Amount::from_btc(0.00009).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_no_balance_and_transfers_more_then_swaps_max_quantity_from_quote() {
|
||||||
|
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
|
||||||
|
|
||||||
|
let amount = determine_btc_to_swap(
|
||||||
|
async { Ok(quote_with_max(0.01)) },
|
||||||
|
async { Ok(Amount::ZERO) },
|
||||||
|
get_dummy_address(),
|
||||||
|
async { Ok(Amount::from_btc(0.1)?) },
|
||||||
|
async { Ok(Amount::from_btc(0.09)?) },
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(amount, Amount::from_btc(0.01).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_initial_balance_below_max_quantity_swaps_max_givable() {
|
||||||
|
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
|
||||||
|
|
||||||
|
let amount = determine_btc_to_swap(
|
||||||
|
async { Ok(quote_with_max(0.01)) },
|
||||||
|
async { Ok(Amount::from_btc(0.005)?) },
|
||||||
|
async { panic!("should not request new address when initial balance is > 0") },
|
||||||
|
async { panic!("should not wait for deposit when initial balance > 0") },
|
||||||
|
async { Ok(Amount::from_btc(0.0049)?) },
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(amount, Amount::from_btc(0.0049).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_initial_balance_above_max_quantity_swaps_max_quantity() {
|
||||||
|
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
|
||||||
|
|
||||||
|
let amount = determine_btc_to_swap(
|
||||||
|
async { Ok(quote_with_max(0.01)) },
|
||||||
|
async { Ok(Amount::from_btc(0.1)?) },
|
||||||
|
async { panic!("should not request new address when initial balance is > 0") },
|
||||||
|
async { panic!("should not wait for deposit when initial balance > 0") },
|
||||||
|
async { Ok(Amount::from_btc(0.09)?) },
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(amount, Amount::from_btc(0.01).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quote_with_max(btc: f64) -> BidQuote {
|
||||||
|
BidQuote {
|
||||||
|
price: Amount::from_btc(0.001).unwrap(),
|
||||||
|
max_quantity: Amount::from_btc(btc).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_dummy_address() -> Result<bitcoin::Address> {
|
||||||
|
Ok("1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6".parse()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -23,10 +23,10 @@ pub mod database;
|
|||||||
pub mod execution_params;
|
pub mod execution_params;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod monero;
|
pub mod monero;
|
||||||
|
pub mod network;
|
||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
pub mod seed;
|
pub mod seed;
|
||||||
pub mod trace;
|
pub mod trace;
|
||||||
|
|
||||||
mod monero_ext;
|
mod monero_ext;
|
||||||
mod network;
|
|
||||||
mod serde_peer_id;
|
mod serde_peer_id;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
pub mod peer_tracker;
|
pub mod peer_tracker;
|
||||||
|
pub mod quote;
|
||||||
pub mod request_response;
|
pub mod request_response;
|
||||||
pub mod spot_price;
|
pub mod spot_price;
|
||||||
pub mod transport;
|
pub mod transport;
|
||||||
|
53
swap/src/network/quote.rs
Normal file
53
swap/src/network/quote.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use crate::bitcoin;
|
||||||
|
use crate::network::request_response::CborCodec;
|
||||||
|
use libp2p::core::ProtocolName;
|
||||||
|
use libp2p::request_response::{
|
||||||
|
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub type OutEvent = RequestResponseEvent<(), BidQuote>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct BidQuoteProtocol;
|
||||||
|
|
||||||
|
impl ProtocolName for BidQuoteProtocol {
|
||||||
|
fn protocol_name(&self) -> &[u8] {
|
||||||
|
b"/comit/xmr/btc/bid-quote/1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a quote for buying XMR.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct BidQuote {
|
||||||
|
/// The price at which the maker is willing to buy at.
|
||||||
|
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||||
|
pub price: bitcoin::Amount,
|
||||||
|
/// The maximum quantity the maker is willing to buy.
|
||||||
|
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||||
|
pub max_quantity: bitcoin::Amount,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Behaviour = RequestResponse<CborCodec<BidQuoteProtocol, (), BidQuote>>;
|
||||||
|
|
||||||
|
/// Constructs a new instance of the `quote` behaviour to be used by Alice.
|
||||||
|
///
|
||||||
|
/// Alice only supports inbound connections, i.e. handing out quotes.
|
||||||
|
pub fn alice() -> Behaviour {
|
||||||
|
Behaviour::new(
|
||||||
|
CborCodec::default(),
|
||||||
|
vec![(BidQuoteProtocol, ProtocolSupport::Inbound)],
|
||||||
|
RequestResponseConfig::default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a new instance of the `quote` behaviour to be used by Bob.
|
||||||
|
///
|
||||||
|
/// Bob only supports outbound connections, i.e. requesting quotes.
|
||||||
|
pub fn bob() -> Behaviour {
|
||||||
|
Behaviour::new(
|
||||||
|
CborCodec::default(),
|
||||||
|
vec![(BidQuoteProtocol, ProtocolSupport::Outbound)],
|
||||||
|
RequestResponseConfig::default(),
|
||||||
|
)
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
use crate::execution_params::ExecutionParams;
|
use crate::execution_params::ExecutionParams;
|
||||||
use crate::network::spot_price::{Request, Response};
|
use crate::network::quote::BidQuote;
|
||||||
use crate::network::{peer_tracker, spot_price};
|
use crate::network::{peer_tracker, quote, spot_price};
|
||||||
use crate::protocol::alice::{
|
use crate::protocol::alice::{
|
||||||
encrypted_signature, execution_setup, transfer_proof, State0, State3, TransferProof,
|
encrypted_signature, execution_setup, transfer_proof, State0, State3, TransferProof,
|
||||||
};
|
};
|
||||||
@ -16,8 +16,12 @@ use tracing::debug;
|
|||||||
pub enum OutEvent {
|
pub enum OutEvent {
|
||||||
ConnectionEstablished(PeerId),
|
ConnectionEstablished(PeerId),
|
||||||
SpotPriceRequested {
|
SpotPriceRequested {
|
||||||
msg: Request,
|
msg: spot_price::Request,
|
||||||
channel: ResponseChannel<Response>,
|
channel: ResponseChannel<spot_price::Response>,
|
||||||
|
peer: PeerId,
|
||||||
|
},
|
||||||
|
QuoteRequested {
|
||||||
|
channel: ResponseChannel<BidQuote>,
|
||||||
peer: PeerId,
|
peer: PeerId,
|
||||||
},
|
},
|
||||||
ExecutionSetupDone {
|
ExecutionSetupDone {
|
||||||
@ -78,6 +82,34 @@ impl From<spot_price::OutEvent> for OutEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<quote::OutEvent> for OutEvent {
|
||||||
|
fn from(event: quote::OutEvent) -> Self {
|
||||||
|
match event {
|
||||||
|
quote::OutEvent::Message {
|
||||||
|
peer,
|
||||||
|
message: RequestResponseMessage::Request { channel, .. },
|
||||||
|
} => OutEvent::QuoteRequested { channel, peer },
|
||||||
|
quote::OutEvent::Message {
|
||||||
|
message: RequestResponseMessage::Response { .. },
|
||||||
|
..
|
||||||
|
} => OutEvent::Failure(anyhow!(
|
||||||
|
"Alice is only meant to hand out quotes, not receive them"
|
||||||
|
)),
|
||||||
|
quote::OutEvent::ResponseSent { .. } => OutEvent::ResponseSent,
|
||||||
|
quote::OutEvent::InboundFailure { peer, error, .. } => OutEvent::Failure(anyhow!(
|
||||||
|
"quote protocol with peer {} failed due to {:?}",
|
||||||
|
peer,
|
||||||
|
error
|
||||||
|
)),
|
||||||
|
quote::OutEvent::OutboundFailure { peer, error, .. } => OutEvent::Failure(anyhow!(
|
||||||
|
"quote protocol with peer {} failed due to {:?}",
|
||||||
|
peer,
|
||||||
|
error
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<execution_setup::OutEvent> for OutEvent {
|
impl From<execution_setup::OutEvent> for OutEvent {
|
||||||
fn from(event: execution_setup::OutEvent) -> Self {
|
fn from(event: execution_setup::OutEvent) -> Self {
|
||||||
use crate::protocol::alice::execution_setup::OutEvent::*;
|
use crate::protocol::alice::execution_setup::OutEvent::*;
|
||||||
@ -124,6 +156,7 @@ impl From<encrypted_signature::OutEvent> for OutEvent {
|
|||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Behaviour {
|
pub struct Behaviour {
|
||||||
pt: peer_tracker::Behaviour,
|
pt: peer_tracker::Behaviour,
|
||||||
|
quote: quote::Behaviour,
|
||||||
spot_price: spot_price::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,
|
||||||
@ -134,6 +167,7 @@ impl Default for Behaviour {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
pt: Default::default(),
|
pt: Default::default(),
|
||||||
|
quote: quote::alice(),
|
||||||
spot_price: spot_price::alice(),
|
spot_price: spot_price::alice(),
|
||||||
execution_setup: Default::default(),
|
execution_setup: Default::default(),
|
||||||
transfer_proof: Default::default(),
|
transfer_proof: Default::default(),
|
||||||
@ -143,10 +177,22 @@ impl Default for Behaviour {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Behaviour {
|
impl Behaviour {
|
||||||
|
pub fn send_quote(
|
||||||
|
&mut self,
|
||||||
|
channel: ResponseChannel<BidQuote>,
|
||||||
|
response: BidQuote,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.quote
|
||||||
|
.send_response(channel, response)
|
||||||
|
.map_err(|_| anyhow!("failed to respond with quote"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn send_spot_price(
|
pub fn send_spot_price(
|
||||||
&mut self,
|
&mut self,
|
||||||
channel: ResponseChannel<Response>,
|
channel: ResponseChannel<spot_price::Response>,
|
||||||
response: Response,
|
response: spot_price::Response,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.spot_price
|
self.spot_price
|
||||||
.send_response(channel, response)
|
.send_response(channel, response)
|
||||||
|
@ -2,8 +2,8 @@ use crate::asb::LatestRate;
|
|||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
use crate::execution_params::ExecutionParams;
|
use crate::execution_params::ExecutionParams;
|
||||||
use crate::monero::BalanceTooLow;
|
use crate::monero::BalanceTooLow;
|
||||||
use crate::network::spot_price::Response;
|
use crate::network::quote::BidQuote;
|
||||||
use crate::network::{transport, TokioExecutor};
|
use crate::network::{spot_price, transport, TokioExecutor};
|
||||||
use crate::protocol::alice;
|
use crate::protocol::alice;
|
||||||
use crate::protocol::alice::{AliceState, Behaviour, OutEvent, State3, Swap, TransferProof};
|
use crate::protocol::alice::{AliceState, Behaviour, OutEvent, State3, Swap, TransferProof};
|
||||||
use crate::protocol::bob::EncryptedSignature;
|
use crate::protocol::bob::EncryptedSignature;
|
||||||
@ -171,7 +171,7 @@ where
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.swarm.send_spot_price(channel, Response { xmr }) {
|
match self.swarm.send_spot_price(channel, spot_price::Response { xmr }) {
|
||||||
Ok(_) => {},
|
Ok(_) => {},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// if we can't respond, the peer probably just disconnected so it is not a huge deal, only log this on debug
|
// if we can't respond, the peer probably just disconnected so it is not a huge deal, only log this on debug
|
||||||
@ -187,6 +187,24 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
OutEvent::QuoteRequested { channel, peer } => {
|
||||||
|
let quote = match self.make_quote(self.max_buy).await {
|
||||||
|
Ok(quote) => quote,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(%peer, "failed to make quote: {:#}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.swarm.send_quote(channel, quote) {
|
||||||
|
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 quote: {:#}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
OutEvent::ExecutionSetupDone{bob_peer_id, state3} => {
|
OutEvent::ExecutionSetupDone{bob_peer_id, state3} => {
|
||||||
let _ = self.handle_execution_setup_done(bob_peer_id, *state3).await;
|
let _ = self.handle_execution_setup_done(bob_peer_id, *state3).await;
|
||||||
}
|
}
|
||||||
@ -245,6 +263,18 @@ where
|
|||||||
Ok(xmr)
|
Ok(xmr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn make_quote(&mut self, max_buy: bitcoin::Amount) -> Result<BidQuote> {
|
||||||
|
let rate = self
|
||||||
|
.rate_service
|
||||||
|
.latest_rate()
|
||||||
|
.context("Failed to get latest rate")?;
|
||||||
|
|
||||||
|
Ok(BidQuote {
|
||||||
|
price: rate.ask,
|
||||||
|
max_quantity: max_buy,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_execution_setup_done(
|
async fn handle_execution_setup_done(
|
||||||
&mut self,
|
&mut self,
|
||||||
bob_peer_id: PeerId,
|
bob_peer_id: PeerId,
|
||||||
|
@ -2,8 +2,10 @@ use crate::database::Database;
|
|||||||
use crate::execution_params::ExecutionParams;
|
use crate::execution_params::ExecutionParams;
|
||||||
use crate::network::{peer_tracker, spot_price};
|
use crate::network::{peer_tracker, spot_price};
|
||||||
use crate::protocol::alice::TransferProof;
|
use crate::protocol::alice::TransferProof;
|
||||||
|
use crate::protocol::bob;
|
||||||
use crate::{bitcoin, monero};
|
use crate::{bitcoin, monero};
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{anyhow, Error, Result};
|
||||||
|
pub use execution_setup::{Message0, Message2, Message4};
|
||||||
use libp2p::core::Multiaddr;
|
use libp2p::core::Multiaddr;
|
||||||
use libp2p::request_response::{RequestResponseMessage, ResponseChannel};
|
use libp2p::request_response::{RequestResponseMessage, ResponseChannel};
|
||||||
use libp2p::{NetworkBehaviour, PeerId};
|
use libp2p::{NetworkBehaviour, PeerId};
|
||||||
@ -14,10 +16,11 @@ use uuid::Uuid;
|
|||||||
pub use self::cancel::cancel;
|
pub use self::cancel::cancel;
|
||||||
pub use self::encrypted_signature::EncryptedSignature;
|
pub use self::encrypted_signature::EncryptedSignature;
|
||||||
pub use self::event_loop::{EventLoop, EventLoopHandle};
|
pub use self::event_loop::{EventLoop, EventLoopHandle};
|
||||||
pub use self::execution_setup::{Message0, Message2, Message4};
|
|
||||||
pub use self::refund::refund;
|
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};
|
||||||
|
use crate::network::quote;
|
||||||
|
use crate::network::quote::BidQuote;
|
||||||
|
|
||||||
pub mod cancel;
|
pub mod cancel;
|
||||||
mod encrypted_signature;
|
mod encrypted_signature;
|
||||||
@ -30,7 +33,7 @@ mod transfer_proof;
|
|||||||
|
|
||||||
pub struct Swap {
|
pub struct Swap {
|
||||||
pub state: BobState,
|
pub state: BobState,
|
||||||
pub event_loop_handle: EventLoopHandle,
|
pub event_loop_handle: bob::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>,
|
||||||
@ -89,7 +92,7 @@ impl Builder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> Result<Swap> {
|
pub fn build(self) -> Result<bob::Swap> {
|
||||||
let state = match self.init_params {
|
let state = match self.init_params {
|
||||||
InitParams::New { btc_amount } => BobState::Started { btc_amount },
|
InitParams::New { btc_amount } => BobState::Started { btc_amount },
|
||||||
InitParams::None => self.db.get_state(self.swap_id)?.try_into_bob()?.into(),
|
InitParams::None => self.db.get_state(self.swap_id)?.try_into_bob()?.into(),
|
||||||
@ -111,6 +114,7 @@ impl Builder {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum OutEvent {
|
pub enum OutEvent {
|
||||||
ConnectionEstablished(PeerId),
|
ConnectionEstablished(PeerId),
|
||||||
|
QuoteReceived(BidQuote),
|
||||||
SpotPriceReceived(spot_price::Response),
|
SpotPriceReceived(spot_price::Response),
|
||||||
ExecutionSetupDone(Result<Box<State2>>),
|
ExecutionSetupDone(Result<Box<State2>>),
|
||||||
TransferProof {
|
TransferProof {
|
||||||
@ -164,6 +168,38 @@ impl From<spot_price::OutEvent> for OutEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<quote::OutEvent> for OutEvent {
|
||||||
|
fn from(event: quote::OutEvent) -> Self {
|
||||||
|
match event {
|
||||||
|
quote::OutEvent::Message {
|
||||||
|
message: RequestResponseMessage::Response { response, .. },
|
||||||
|
..
|
||||||
|
} => OutEvent::QuoteReceived(response),
|
||||||
|
quote::OutEvent::Message {
|
||||||
|
message: RequestResponseMessage::Request { .. },
|
||||||
|
..
|
||||||
|
} => OutEvent::CommunicationError(anyhow!(
|
||||||
|
"Bob is only meant to receive quotes, not hand them out"
|
||||||
|
)),
|
||||||
|
quote::OutEvent::ResponseSent { .. } => OutEvent::ResponseSent,
|
||||||
|
quote::OutEvent::InboundFailure { peer, error, .. } => {
|
||||||
|
OutEvent::CommunicationError(anyhow!(
|
||||||
|
"quote protocol with peer {} failed due to {:?}",
|
||||||
|
peer,
|
||||||
|
error
|
||||||
|
))
|
||||||
|
}
|
||||||
|
quote::OutEvent::OutboundFailure { peer, error, .. } => {
|
||||||
|
OutEvent::CommunicationError(anyhow!(
|
||||||
|
"quote protocol with peer {} failed due to {:?}",
|
||||||
|
peer,
|
||||||
|
error
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<execution_setup::OutEvent> for OutEvent {
|
impl From<execution_setup::OutEvent> for OutEvent {
|
||||||
fn from(event: execution_setup::OutEvent) -> Self {
|
fn from(event: execution_setup::OutEvent) -> Self {
|
||||||
match event {
|
match event {
|
||||||
@ -206,6 +242,7 @@ impl From<encrypted_signature::OutEvent> for OutEvent {
|
|||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Behaviour {
|
pub struct Behaviour {
|
||||||
pt: peer_tracker::Behaviour,
|
pt: peer_tracker::Behaviour,
|
||||||
|
quote: quote::Behaviour,
|
||||||
spot_price: spot_price::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,
|
||||||
@ -216,6 +253,7 @@ impl Default for Behaviour {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
pt: Default::default(),
|
pt: Default::default(),
|
||||||
|
quote: quote::bob(),
|
||||||
spot_price: spot_price::bob(),
|
spot_price: spot_price::bob(),
|
||||||
execution_setup: Default::default(),
|
execution_setup: Default::default(),
|
||||||
transfer_proof: Default::default(),
|
transfer_proof: Default::default(),
|
||||||
@ -225,6 +263,10 @@ impl Default for Behaviour {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Behaviour {
|
impl Behaviour {
|
||||||
|
pub fn request_quote(&mut self, alice: PeerId) {
|
||||||
|
let _ = self.quote.send_request(&alice, ());
|
||||||
|
}
|
||||||
|
|
||||||
pub fn request_spot_price(&mut self, alice: PeerId, request: spot_price::Request) {
|
pub fn request_spot_price(&mut self, alice: PeerId, request: spot_price::Request) {
|
||||||
let _ = self.spot_price.send_request(&alice, request);
|
let _ = self.spot_price.send_request(&alice, request);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::bitcoin::EncryptedSignature;
|
use crate::bitcoin::EncryptedSignature;
|
||||||
use crate::network::spot_price::{Request, Response};
|
use crate::network::quote::BidQuote;
|
||||||
use crate::network::{transport, TokioExecutor};
|
use crate::network::{spot_price, transport, TokioExecutor};
|
||||||
use crate::protocol::alice::TransferProof;
|
use crate::protocol::alice::TransferProof;
|
||||||
use crate::protocol::bob::{Behaviour, OutEvent, State0, State2};
|
use crate::protocol::bob::{Behaviour, OutEvent, State0, State2};
|
||||||
use crate::{bitcoin, monero};
|
use crate::{bitcoin, monero};
|
||||||
@ -34,14 +34,16 @@ impl<T> Default for Channels<T> {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EventLoopHandle {
|
pub struct EventLoopHandle {
|
||||||
recv_spot_price: Receiver<Response>,
|
|
||||||
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<()>,
|
||||||
request_spot_price: Sender<Request>,
|
|
||||||
send_encrypted_signature: Sender<EncryptedSignature>,
|
send_encrypted_signature: Sender<EncryptedSignature>,
|
||||||
|
request_spot_price: Sender<spot_price::Request>,
|
||||||
|
recv_spot_price: Receiver<spot_price::Response>,
|
||||||
|
request_quote: Sender<()>,
|
||||||
|
recv_quote: Receiver<BidQuote>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventLoopHandle {
|
impl EventLoopHandle {
|
||||||
@ -75,7 +77,10 @@ impl EventLoopHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn request_spot_price(&mut self, btc: bitcoin::Amount) -> Result<monero::Amount> {
|
pub async fn request_spot_price(&mut self, btc: bitcoin::Amount) -> Result<monero::Amount> {
|
||||||
let _ = self.request_spot_price.send(Request { btc }).await?;
|
let _ = self
|
||||||
|
.request_spot_price
|
||||||
|
.send(spot_price::Request { btc })
|
||||||
|
.await?;
|
||||||
|
|
||||||
let response = self
|
let response = self
|
||||||
.recv_spot_price
|
.recv_spot_price
|
||||||
@ -86,6 +91,18 @@ impl EventLoopHandle {
|
|||||||
Ok(response.xmr)
|
Ok(response.xmr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn request_quote(&mut self) -> Result<BidQuote> {
|
||||||
|
let _ = self.request_quote.send(()).await?;
|
||||||
|
|
||||||
|
let quote = self
|
||||||
|
.recv_quote
|
||||||
|
.recv()
|
||||||
|
.await
|
||||||
|
.ok_or_else(|| anyhow!("Failed to receive quote from Alice"))?;
|
||||||
|
|
||||||
|
Ok(quote)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn send_encrypted_signature(
|
pub async fn send_encrypted_signature(
|
||||||
&mut self,
|
&mut self,
|
||||||
tx_redeem_encsig: EncryptedSignature,
|
tx_redeem_encsig: EncryptedSignature,
|
||||||
@ -101,14 +118,16 @@ 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,
|
||||||
request_spot_price: Receiver<Request>,
|
request_spot_price: Receiver<spot_price::Request>,
|
||||||
recv_spot_price: Sender<Response>,
|
recv_spot_price: Sender<spot_price::Response>,
|
||||||
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_encrypted_signature: Receiver<EncryptedSignature>,
|
send_encrypted_signature: Receiver<EncryptedSignature>,
|
||||||
|
request_quote: Receiver<()>,
|
||||||
|
recv_quote: Sender<BidQuote>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventLoop {
|
impl EventLoop {
|
||||||
@ -133,38 +152,44 @@ impl EventLoop {
|
|||||||
|
|
||||||
swarm.add_address(alice_peer_id, alice_addr);
|
swarm.add_address(alice_peer_id, alice_addr);
|
||||||
|
|
||||||
let quote_response = Channels::new();
|
|
||||||
let start_execution_setup = Channels::new();
|
let start_execution_setup = Channels::new();
|
||||||
let done_execution_setup = Channels::new();
|
let done_execution_setup = Channels::new();
|
||||||
let recv_transfer_proof = Channels::new();
|
let recv_transfer_proof = Channels::new();
|
||||||
let dial_alice = Channels::new();
|
let dial_alice = Channels::new();
|
||||||
let conn_established = Channels::new();
|
let conn_established = Channels::new();
|
||||||
let send_quote_request = Channels::new();
|
|
||||||
let send_encrypted_signature = Channels::new();
|
let send_encrypted_signature = Channels::new();
|
||||||
|
let request_spot_price = Channels::new();
|
||||||
|
let recv_spot_price = Channels::new();
|
||||||
|
let request_quote = Channels::new();
|
||||||
|
let recv_quote = Channels::new();
|
||||||
|
|
||||||
let event_loop = EventLoop {
|
let event_loop = EventLoop {
|
||||||
swarm,
|
swarm,
|
||||||
alice_peer_id,
|
alice_peer_id,
|
||||||
bitcoin_wallet,
|
bitcoin_wallet,
|
||||||
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,
|
||||||
request_spot_price: send_quote_request.receiver,
|
|
||||||
send_encrypted_signature: send_encrypted_signature.receiver,
|
send_encrypted_signature: send_encrypted_signature.receiver,
|
||||||
|
request_spot_price: request_spot_price.receiver,
|
||||||
|
recv_spot_price: recv_spot_price.sender,
|
||||||
|
request_quote: request_quote.receiver,
|
||||||
|
recv_quote: recv_quote.sender,
|
||||||
};
|
};
|
||||||
|
|
||||||
let handle = EventLoopHandle {
|
let handle = EventLoopHandle {
|
||||||
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,
|
||||||
request_spot_price: send_quote_request.sender,
|
|
||||||
send_encrypted_signature: send_encrypted_signature.sender,
|
send_encrypted_signature: send_encrypted_signature.sender,
|
||||||
|
request_spot_price: request_spot_price.sender,
|
||||||
|
recv_spot_price: recv_spot_price.receiver,
|
||||||
|
request_quote: request_quote.sender,
|
||||||
|
recv_quote: recv_quote.receiver,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((event_loop, handle))
|
Ok((event_loop, handle))
|
||||||
@ -181,6 +206,9 @@ impl EventLoop {
|
|||||||
OutEvent::SpotPriceReceived(msg) => {
|
OutEvent::SpotPriceReceived(msg) => {
|
||||||
let _ = self.recv_spot_price.send(msg).await;
|
let _ = self.recv_spot_price.send(msg).await;
|
||||||
},
|
},
|
||||||
|
OutEvent::QuoteReceived(msg) => {
|
||||||
|
let _ = self.recv_quote.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;
|
||||||
}
|
}
|
||||||
@ -212,9 +240,14 @@ impl EventLoop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
quote_request = self.request_spot_price.recv().fuse() => {
|
spot_price_request = self.request_spot_price.recv().fuse() => {
|
||||||
if let Some(quote_request) = quote_request {
|
if let Some(request) = spot_price_request {
|
||||||
self.swarm.request_spot_price(self.alice_peer_id, quote_request);
|
self.swarm.request_spot_price(self.alice_peer_id, request);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
quote_request = self.request_quote.recv().fuse() => {
|
||||||
|
if quote_request.is_some() {
|
||||||
|
self.swarm.request_quote(self.alice_peer_id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
option = self.start_execution_setup.recv().fuse() => {
|
option = self.start_execution_setup.recv().fuse() => {
|
||||||
|
Loading…
Reference in New Issue
Block a user