Alice's spot_price Behaviour reports back Error

Instead of handling all errors on the inside spot_price errors are bubbled up (as `SwapRequestDeclined`).
This allows us to test both Alice's and Bob's behaviour for all scenarios.
This commit is contained in:
Daniel Karzel 2021-05-06 10:50:29 +10:00
parent 5aac76598d
commit 03a0dc73cd
No known key found for this signature in database
GPG Key ID: 30C3FC2E438ADB6E
3 changed files with 70 additions and 57 deletions

View File

@ -6,11 +6,14 @@ use crate::protocol::alice::{execution_setup, spot_price, State3};
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use libp2p::request_response::{RequestId, ResponseChannel}; use libp2p::request_response::{RequestId, ResponseChannel};
use libp2p::{NetworkBehaviour, PeerId}; use libp2p::{NetworkBehaviour, PeerId};
use std::fmt::Debug;
use uuid::Uuid; use uuid::Uuid;
#[derive(Debug)] #[derive(Debug)]
pub enum OutEvent { pub enum OutEvent {
SwapRequestDeclined {
peer: PeerId,
error: spot_price::Error,
},
ExecutionSetupStart { ExecutionSetupStart {
peer: PeerId, peer: PeerId,
btc: bitcoin::Amount, btc: bitcoin::Amount,
@ -65,7 +68,7 @@ impl OutEvent {
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Behaviour<LR> pub struct Behaviour<LR>
where where
LR: LatestRate + Send + 'static + Debug, LR: LatestRate + Send + 'static,
{ {
pub quote: quote::Behaviour, pub quote: quote::Behaviour,
pub spot_price: spot_price::Behaviour<LR>, pub spot_price: spot_price::Behaviour<LR>,
@ -76,7 +79,7 @@ where
impl<LR> Behaviour<LR> impl<LR> Behaviour<LR>
where where
LR: LatestRate + Send + 'static + Debug, LR: LatestRate + Send + 'static,
{ {
pub fn new( pub fn new(
balance: monero::Amount, balance: monero::Amount,

View File

@ -3,6 +3,7 @@ use crate::database::Database;
use crate::env::Config; use crate::env::Config;
use crate::network::quote::BidQuote; use crate::network::quote::BidQuote;
use crate::network::transfer_proof; use crate::network::transfer_proof;
use crate::protocol::alice::spot_price::Error;
use crate::protocol::alice::{AliceState, Behaviour, OutEvent, State0, State3, Swap}; use crate::protocol::alice::{AliceState, Behaviour, OutEvent, State0, State3, Swap};
use crate::{bitcoin, kraken, monero}; use crate::{bitcoin, kraken, monero};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
@ -203,6 +204,18 @@ where
self.swarm.behaviour_mut().execution_setup.run(peer, state0); self.swarm.behaviour_mut().execution_setup.run(peer, state0);
} }
SwarmEvent::Behaviour(OutEvent::SwapRequestDeclined { peer, error }) => {
match error {
Error::ResumeOnlyMode | Error::MaxBuyAmountExceeded { .. } => {
tracing::warn!(%peer, "Ignoring spot price request because: {}", error);
}
Error::BalanceTooLow { .. }
| Error::LatestRateFetchFailed(_)
| Error::SellQuoteCalculationFailed(_) => {
tracing::error!(%peer, "Ignoring spot price request because: {}", error);
}
}
}
SwarmEvent::Behaviour(OutEvent::QuoteRequested { channel, peer }) => { SwarmEvent::Behaviour(OutEvent::QuoteRequested { channel, peer }) => {
// TODO: Move the spot-price update into dedicated update stream to decouple it from quote requests // TODO: Move the spot-price update into dedicated update stream to decouple it from quote requests
let current_balance = self.monero_wallet.get_balance().await; let current_balance = self.monero_wallet.get_balance().await;

View File

@ -14,10 +14,16 @@ use std::collections::VecDeque;
use std::fmt::Debug; use std::fmt::Debug;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
pub struct OutEvent { pub enum OutEvent {
peer: PeerId, ExecutionSetupParams {
btc: bitcoin::Amount, peer: PeerId,
xmr: monero::Amount, btc: bitcoin::Amount,
xmr: monero::Amount,
},
Error {
peer: PeerId,
error: Error,
},
} }
#[derive(NetworkBehaviour)] #[derive(NetworkBehaviour)]
@ -25,7 +31,7 @@ pub struct OutEvent {
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Behaviour<LR> pub struct Behaviour<LR>
where where
LR: LatestRate + Send + 'static + Debug, LR: LatestRate + Send + 'static,
{ {
behaviour: spot_price::Behaviour, behaviour: spot_price::Behaviour,
@ -50,7 +56,7 @@ where
/// bubbled up to the parent behaviour. /// bubbled up to the parent behaviour.
impl<LR> Behaviour<LR> impl<LR> Behaviour<LR>
where where
LR: LatestRate + Send + 'static + Debug, LR: LatestRate + Send + 'static,
{ {
pub fn new( pub fn new(
balance: monero::Amount, balance: monero::Amount,
@ -78,30 +84,24 @@ where
self.balance = balance; self.balance = balance;
} }
fn send_error_response( fn decline(
&mut self, &mut self,
peer: PeerId, peer: PeerId,
channel: ResponseChannel<spot_price::Response>, channel: ResponseChannel<spot_price::Response>,
error: Error<LR>, error: Error,
) { ) {
match error {
Error::ResumeOnlyMode | Error::MaxBuyAmountExceeded { .. } => {
tracing::warn!(%peer, "Ignoring spot price request because: {}", error);
}
Error::BalanceTooLow { .. }
| Error::LatestRateFetchFailed(_)
| Error::SellQuoteCalculationFailed(_) => {
tracing::error!(%peer, "Ignoring spot price request because: {}", error);
}
}
if self if self
.behaviour .behaviour
.send_response(channel, spot_price::Response::Error(error.into())) .send_response(
channel,
spot_price::Response::Error(error.to_error_response()),
)
.is_err() .is_err()
{ {
tracing::debug!(%peer, "Unable to send error response for spot price request"); tracing::debug!(%peer, "Unable to send error response for spot price request");
} }
self.events.push_back(OutEvent::Error { peer, error });
} }
fn poll<BIE>( fn poll<BIE>(
@ -120,7 +120,7 @@ where
impl<LR> NetworkBehaviourEventProcess<spot_price::OutEvent> for Behaviour<LR> impl<LR> NetworkBehaviourEventProcess<spot_price::OutEvent> for Behaviour<LR>
where where
LR: LatestRate + Send + 'static + Debug, LR: LatestRate + Send + 'static,
{ {
fn inject_event(&mut self, event: spot_price::OutEvent) { fn inject_event(&mut self, event: spot_price::OutEvent) {
let (peer, message) = match event { let (peer, message) = match event {
@ -150,13 +150,13 @@ where
}; };
if self.resume_only { if self.resume_only {
self.send_error_response(peer, channel, Error::ResumeOnlyMode); self.decline(peer, channel, Error::ResumeOnlyMode);
return; return;
} }
let btc = request.btc; let btc = request.btc;
if btc > self.max_buy { if btc > self.max_buy {
self.send_error_response(peer, channel, Error::MaxBuyAmountExceeded { self.decline(peer, channel, Error::MaxBuyAmountExceeded {
max: self.max_buy, max: self.max_buy,
buy: btc, buy: btc,
}); });
@ -166,14 +166,14 @@ where
let rate = match self.latest_rate.latest_rate() { let rate = match self.latest_rate.latest_rate() {
Ok(rate) => rate, Ok(rate) => rate,
Err(e) => { Err(e) => {
self.send_error_response(peer, channel, Error::LatestRateFetchFailed(e)); self.decline(peer, channel, Error::LatestRateFetchFailed(Box::new(e)));
return; return;
} }
}; };
let xmr = match rate.sell_quote(btc) { let xmr = match rate.sell_quote(btc) {
Ok(xmr) => xmr, Ok(xmr) => xmr,
Err(e) => { Err(e) => {
self.send_error_response(peer, channel, Error::SellQuoteCalculationFailed(e)); self.decline(peer, channel, Error::SellQuoteCalculationFailed(e));
return; return;
} }
}; };
@ -182,7 +182,7 @@ where
let xmr_lock_fees = self.lock_fee; let xmr_lock_fees = self.lock_fee;
if xmr_balance < xmr + xmr_lock_fees { if xmr_balance < xmr + xmr_lock_fees {
self.send_error_response(peer, channel, Error::BalanceTooLow { buy: btc }); self.decline(peer, channel, Error::BalanceTooLow { buy: btc });
return; return;
} }
@ -194,43 +194,24 @@ where
tracing::error!(%peer, "Failed to send spot price response of {} for {}", xmr, btc) tracing::error!(%peer, "Failed to send spot price response of {} for {}", xmr, btc)
} }
self.events.push_back(OutEvent { peer, btc, xmr }); self.events
.push_back(OutEvent::ExecutionSetupParams { peer, btc, xmr });
} }
} }
impl From<OutEvent> for alice::OutEvent { impl From<OutEvent> for alice::OutEvent {
fn from(event: OutEvent) -> Self { fn from(event: OutEvent) -> Self {
Self::ExecutionSetupStart { match event {
peer: event.peer, OutEvent::ExecutionSetupParams { peer, btc, xmr } => {
btc: event.btc, Self::ExecutionSetupStart { peer, btc, xmr }
xmr: event.xmr,
}
}
}
impl<LR> From<Error<LR>> for spot_price::Error
where
LR: LatestRate + Debug,
{
fn from(error: Error<LR>) -> Self {
match error {
Error::ResumeOnlyMode => spot_price::Error::NoSwapsAccepted,
Error::MaxBuyAmountExceeded { max, buy } => {
spot_price::Error::MaxBuyAmountExceeded { max, buy }
}
Error::BalanceTooLow { buy } => spot_price::Error::BalanceTooLow { buy },
Error::LatestRateFetchFailed(_) | Error::SellQuoteCalculationFailed(_) => {
spot_price::Error::Other
} }
OutEvent::Error { peer, error } => Self::SwapRequestDeclined { peer, error },
} }
} }
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error<LR> pub enum Error {
where
LR: LatestRate + Debug,
{
#[error("ASB is running in resume-only mode")] #[error("ASB is running in resume-only mode")]
ResumeOnlyMode, ResumeOnlyMode,
#[error("Maximum buy {max} exceeded {buy}")] #[error("Maximum buy {max} exceeded {buy}")]
@ -242,8 +223,24 @@ where
BalanceTooLow { buy: bitcoin::Amount }, BalanceTooLow { buy: bitcoin::Amount },
#[error("Failed to fetch latest rate")] #[error("Failed to fetch latest rate")]
LatestRateFetchFailed(LR::Error), LatestRateFetchFailed(#[source] Box<dyn std::error::Error + Send + 'static>),
#[error("Failed to calculate quote: {0}")] #[error("Failed to calculate quote: {0}")]
SellQuoteCalculationFailed(anyhow::Error), SellQuoteCalculationFailed(#[source] anyhow::Error),
}
impl Error {
pub fn to_error_response(&self) -> spot_price::Error {
match self {
Error::ResumeOnlyMode => spot_price::Error::NoSwapsAccepted,
Error::MaxBuyAmountExceeded { max, buy } => spot_price::Error::MaxBuyAmountExceeded {
max: *max,
buy: *buy,
},
Error::BalanceTooLow { buy } => spot_price::Error::BalanceTooLow { buy: *buy },
Error::LatestRateFetchFailed(_) | Error::SellQuoteCalculationFailed(_) => {
spot_price::Error::Other
}
}
}
} }