Thomas Eizinger
3 years ago
5 changed files with 380 additions and 133 deletions
@ -0,0 +1,194 @@ |
|||||
|
use crate::address_map::Stopping; |
||||
|
use crate::model::cfd::{ |
||||
|
Cfd, CollaborativeSettlement, OrderId, SettlementKind, SettlementProposal, |
||||
|
}; |
||||
|
use crate::model::Price; |
||||
|
use crate::{connection, projection, wire}; |
||||
|
use anyhow::{Context, Result}; |
||||
|
use async_trait::async_trait; |
||||
|
use xtra::prelude::MessageChannel; |
||||
|
use xtra_productivity::xtra_productivity; |
||||
|
|
||||
|
pub struct Actor { |
||||
|
cfd: Cfd, |
||||
|
projection: xtra::Address<projection::Actor>, |
||||
|
on_completed: Box<dyn MessageChannel<Completed>>, |
||||
|
connection: xtra::Address<connection::Actor>, |
||||
|
proposal: SettlementProposal, |
||||
|
} |
||||
|
|
||||
|
impl Actor { |
||||
|
pub fn new( |
||||
|
cfd: Cfd, |
||||
|
projection: xtra::Address<projection::Actor>, |
||||
|
on_completed: impl MessageChannel<Completed> + 'static, |
||||
|
current_price: Price, |
||||
|
connection: xtra::Address<connection::Actor>, |
||||
|
n_payouts: usize, |
||||
|
) -> Result<Self> { |
||||
|
let proposal = cfd.calculate_settlement(current_price, n_payouts)?; |
||||
|
|
||||
|
Ok(Self { |
||||
|
cfd, |
||||
|
projection, |
||||
|
on_completed: Box::new(on_completed), |
||||
|
connection, |
||||
|
proposal, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
async fn propose(&mut self, this: xtra::Address<Self>) -> Result<()> { |
||||
|
if !self.cfd.is_collaborative_settle_possible() { |
||||
|
anyhow::bail!( |
||||
|
"Settlement proposal not possible because for cfd {} is in state {} which cannot be collaboratively settled", |
||||
|
self.cfd.order.id, |
||||
|
self.cfd.state |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
self.connection |
||||
|
.send(connection::ProposeSettlement { |
||||
|
timestamp: self.proposal.timestamp, |
||||
|
taker: self.proposal.taker, |
||||
|
maker: self.proposal.maker, |
||||
|
price: self.proposal.price, |
||||
|
address: this, |
||||
|
order_id: self.cfd.order.id, |
||||
|
}) |
||||
|
.await??; |
||||
|
|
||||
|
self.update_proposal(Some((self.proposal.clone(), SettlementKind::Outgoing))) |
||||
|
.await?; |
||||
|
|
||||
|
Ok(()) |
||||
|
} |
||||
|
|
||||
|
async fn handle_confirmed(&mut self) -> Result<CollaborativeSettlement> { |
||||
|
let order_id = self.cfd.order.id; |
||||
|
|
||||
|
tracing::info!(%order_id, "Settlement proposal got accepted"); |
||||
|
|
||||
|
self.update_proposal(None).await?; |
||||
|
|
||||
|
let dlc = self.cfd.dlc().context("No DLC in CFD")?; |
||||
|
|
||||
|
let (tx, sig) = dlc.close_transaction(&self.proposal)?; |
||||
|
|
||||
|
// Need to use `do_send_async` here because this handler is called in
|
||||
|
// context of a message arriving over the wire, and would result in a
|
||||
|
// deadlock otherwise.
|
||||
|
#[allow(clippy::disallowed_method)] |
||||
|
self.connection |
||||
|
.do_send_async(wire::TakerToMaker::Settlement { |
||||
|
order_id, |
||||
|
msg: wire::taker_to_maker::Settlement::Initiate { sig_taker: sig }, |
||||
|
}) |
||||
|
.await?; |
||||
|
|
||||
|
Ok(CollaborativeSettlement::new( |
||||
|
tx, |
||||
|
dlc.script_pubkey_for(self.cfd.role()), // TODO: Hardcode role to Taker?
|
||||
|
self.proposal.price, |
||||
|
)?) |
||||
|
} |
||||
|
|
||||
|
async fn handle_rejected(&mut self) -> Result<()> { |
||||
|
let order_id = self.cfd.order.id; |
||||
|
|
||||
|
tracing::info!(%order_id, "Settlement proposal got rejected"); |
||||
|
|
||||
|
self.update_proposal(None).await?; |
||||
|
|
||||
|
Ok(()) |
||||
|
} |
||||
|
|
||||
|
async fn update_proposal( |
||||
|
&mut self, |
||||
|
proposal: Option<(SettlementProposal, SettlementKind)>, |
||||
|
) -> Result<()> { |
||||
|
self.projection |
||||
|
.send(projection::UpdateSettlementProposal { |
||||
|
order: self.cfd.order.id, |
||||
|
proposal, |
||||
|
}) |
||||
|
.await?; |
||||
|
|
||||
|
Ok(()) |
||||
|
} |
||||
|
|
||||
|
async fn complete(&mut self, completed: Completed, ctx: &mut xtra::Context<Self>) { |
||||
|
let _ = self.on_completed.send(completed).await; |
||||
|
|
||||
|
ctx.stop(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#[async_trait] |
||||
|
impl xtra::Actor for Actor { |
||||
|
async fn started(&mut self, ctx: &mut xtra::Context<Self>) { |
||||
|
let this = ctx.address().expect("get address to ourselves"); |
||||
|
|
||||
|
if let Err(e) = self.propose(this).await { |
||||
|
self.complete( |
||||
|
Completed::Failed { |
||||
|
order_id: self.cfd.order.id, |
||||
|
error: e, |
||||
|
}, |
||||
|
ctx, |
||||
|
) |
||||
|
.await; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async fn stopping(&mut self, ctx: &mut xtra::Context<Self>) -> xtra::KeepRunning { |
||||
|
// inform the connection actor that we stopping so it can GC the address from the hashmap
|
||||
|
let me = ctx.address().expect("we are still alive"); |
||||
|
let _ = self.connection.send(Stopping { me }).await; |
||||
|
|
||||
|
xtra::KeepRunning::StopAll |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
pub enum Completed { |
||||
|
Confirmed { |
||||
|
order_id: OrderId, |
||||
|
settlement: CollaborativeSettlement, |
||||
|
}, |
||||
|
Rejected { |
||||
|
order_id: OrderId, |
||||
|
}, |
||||
|
Failed { |
||||
|
order_id: OrderId, |
||||
|
error: anyhow::Error, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
#[xtra_productivity] |
||||
|
impl Actor { |
||||
|
async fn handle( |
||||
|
&mut self, |
||||
|
msg: wire::maker_to_taker::Settlement, |
||||
|
ctx: &mut xtra::Context<Self>, |
||||
|
) { |
||||
|
let order_id = self.cfd.order.id; |
||||
|
|
||||
|
let completed = match msg { |
||||
|
wire::maker_to_taker::Settlement::Confirm => match self.handle_confirmed().await { |
||||
|
Ok(settlement) => Completed::Confirmed { |
||||
|
settlement, |
||||
|
order_id, |
||||
|
}, |
||||
|
Err(e) => Completed::Failed { error: e, order_id }, |
||||
|
}, |
||||
|
wire::maker_to_taker::Settlement::Reject => { |
||||
|
if let Err(e) = self.handle_rejected().await { |
||||
|
Completed::Failed { error: e, order_id } |
||||
|
} else { |
||||
|
Completed::Rejected { order_id } |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
self.complete(completed, ctx).await; |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue