Browse Source

Merge pull request #179 from comit-network/non-collaborative-close

Non collaborative close
upload-correct-windows-binary
Daniel Karzel 3 years ago
committed by GitHub
parent
commit
a9cad7ec3e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 46
      daemon/src/maker_cfd.rs
  2. 245
      daemon/src/model/cfd.rs
  3. 2
      daemon/src/monitor.rs
  4. 11
      daemon/src/routes_maker.rs
  5. 43
      daemon/src/routes_taker.rs
  6. 9
      daemon/src/setup_contract.rs
  7. 2
      daemon/src/taker.rs
  8. 46
      daemon/src/taker_cfd.rs
  9. 23
      daemon/src/to_sse_event.rs
  10. 7
      frontend/src/components/Types.tsx
  11. 10
      frontend/src/components/cfdtables/CfdTable.tsx

46
daemon/src/maker_cfd.rs

@ -5,7 +5,8 @@ use crate::db::{
}; };
use crate::maker_inc_connections::TakerCommand; use crate::maker_inc_connections::TakerCommand;
use crate::model::cfd::{ use crate::model::cfd::{
Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, SettlementProposal, Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Role,
SettlementProposal,
}; };
use crate::model::{TakerId, Usd}; use crate::model::{TakerId, Usd};
use crate::monitor::MonitorParams; use crate::monitor::MonitorParams;
@ -29,6 +30,10 @@ pub struct RejectOrder {
pub order_id: OrderId, pub order_id: OrderId,
} }
pub struct Commit {
pub order_id: OrderId,
}
pub struct NewOrder(pub Order); pub struct NewOrder(pub Order);
pub struct NewTakerOnline { pub struct NewTakerOnline {
@ -93,6 +98,13 @@ impl Actor {
tracing::info!("Refund transaction published on chain: {}", txid); tracing::info!("Refund transaction published on chain: {}", txid);
} }
for cfd in cfds.iter().filter(|cfd| Cfd::is_pending_commit(cfd)) {
let signed_commit_tx = cfd.commit_tx()?;
let txid = wallet.try_broadcast_transaction(signed_commit_tx).await?;
tracing::info!("Commit transaction published on chain: {}", txid);
}
Ok(Self { Ok(Self {
db, db,
wallet, wallet,
@ -347,7 +359,7 @@ impl Actor {
(self.oracle_pk, nonce_pks), (self.oracle_pk, nonce_pks),
cfd, cfd,
self.wallet.clone(), self.wallet.clone(),
setup_contract::Role::Maker, Role::Maker,
); );
let this = ctx let this = ctx
@ -417,6 +429,25 @@ impl Actor {
Ok(()) Ok(())
} }
async fn handle_commit(&mut self, order_id: OrderId) -> Result<()> {
let mut conn = self.db.acquire().await?;
let cfd = load_cfd_by_order_id(order_id, &mut conn).await?;
let signed_commit_tx = cfd.commit_tx()?;
let txid = self
.wallet
.try_broadcast_transaction(signed_commit_tx)
.await?;
tracing::info!("Commit transaction published on chain: {}", txid);
let new_state = cfd.handle(CfdStateChangeEvent::CommitTxSent)?;
insert_new_cfd_state_by_order_id(cfd.order.id, new_state, &mut conn).await?;
Ok(())
}
async fn handle_monitoring_event(&mut self, event: monitor::Event) -> Result<()> { async fn handle_monitoring_event(&mut self, event: monitor::Event) -> Result<()> {
let order_id = event.order_id(); let order_id = event.order_id();
@ -479,6 +510,13 @@ impl Handler<RejectOrder> for Actor {
} }
} }
#[async_trait]
impl Handler<Commit> for Actor {
async fn handle(&mut self, msg: Commit, _ctx: &mut Context<Self>) {
log_error!(self.handle_commit(msg.order_id))
}
}
#[async_trait] #[async_trait]
impl Handler<NewOrder> for Actor { impl Handler<NewOrder> for Actor {
async fn handle(&mut self, msg: NewOrder, _ctx: &mut Context<Self>) { async fn handle(&mut self, msg: NewOrder, _ctx: &mut Context<Self>) {
@ -586,6 +624,10 @@ impl Message for RejectOrder {
type Result = (); type Result = ();
} }
impl Message for Commit {
type Result = ();
}
// this signature is a bit different because we use `Address::attach_stream` // this signature is a bit different because we use `Address::attach_stream`
impl Message for TakerStreamMessage { impl Message for TakerStreamMessage {
type Result = KeepRunning; type Result = KeepRunning;

245
daemon/src/model/cfd.rs

@ -39,12 +39,30 @@ impl<'v> FromParam<'v> for OrderId {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] // TODO: Could potentially remove this and use the Role in the Order instead
/// Origin of the order
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
pub enum Origin { pub enum Origin {
Ours, Ours,
Theirs, Theirs,
} }
/// Role in the Cfd
#[derive(Debug, Copy, Clone)]
pub enum Role {
Maker,
Taker,
}
impl From<Origin> for Role {
fn from(origin: Origin) -> Self {
match origin {
Origin::Ours => Role::Maker,
Origin::Theirs => Role::Taker,
}
}
}
/// A concrete order created by a maker for a taker /// A concrete order created by a maker for a taker
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Order { pub struct Order {
@ -198,6 +216,15 @@ pub enum CfdState {
dlc: Dlc, dlc: Dlc,
}, },
/// The commit transaction was published but it not final yet
///
/// This state applies to taker and maker.
/// This state is needed, because otherwise the user does not get any feedback.
PendingCommit {
common: CfdStateCommon,
dlc: Dlc,
},
// TODO: At the moment we are appending to this state. The way this is handled internally is // TODO: At the moment we are appending to this state. The way this is handled internally is
// by inserting the same state with more information in the database. We could consider // by inserting the same state with more information in the database. We could consider
// changing this to insert different states or update the stae instead of inserting again. // changing this to insert different states or update the stae instead of inserting again.
@ -259,6 +286,7 @@ impl CfdState {
CfdState::MustRefund { common, .. } => common, CfdState::MustRefund { common, .. } => common,
CfdState::Refunded { common, .. } => common, CfdState::Refunded { common, .. } => common,
CfdState::SetupFailed { common, .. } => common, CfdState::SetupFailed { common, .. } => common,
CfdState::PendingCommit { common, .. } => common,
}; };
*common *common
@ -293,6 +321,9 @@ impl Display for CfdState {
CfdState::Open { .. } => { CfdState::Open { .. } => {
write!(f, "Open") write!(f, "Open")
} }
CfdState::PendingCommit { .. } => {
write!(f, "Pending Committ")
}
CfdState::OpenCommitted { .. } => { CfdState::OpenCommitted { .. } => {
write!(f, "Open Committed") write!(f, "Open Committed")
} }
@ -428,41 +459,32 @@ impl Cfd {
let new_state = match event { let new_state = match event {
CfdStateChangeEvent::Monitor(event) => match event { CfdStateChangeEvent::Monitor(event) => match event {
monitor::Event::LockFinality(_) => match self.state.clone() { monitor::Event::LockFinality(_) => {
PendingOpen { dlc, .. } => CfdState::Open { if let PendingOpen { dlc, .. } = self.state.clone() {
common: CfdStateCommon { CfdState::Open {
transition_timestamp: SystemTime::now(), common: CfdStateCommon {
}, transition_timestamp: SystemTime::now(),
dlc, },
}, dlc,
OutgoingOrderRequest { .. } => unreachable!("taker-only state"), }
IncomingOrderRequest { .. } | Accepted { .. } | ContractSetup { .. } => { } else {
bail!("Did not expect lock finality yet: ignoring") bail!(
} "Cannot transition to Open because of unexpected state {}",
Open { .. } | OpenCommitted { .. } | MustRefund { .. } => { self.state
bail!("State already assumes lock finality: ignoring") )
}
Rejected { .. } | Refunded { .. } | SetupFailed { .. } => {
bail!("The cfd is already in final state {}", self.state)
} }
}, }
monitor::Event::CommitFinality(_) => { monitor::Event::CommitFinality(_) => {
let dlc = match self.state.clone() { let dlc = if let Open { dlc, .. } = self.state.clone() {
Open { dlc, .. } => dlc, dlc
PendingOpen { dlc, .. } => { } else if let PendingOpen { dlc, .. } = self.state.clone() {
tracing::debug!(%order_id, "Was waiting on lock finality, jumping ahead"); tracing::debug!(%order_id, "Was in unexpected state {}, jumping ahead to OpenCommitted", self.state);
dlc dlc
} } else {
OutgoingOrderRequest { .. } => unreachable!("taker-only state"), bail!(
IncomingOrderRequest { .. } | Accepted { .. } | ContractSetup { .. } => { "Cannot transition to OpenCommitted because of unexpected state {}",
bail!("Did not expect commit finality yet: ignoring") self.state
} )
OpenCommitted { .. } | MustRefund { .. } => {
bail!("State already assumes commit finality: ignoring")
}
Rejected { .. } | Refunded { .. } | SetupFailed { .. } => {
bail!("The cfd is already in final state {}", self.state)
}
}; };
OpenCommitted { OpenCommitted {
@ -496,8 +518,8 @@ impl Cfd {
dlc, dlc,
cet_status: CetStatus::Ready(price), cet_status: CetStatus::Ready(price),
}, },
PendingOpen { dlc, .. } => { PendingOpen { dlc, .. } | Open { dlc, .. } => {
tracing::debug!(%order_id, "Was waiting on lock finality, jumping ahead"); tracing::debug!(%order_id, "Was in unexpected state {}, jumping ahead to MustRefund", self.state);
CfdState::OpenCommitted { CfdState::OpenCommitted {
common: CfdStateCommon { common: CfdStateCommon {
transition_timestamp: SystemTime::now(), transition_timestamp: SystemTime::now(),
@ -506,56 +528,22 @@ impl Cfd {
cet_status: CetStatus::TimelockExpired, cet_status: CetStatus::TimelockExpired,
} }
} }
Open { dlc, .. } => { _ => bail!(
tracing::debug!(%order_id, "Was not aware of commit TX broadcast, jumping ahead"); "Cannot transition to OpenCommitted because of unexpected state {}",
CfdState::OpenCommitted { self.state
common: CfdStateCommon { ),
transition_timestamp: SystemTime::now(),
},
dlc,
cet_status: CetStatus::TimelockExpired,
}
}
OutgoingOrderRequest { .. } => unreachable!("taker-only state"),
IncomingOrderRequest { .. } | Accepted { .. } | ContractSetup { .. } => {
bail!("Did not expect CET timelock expiry yet: ignoring")
}
OpenCommitted {
cet_status: CetStatus::TimelockExpired,
..
}
| OpenCommitted {
cet_status: CetStatus::Ready(_),
..
} => bail!("State already assumes CET timelock expiry: ignoring"),
MustRefund { .. } => {
bail!("Refund path does not care about CET timelock expiry: ignoring")
}
Rejected { .. } | Refunded { .. } | SetupFailed { .. } => {
bail!("The cfd is already in final state {}", self.state)
}
}, },
monitor::Event::RefundTimelockExpired(_) => { monitor::Event::RefundTimelockExpired(_) => {
let dlc = match self.state.clone() { let dlc = if let OpenCommitted { dlc, .. } = self.state.clone() {
OpenCommitted { dlc, .. } => dlc, dlc
MustRefund { .. } => { } else if let Open { dlc, .. } | PendingOpen { dlc, .. } = self.state.clone() {
bail!("State already assumes refund timelock expiry: ignoring") tracing::debug!(%order_id, "Was in unexpected state {}, jumping ahead to MustRefund", self.state);
} dlc
OutgoingOrderRequest { .. } => unreachable!("taker-only state"), } else {
IncomingOrderRequest { .. } | Accepted { .. } | ContractSetup { .. } => { bail!(
bail!("Did not expect refund timelock expiry yet: ignoring") "Cannot transition to OpenCommitted because of unexpected state {}",
} self.state
PendingOpen { dlc, .. } => { )
tracing::debug!(%order_id, "Was waiting on lock finality, jumping ahead");
dlc
}
Open { dlc, .. } => {
tracing::debug!(%order_id, "Was waiting on CET timelock expiry, jumping ahead");
dlc
}
Rejected { .. } | Refunded { .. } | SetupFailed { .. } => {
bail!("The cfd is already in final state {}", self.state)
}
}; };
MustRefund { MustRefund {
@ -566,24 +554,12 @@ impl Cfd {
} }
} }
monitor::Event::RefundFinality(_) => { monitor::Event::RefundFinality(_) => {
match self.state { if let MustRefund { .. } = self.state.clone() {
MustRefund { .. } => (), } else {
OutgoingOrderRequest { .. } => unreachable!("taker-only state"), tracing::debug!(
IncomingOrderRequest { .. } | Accepted { .. } | ContractSetup { .. } => { "Was in unexpected state {}, jumping ahead to Refunded",
bail!("Did not expect refund finality yet: ignoring") self.state
} );
PendingOpen { .. } => {
tracing::debug!(%order_id, "Was waiting on lock finality, jumping ahead");
}
Open { .. } => {
tracing::debug!(%order_id, "Was waiting on CET timelock expiry, jumping ahead");
}
OpenCommitted { .. } => {
tracing::debug!(%order_id, "Was waiting on refund timelock expiry, jumping ahead");
}
Rejected { .. } | Refunded { .. } | SetupFailed { .. } => {
bail!("The cfd is already in final state {}", self.state)
}
} }
Refunded { Refunded {
@ -596,6 +572,23 @@ impl Cfd {
todo!("Implement state transition") todo!("Implement state transition")
} }
}, },
CfdStateChangeEvent::CommitTxSent => {
let dlc = if let Open { dlc, .. } | PendingOpen { dlc, .. } = self.state.clone() {
dlc
} else {
bail!(
"Cannot transition to PendingCommit because of unexpected state {}",
self.state
)
};
PendingCommit {
common: CfdStateCommon {
transition_timestamp: SystemTime::now(),
},
dlc,
}
}
}; };
Ok(new_state) Ok(new_state)
@ -630,6 +623,43 @@ impl Cfd {
Ok(signed_refund_tx) Ok(signed_refund_tx)
} }
pub fn commit_tx(&self) -> Result<Transaction> {
let dlc = if let CfdState::Open { dlc, .. } | CfdState::PendingOpen { dlc, .. } =
self.state.clone()
{
dlc
} else {
bail!(
"Cannot publish commit transaction in state {}",
self.state.clone()
)
};
let sig_hash = spending_tx_sighash(
&dlc.commit.0,
&dlc.lock.1,
Amount::from_sat(dlc.lock.0.output[0].value),
);
let our_sig = SECP256K1.sign(&sig_hash, &dlc.identity);
let our_pubkey = PublicKey::new(bdk::bitcoin::secp256k1::PublicKey::from_secret_key(
SECP256K1,
&dlc.identity,
));
// TODO: verify that the dlc's `publish` sk corresponds to the decryption_sk we need here
let counterparty_sig = dlc.commit.1.decrypt(&dlc.publish)?;
let counterparty_pubkey = dlc.identity_counterparty;
let signed_commit_tx = finalize_spend_transaction(
dlc.commit.0,
&dlc.commit.2,
(our_pubkey, our_sig),
(counterparty_pubkey, counterparty_sig),
)?;
Ok(signed_commit_tx)
}
pub fn pending_open_dlc(&self) -> Option<Dlc> { pub fn pending_open_dlc(&self) -> Option<Dlc> {
if let CfdState::PendingOpen { dlc, .. } = self.state.clone() { if let CfdState::PendingOpen { dlc, .. } = self.state.clone() {
Some(dlc) Some(dlc)
@ -642,6 +672,10 @@ impl Cfd {
matches!(self.state.clone(), CfdState::MustRefund { .. }) matches!(self.state.clone(), CfdState::MustRefund { .. })
} }
pub fn is_pending_commit(&self) -> bool {
matches!(self.state.clone(), CfdState::PendingCommit { .. })
}
pub fn is_cleanup(&self) -> bool { pub fn is_cleanup(&self) -> bool {
matches!( matches!(
self.state.clone(), self.state.clone(),
@ -651,13 +685,18 @@ impl Cfd {
| CfdState::ContractSetup { .. } | CfdState::ContractSetup { .. }
) )
} }
pub fn role(&self) -> Role {
self.order.origin.into()
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum CfdStateChangeEvent { pub enum CfdStateChangeEvent {
// TODO: groupd other events by actors into enums and add them here so we can bundle all // TODO: group other events by actors into enums and add them here so we can bundle all
// transitions into cfd.transition_to(...) // transitions into cfd.transition_to(...)
Monitor(monitor::Event), Monitor(monitor::Event),
CommitTxSent,
} }
fn calculate_profit( fn calculate_profit(

2
daemon/src/monitor.rs

@ -68,7 +68,7 @@ where
actor.cfds.insert(cfd.order.id, params.clone()); actor.cfds.insert(cfd.order.id, params.clone());
actor.monitor_all(&params, cfd.order.id).await; actor.monitor_all(&params, cfd.order.id).await;
} }
CfdState::Open { dlc, .. } => { CfdState::Open { dlc, .. } | CfdState::PendingCommit { dlc, .. } => {
let params = MonitorParams::from_dlc_and_timelocks(dlc.clone(), cfd.refund_timelock_in_blocks()); let params = MonitorParams::from_dlc_and_timelocks(dlc.clone(), cfd.refund_timelock_in_blocks());
actor.cfds.insert(cfd.order.id, params.clone()); actor.cfds.insert(cfd.order.id, params.clone());

11
daemon/src/routes_maker.rs

@ -145,6 +145,17 @@ pub async fn post_cfd_action(
.await .await
.expect("actor to always be available"); .expect("actor to always be available");
} }
CfdAction::Commit => {
cfd_actor_address
.do_send_async(maker_cfd::Commit { order_id: id })
.await
.expect("actor to always be available");
}
CfdAction::Settle => {
return Err(status::BadRequest(Some(
"Collaborative settlement can only be triggered by taker".to_string(),
)));
}
} }
Ok(status::Accepted(None)) Ok(status::Accepted(None))

43
daemon/src/routes_taker.rs

@ -1,7 +1,7 @@
use crate::model::cfd::{calculate_buy_margin, Cfd, Order, OrderId}; use crate::model::cfd::{calculate_buy_margin, Cfd, Order, OrderId};
use crate::model::{Leverage, Usd, WalletInfo}; use crate::model::{Leverage, Usd, WalletInfo};
use crate::routes::EmbeddedFileExt; use crate::routes::EmbeddedFileExt;
use crate::to_sse_event::{CfdsWithCurrentPrice, ToSseEvent}; use crate::to_sse_event::{CfdAction, CfdsWithCurrentPrice, ToSseEvent};
use crate::{bitmex_price_feed, taker_cfd}; use crate::{bitmex_price_feed, taker_cfd};
use bdk::bitcoin::Amount; use bdk::bitcoin::Amount;
use rocket::http::{ContentType, Status}; use rocket::http::{ContentType, Status};
@ -96,20 +96,37 @@ pub async fn post_order_request(
.expect("actor to always be available"); .expect("actor to always be available");
} }
#[rocket::post("/cfd/<id>/settle")] #[rocket::post("/cfd/<id>/<action>")]
pub async fn post_settlement_proposal( pub async fn post_cfd_action(
id: OrderId, id: OrderId,
cfd_actor_inbox: &State<Address<taker_cfd::Actor>>, action: CfdAction,
cfd_actor_address: &State<Address<taker_cfd::Actor>>,
quote_updates: &State<watch::Receiver<bitmex_price_feed::Quote>>, quote_updates: &State<watch::Receiver<bitmex_price_feed::Quote>>,
) { ) -> Result<status::Accepted<()>, status::BadRequest<String>> {
let current_price = quote_updates.borrow().for_taker(); match action {
cfd_actor_inbox CfdAction::Accept | CfdAction::Reject => {
.do_send_async(taker_cfd::ProposeSettlement { return Err(status::BadRequest(None));
order_id: id, }
current_price,
}) CfdAction::Commit => {
.await cfd_actor_address
.expect("actor to always be available"); .do_send_async(taker_cfd::Commit { order_id: id })
.await
.map_err(|e| status::BadRequest(Some(e.to_string())))?;
}
CfdAction::Settle => {
let current_price = quote_updates.borrow().for_taker();
cfd_actor_address
.do_send_async(taker_cfd::ProposeSettlement {
order_id: id,
current_price,
})
.await
.expect("actor to always be available");
}
}
Ok(status::Accepted(None))
} }
#[rocket::get("/alive")] #[rocket::get("/alive")]

9
daemon/src/setup_contract.rs

@ -1,4 +1,4 @@
use crate::model::cfd::{Cfd, Dlc}; use crate::model::cfd::{Cfd, Dlc, Role};
use crate::wallet::Wallet; use crate::wallet::Wallet;
use crate::wire::{Msg0, Msg1, Msg2, SetupMsg}; use crate::wire::{Msg0, Msg1, Msg2, SetupMsg};
use crate::{model, payout_curve}; use crate::{model, payout_curve};
@ -190,13 +190,6 @@ pub async fn new(
}) })
} }
#[allow(dead_code)]
/// Role of the actor's owner in the upcoming contract
pub enum Role {
Maker,
Taker,
}
/// A convenience struct for storing PartyParams and PunishParams of both /// A convenience struct for storing PartyParams and PunishParams of both
/// parties and the role of the caller. /// parties and the role of the caller.
struct AllParams { struct AllParams {

2
daemon/src/taker.rs

@ -213,9 +213,9 @@ async fn main() -> Result<()> {
rocket::routes![ rocket::routes![
routes_taker::feed, routes_taker::feed,
routes_taker::post_order_request, routes_taker::post_order_request,
routes_taker::post_settlement_proposal,
routes_taker::get_health_check, routes_taker::get_health_check,
routes_taker::margin_calc, routes_taker::margin_calc,
routes_taker::post_cfd_action,
], ],
) )
.mount( .mount(

46
daemon/src/taker_cfd.rs

@ -4,7 +4,7 @@ use crate::db::{
load_cfd_by_order_id, load_order_by_id, load_cfd_by_order_id, load_order_by_id,
}; };
use crate::model::cfd::{ use crate::model::cfd::{
Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Origin, Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Origin, Role,
}; };
use crate::model::Usd; use crate::model::Usd;
use crate::monitor::{self, MonitorParams}; use crate::monitor::{self, MonitorParams};
@ -40,6 +40,10 @@ pub struct CfdSetupCompleted {
pub dlc: Result<Dlc>, pub dlc: Result<Dlc>,
} }
pub struct Commit {
pub order_id: OrderId,
}
enum SetupState { enum SetupState {
Active { Active {
sender: mpsc::UnboundedSender<wire::SetupMsg>, sender: mpsc::UnboundedSender<wire::SetupMsg>,
@ -86,6 +90,13 @@ impl Actor {
tracing::info!("Refund transaction published on chain: {}", txid); tracing::info!("Refund transaction published on chain: {}", txid);
} }
for cfd in cfds.iter().filter(|cfd| Cfd::is_pending_commit(cfd)) {
let signed_commit_tx = cfd.commit_tx()?;
let txid = wallet.try_broadcast_transaction(signed_commit_tx).await?;
tracing::info!("Commit transaction published on chain: {}", txid);
}
Ok(Self { Ok(Self {
db, db,
wallet, wallet,
@ -215,7 +226,7 @@ impl Actor {
(self.oracle_pk, nonce_pks), (self.oracle_pk, nonce_pks),
cfd, cfd,
self.wallet.clone(), self.wallet.clone(),
setup_contract::Role::Taker, Role::Taker,
); );
let this = ctx let this = ctx
@ -342,6 +353,26 @@ impl Actor {
Ok(()) Ok(())
} }
// TODO: Duplicated with maker
async fn handle_commit(&mut self, order_id: OrderId) -> Result<()> {
let mut conn = self.db.acquire().await?;
let cfd = load_cfd_by_order_id(order_id, &mut conn).await?;
let signed_commit_tx = cfd.commit_tx()?;
let txid = self
.wallet
.try_broadcast_transaction(signed_commit_tx)
.await?;
tracing::info!("Commit transaction published on chain: {}", txid);
let new_state = cfd.handle(CfdStateChangeEvent::CommitTxSent)?;
insert_new_cfd_state_by_order_id(cfd.order.id, new_state, &mut conn).await?;
Ok(())
}
async fn handle_oracle_announcements( async fn handle_oracle_announcements(
&mut self, &mut self,
announcements: oracle::Announcements, announcements: oracle::Announcements,
@ -441,6 +472,13 @@ impl Handler<oracle::Attestation> for Actor {
} }
} }
#[async_trait]
impl Handler<Commit> for Actor {
async fn handle(&mut self, msg: Commit, _ctx: &mut Context<Self>) {
log_error!(self.handle_commit(msg.order_id))
}
}
impl Message for TakeOffer { impl Message for TakeOffer {
type Result = (); type Result = ();
} }
@ -458,4 +496,8 @@ impl Message for CfdSetupCompleted {
type Result = (); type Result = ();
} }
impl Message for Commit {
type Result = ();
}
impl xtra::Actor for Actor {} impl xtra::Actor for Actor {}

23
daemon/src/to_sse_event.rs

@ -1,4 +1,4 @@
use crate::model::cfd::OrderId; use crate::model::cfd::{OrderId, Role};
use crate::model::{Leverage, Position, TradingPair, Usd}; use crate::model::{Leverage, Position, TradingPair, Usd};
use crate::{bitmex_price_feed, model}; use crate::{bitmex_price_feed, model};
use bdk::bitcoin::Amount; use bdk::bitcoin::Amount;
@ -36,6 +36,8 @@ pub struct Cfd {
pub enum CfdAction { pub enum CfdAction {
Accept, Accept,
Reject, Reject,
Commit,
Settle,
} }
impl<'v> FromParam<'v> for CfdAction { impl<'v> FromParam<'v> for CfdAction {
@ -56,6 +58,7 @@ pub enum CfdState {
ContractSetup, ContractSetup,
PendingOpen, PendingOpen,
Open, Open,
PendingCommit,
OpenCommitted, OpenCommitted,
MustRefund, MustRefund,
Refunded, Refunded,
@ -113,7 +116,7 @@ impl ToSseEvent for CfdsWithCurrentPrice {
profit_btc, profit_btc,
profit_usd, profit_usd,
state: cfd.state.clone().into(), state: cfd.state.clone().into(),
actions: actions_for_state(cfd.state.clone()), actions: actions_for_state(cfd.state.clone(), cfd.role()),
state_transition_timestamp: cfd state_transition_timestamp: cfd
.state .state
.get_transition_timestamp() .get_transition_timestamp()
@ -189,6 +192,7 @@ impl From<model::cfd::CfdState> for CfdState {
model::cfd::CfdState::MustRefund { .. } => CfdState::MustRefund, model::cfd::CfdState::MustRefund { .. } => CfdState::MustRefund,
model::cfd::CfdState::Refunded { .. } => CfdState::Refunded, model::cfd::CfdState::Refunded { .. } => CfdState::Refunded,
model::cfd::CfdState::SetupFailed { .. } => CfdState::SetupFailed, model::cfd::CfdState::SetupFailed { .. } => CfdState::SetupFailed,
model::cfd::CfdState::PendingCommit { .. } => CfdState::PendingCommit,
} }
} }
} }
@ -218,11 +222,16 @@ fn into_unix_secs(time: SystemTime) -> u64 {
.as_secs() .as_secs()
} }
fn actions_for_state(state: model::cfd::CfdState) -> Vec<CfdAction> { fn actions_for_state(state: model::cfd::CfdState, role: Role) -> Vec<CfdAction> {
if let model::cfd::CfdState::IncomingOrderRequest { .. } = state { match (state, role) {
vec![CfdAction::Accept, CfdAction::Reject] (model::cfd::CfdState::IncomingOrderRequest { .. }, Role::Maker) => {
} else { vec![CfdAction::Accept, CfdAction::Reject]
vec![] }
(model::cfd::CfdState::Open { .. }, Role::Taker) => {
vec![CfdAction::Commit, CfdAction::Settle]
}
(model::cfd::CfdState::Open { .. }, Role::Maker) => vec![CfdAction::Commit],
_ => vec![],
} }
} }

7
frontend/src/components/Types.tsx

@ -69,6 +69,8 @@ export class State {
return "Pending Open"; return "Pending Open";
case StateKey.OPEN: case StateKey.OPEN:
return "Open"; return "Open";
case StateKey.PENDING_COMMIT:
return "Pending Commit";
case StateKey.OPEN_COMMITTED: case StateKey.OPEN_COMMITTED:
return "Open (commit-tx published)"; return "Open (commit-tx published)";
case StateKey.MUST_REFUND: case StateKey.MUST_REFUND:
@ -94,6 +96,7 @@ export class State {
case StateKey.REJECTED: case StateKey.REJECTED:
return red; return red;
case StateKey.PENDING_COMMIT:
case StateKey.OPEN_COMMITTED: case StateKey.OPEN_COMMITTED:
case StateKey.MUST_REFUND: case StateKey.MUST_REFUND:
return orange; return orange;
@ -120,6 +123,7 @@ export class State {
case StateKey.PENDING_OPEN: case StateKey.PENDING_OPEN:
case StateKey.OPEN: case StateKey.OPEN:
case StateKey.PENDING_COMMIT:
case StateKey.OPEN_COMMITTED: case StateKey.OPEN_COMMITTED:
case StateKey.MUST_REFUND: case StateKey.MUST_REFUND:
return StateGroupKey.OPEN; return StateGroupKey.OPEN;
@ -135,6 +139,8 @@ export class State {
export enum Action { export enum Action {
ACCEPT = "accept", ACCEPT = "accept",
REJECT = "reject", REJECT = "reject",
COMMIT = "commit",
SETTLE = "settle",
} }
const enum StateKey { const enum StateKey {
@ -145,6 +151,7 @@ const enum StateKey {
CONTRACT_SETUP = "ContractSetup", CONTRACT_SETUP = "ContractSetup",
PENDING_OPEN = "PendingOpen", PENDING_OPEN = "PendingOpen",
OPEN = "Open", OPEN = "Open",
PENDING_COMMIT = "PendingCommit",
OPEN_COMMITTED = "OpenCommitted", OPEN_COMMITTED = "OpenCommitted",
MUST_REFUND = "MustRefund", MUST_REFUND = "MustRefund",
REFUNDED = "Refunded", REFUNDED = "Refunded",

10
frontend/src/components/cfdtables/CfdTable.tsx

@ -1,10 +1,12 @@
import { import {
CheckCircleIcon,
CheckIcon, CheckIcon,
ChevronRightIcon, ChevronRightIcon,
ChevronUpIcon, ChevronUpIcon,
CloseIcon, CloseIcon,
TriangleDownIcon, TriangleDownIcon,
TriangleUpIcon, TriangleUpIcon,
WarningIcon,
} from "@chakra-ui/icons"; } from "@chakra-ui/icons";
import { import {
Badge, Badge,
@ -186,6 +188,10 @@ function iconForAction(action: Action): any {
return <CheckIcon />; return <CheckIcon />;
case Action.REJECT: case Action.REJECT:
return <CloseIcon />; return <CloseIcon />;
case Action.COMMIT:
return <WarningIcon />;
case Action.SETTLE:
return <CheckCircleIcon />;
} }
} }
@ -195,6 +201,10 @@ function colorSchemaForAction(action: Action): string {
return "green"; return "green";
case Action.REJECT: case Action.REJECT:
return "red"; return "red";
case Action.COMMIT:
return "red";
case Action.SETTLE:
return "green";
} }
} }

Loading…
Cancel
Save