Browse Source
796: Introduce short-lived `rollover_taker::Actor` r=luckysori a=luckysori
Very similar to the other ones we've done.
Things to consider:
- Shall we consolidate the usage of "rollover" over "roll over" across the entire codebase? I don't think I have been able to stay consistent even in this PR :(
- I've added an `OrderId` to the `wire::MakerToTaker::ConfirmRollOver` message, because the maker could be talking about more than one rollover.
- The `taker_cfd::Actor` no longer handles the raw `wire::MakerToTaker` enumeration of messages 🎉 After creating all these short-lived actors it only needs to handle a dedicated `CurrentOrder` message originating from the maker.
- Error handling is worth reviewing in detail as I've messed that up in previous related PRs.
Co-authored-by: Lucas Soriano del Pino <l.soriano.del.pino@gmail.com>
feature/force-stop-button
bors[bot]
3 years ago
committed by
GitHub
6 changed files with 455 additions and 260 deletions
@ -0,0 +1,318 @@ |
|||
use crate::address_map::Stopping; |
|||
use crate::connection; |
|||
use crate::model::cfd::{Cfd, Dlc, OrderId, Role, RollOverProposal, SettlementKind}; |
|||
use crate::model::{BitMexPriceEventId, Timestamp}; |
|||
use crate::oracle::{self, GetAnnouncement}; |
|||
use crate::projection::{self, UpdateRollOverProposal}; |
|||
use crate::setup_contract::{self, RolloverParams}; |
|||
use crate::tokio_ext::spawn_fallible; |
|||
use crate::wire::{self, RollOverMsg}; |
|||
use anyhow::{Context, Result}; |
|||
use async_trait::async_trait; |
|||
use futures::channel::mpsc::{self, UnboundedSender}; |
|||
use futures::{future, SinkExt}; |
|||
use maia::secp256k1_zkp::schnorrsig; |
|||
use xtra::prelude::MessageChannel; |
|||
use xtra_productivity::xtra_productivity; |
|||
|
|||
pub struct Actor { |
|||
cfd: Cfd, |
|||
n_payouts: usize, |
|||
oracle_pk: schnorrsig::PublicKey, |
|||
timestamp: Timestamp, |
|||
maker: xtra::Address<connection::Actor>, |
|||
get_announcement: Box<dyn MessageChannel<GetAnnouncement>>, |
|||
projection: xtra::Address<projection::Actor>, |
|||
on_completed: Box<dyn MessageChannel<Completed>>, |
|||
on_stopping: Vec<Box<dyn MessageChannel<Stopping<Self>>>>, |
|||
rollover_msg_sender: Option<UnboundedSender<RollOverMsg>>, |
|||
} |
|||
|
|||
impl Actor { |
|||
pub fn new( |
|||
(cfd, n_payouts): (Cfd, usize), |
|||
oracle_pk: schnorrsig::PublicKey, |
|||
maker: xtra::Address<connection::Actor>, |
|||
get_announcement: &(impl MessageChannel<GetAnnouncement> + 'static), |
|||
projection: xtra::Address<projection::Actor>, |
|||
on_completed: &(impl MessageChannel<Completed> + 'static), |
|||
(on_stopping0, on_stopping1): ( |
|||
&(impl MessageChannel<Stopping<Self>> + 'static), |
|||
&(impl MessageChannel<Stopping<Self>> + 'static), |
|||
), |
|||
) -> Self { |
|||
Self { |
|||
cfd, |
|||
n_payouts, |
|||
oracle_pk, |
|||
timestamp: Timestamp::now(), |
|||
maker, |
|||
get_announcement: get_announcement.clone_channel(), |
|||
projection, |
|||
on_completed: on_completed.clone_channel(), |
|||
on_stopping: vec![on_stopping0.clone_channel(), on_stopping1.clone_channel()], |
|||
rollover_msg_sender: None, |
|||
} |
|||
} |
|||
|
|||
async fn propose(&self, this: xtra::Address<Self>) -> Result<()> { |
|||
self.maker |
|||
.send(connection::ProposeRollOver { |
|||
order_id: self.cfd.order.id, |
|||
timestamp: self.timestamp, |
|||
address: this, |
|||
}) |
|||
.await??; |
|||
|
|||
self.update_proposal(Some(( |
|||
RollOverProposal { |
|||
order_id: self.cfd.order.id, |
|||
timestamp: self.timestamp, |
|||
}, |
|||
SettlementKind::Outgoing, |
|||
))) |
|||
.await?; |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
async fn handle_confirmed( |
|||
&mut self, |
|||
msg: RollOverAccepted, |
|||
ctx: &mut xtra::Context<Self>, |
|||
) -> Result<()> { |
|||
let RollOverAccepted { oracle_event_id } = msg; |
|||
let announcement = self |
|||
.get_announcement |
|||
.send(oracle::GetAnnouncement(oracle_event_id)) |
|||
.await? |
|||
.with_context(|| format!("Announcement {} not found", oracle_event_id))?; |
|||
|
|||
let order_id = self.cfd.order.id; |
|||
tracing::info!(%order_id, "Rollover proposal got accepted"); |
|||
|
|||
self.update_proposal(None).await?; |
|||
|
|||
let (sender, receiver) = mpsc::unbounded::<RollOverMsg>(); |
|||
// store the writing end to forward messages from the maker to
|
|||
// the spawned rollover task
|
|||
self.rollover_msg_sender = Some(sender); |
|||
|
|||
let rollover_fut = setup_contract::roll_over( |
|||
xtra::message_channel::MessageChannel::sink(&self.maker) |
|||
.with(|msg| future::ok(wire::TakerToMaker::RollOverProtocol(msg))), |
|||
receiver, |
|||
(self.oracle_pk, announcement), |
|||
RolloverParams::new( |
|||
self.cfd.order.price, |
|||
self.cfd.quantity_usd, |
|||
self.cfd.order.leverage, |
|||
self.cfd.refund_timelock_in_blocks(), |
|||
), |
|||
Role::Taker, |
|||
self.cfd.dlc().context("No DLC in CFD")?, |
|||
self.n_payouts, |
|||
); |
|||
|
|||
let this = ctx.address().expect("self to be alive"); |
|||
spawn_fallible::<_, anyhow::Error>(async move { |
|||
let _ = match rollover_fut.await { |
|||
Ok(dlc) => this.send(RolloverSucceeded { dlc }).await?, |
|||
Err(error) => this.send(RolloverFailed { error }).await?, |
|||
}; |
|||
|
|||
Ok(()) |
|||
}); |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
async fn handle_rejected(&self) -> Result<()> { |
|||
let order_id = self.cfd.order.id; |
|||
tracing::info!(%order_id, "Rollover proposal got rejected"); |
|||
|
|||
self.update_proposal(None).await?; |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
pub async fn forward_protocol_msg(&mut self, msg: wire::RollOverMsg) -> Result<()> { |
|||
let sender = self |
|||
.rollover_msg_sender |
|||
.as_mut() |
|||
.context("Cannot forward message to rollover task")?; |
|||
sender.send(msg).await?; |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
async fn update_proposal( |
|||
&self, |
|||
proposal: Option<(RollOverProposal, SettlementKind)>, |
|||
) -> Result<()> { |
|||
self.projection |
|||
.send(UpdateRollOverProposal { |
|||
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("self to be alive"); |
|||
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 other actors that we are stopping so that our
|
|||
// address can be GCd from their AddressMaps
|
|||
let me = ctx.address().expect("we are still alive"); |
|||
|
|||
for channel in self.on_stopping.iter() { |
|||
let _ = channel.send(Stopping { me: me.clone() }).await; |
|||
} |
|||
|
|||
xtra::KeepRunning::StopAll |
|||
} |
|||
} |
|||
|
|||
#[xtra_productivity] |
|||
impl Actor { |
|||
pub async fn handle_confirm_rollover( |
|||
&mut self, |
|||
msg: RollOverAccepted, |
|||
ctx: &mut xtra::Context<Self>, |
|||
) { |
|||
if let Err(error) = self.handle_confirmed(msg, ctx).await { |
|||
self.complete( |
|||
Completed::Failed { |
|||
order_id: self.cfd.order.id, |
|||
error, |
|||
}, |
|||
ctx, |
|||
) |
|||
.await; |
|||
} |
|||
} |
|||
|
|||
pub async fn reject_rollover(&mut self, _: RollOverRejected, ctx: &mut xtra::Context<Self>) { |
|||
let order_id = self.cfd.order.id; |
|||
let completed = if let Err(error) = self.handle_rejected().await { |
|||
Completed::Failed { order_id, error } |
|||
} else { |
|||
Completed::Rejected { order_id } |
|||
}; |
|||
|
|||
self.complete(completed, ctx).await; |
|||
} |
|||
|
|||
pub async fn handle_rollover_succeeded( |
|||
&mut self, |
|||
msg: RolloverSucceeded, |
|||
ctx: &mut xtra::Context<Self>, |
|||
) { |
|||
self.complete( |
|||
Completed::UpdatedContract { |
|||
order_id: self.cfd.order.id, |
|||
dlc: msg.dlc, |
|||
}, |
|||
ctx, |
|||
) |
|||
.await; |
|||
} |
|||
|
|||
pub async fn handle_rollover_failed( |
|||
&mut self, |
|||
msg: RolloverFailed, |
|||
ctx: &mut xtra::Context<Self>, |
|||
) { |
|||
self.complete( |
|||
Completed::Failed { |
|||
order_id: self.cfd.order.id, |
|||
error: msg.error, |
|||
}, |
|||
ctx, |
|||
) |
|||
.await; |
|||
} |
|||
|
|||
pub async fn handle_protocol_msg( |
|||
&mut self, |
|||
msg: wire::RollOverMsg, |
|||
ctx: &mut xtra::Context<Self>, |
|||
) { |
|||
if let Err(error) = self.forward_protocol_msg(msg).await { |
|||
self.complete( |
|||
Completed::Failed { |
|||
order_id: self.cfd.order.id, |
|||
error, |
|||
}, |
|||
ctx, |
|||
) |
|||
.await; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// Message sent from the `connection::Actor` to the
|
|||
/// `rollover_taker::Actor` to notify that the maker has accepted the
|
|||
/// rollover proposal.
|
|||
pub struct RollOverAccepted { |
|||
pub oracle_event_id: BitMexPriceEventId, |
|||
} |
|||
|
|||
/// Message sent from the `connection::Actor` to the
|
|||
/// `rollover_taker::Actor` to notify that the maker has rejected the
|
|||
/// rollover proposal.
|
|||
pub struct RollOverRejected; |
|||
|
|||
/// Message sent from the spawned task to `rollover_taker::Actor` to
|
|||
/// notify that rollover has finished successfully.
|
|||
pub struct RolloverSucceeded { |
|||
dlc: Dlc, |
|||
} |
|||
|
|||
/// Message sent from the spawned task to `rollover_taker::Actor` to
|
|||
/// notify that rollover has failed.
|
|||
pub struct RolloverFailed { |
|||
error: anyhow::Error, |
|||
} |
|||
|
|||
#[allow(clippy::large_enum_variant)] |
|||
pub enum Completed { |
|||
UpdatedContract { |
|||
order_id: OrderId, |
|||
dlc: Dlc, |
|||
}, |
|||
Rejected { |
|||
order_id: OrderId, |
|||
}, |
|||
Failed { |
|||
order_id: OrderId, |
|||
error: anyhow::Error, |
|||
}, |
|||
} |
|||
|
|||
impl xtra::Message for Completed { |
|||
type Result = Result<()>; |
|||
} |
Loading…
Reference in new issue