Browse Source

Non-collaborative close by publishing commit tx

upload-correct-windows-binary
Daniel Karzel 3 years ago
parent
commit
42b4701bc7
No known key found for this signature in database GPG Key ID: 30C3FC2E438ADB6E
  1. 34
      daemon/src/maker_cfd.rs
  2. 70
      daemon/src/model/cfd.rs
  3. 2
      daemon/src/monitor.rs
  4. 6
      daemon/src/routes_maker.rs
  5. 23
      daemon/src/routes_taker.rs
  6. 1
      daemon/src/taker.rs
  7. 35
      daemon/src/taker_cfd.rs
  8. 13
      daemon/src/to_sse_event.rs
  9. 6
      frontend/src/components/Types.tsx
  10. 5
      frontend/src/components/cfdtables/CfdTable.tsx

34
daemon/src/maker_cfd.rs

@ -29,6 +29,10 @@ pub struct RejectOrder {
pub order_id: OrderId,
}
pub struct Commit {
pub order_id: OrderId,
}
pub struct NewOrder(pub Order);
pub struct NewTakerOnline {
@ -417,6 +421,25 @@ impl Actor {
Ok(())
}
async fn handle_commit(&mut self, msg: Commit) -> Result<()> {
let mut conn = self.db.acquire().await?;
let cfd = load_cfd_by_order_id(msg.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<()> {
let order_id = event.order_id();
@ -479,6 +502,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))
}
}
#[async_trait]
impl Handler<NewOrder> for Actor {
async fn handle(&mut self, msg: NewOrder, _ctx: &mut Context<Self>) {
@ -586,6 +616,10 @@ impl Message for RejectOrder {
type Result = ();
}
impl Message for Commit {
type Result = ();
}
// this signature is a bit different because we use `Address::attach_stream`
impl Message for TakerStreamMessage {
type Result = KeepRunning;

70
daemon/src/model/cfd.rs

@ -198,6 +198,15 @@ pub enum CfdState {
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
// 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.
@ -259,6 +268,7 @@ impl CfdState {
CfdState::MustRefund { common, .. } => common,
CfdState::Refunded { common, .. } => common,
CfdState::SetupFailed { common, .. } => common,
CfdState::PendingCommit { common, .. } => common,
};
*common
@ -293,6 +303,9 @@ impl Display for CfdState {
CfdState::Open { .. } => {
write!(f, "Open")
}
CfdState::PendingCommit { .. } => {
write!(f, "Pending Committ")
}
CfdState::OpenCommitted { .. } => {
write!(f, "Open Committed")
}
@ -541,6 +554,23 @@ impl Cfd {
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)
@ -575,6 +605,43 @@ impl Cfd {
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> {
if let CfdState::PendingOpen { dlc, .. } = self.state.clone() {
Some(dlc)
@ -600,9 +667,10 @@ impl Cfd {
#[derive(Debug, Clone)]
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(...)
Monitor(monitor::Event),
CommitTxSent,
}
fn calculate_profit(

2
daemon/src/monitor.rs

@ -68,7 +68,7 @@ where
actor.cfds.insert(cfd.order.id, params.clone());
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());
actor.cfds.insert(cfd.order.id, params.clone());

6
daemon/src/routes_maker.rs

@ -145,6 +145,12 @@ pub async fn post_cfd_action(
.await
.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");
}
}
Ok(status::Accepted(None))

23
daemon/src/routes_taker.rs

@ -1,7 +1,7 @@
use crate::model::cfd::{calculate_buy_margin, Cfd, Order, OrderId};
use crate::model::{Leverage, Usd, WalletInfo};
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 bdk::bitcoin::Amount;
use rocket::http::{ContentType, Status};
@ -112,6 +112,27 @@ pub async fn post_settlement_proposal(
.expect("actor to always be available");
}
#[rocket::post("/cfd/<id>/<action>")]
pub async fn post_cfd_action(
id: OrderId,
action: CfdAction,
cfd_actor_address: &State<Address<taker_cfd::Actor>>,
) -> Result<status::Accepted<()>, status::BadRequest<String>> {
match action {
CfdAction::Accept | CfdAction::Reject => {
unreachable!("The taker does not accept and reject");
}
CfdAction::Commit => {
cfd_actor_address
.do_send_async(taker_cfd::Commit { order_id: id })
.await
.expect("actor to always be available");
}
}
Ok(status::Accepted(None))
}
#[rocket::get("/alive")]
pub fn get_health_check() {}

1
daemon/src/taker.rs

@ -216,6 +216,7 @@ async fn main() -> Result<()> {
routes_taker::post_settlement_proposal,
routes_taker::get_health_check,
routes_taker::margin_calc,
routes_taker::post_cfd_action,
],
)
.mount(

35
daemon/src/taker_cfd.rs

@ -40,6 +40,10 @@ pub struct CfdSetupCompleted {
pub dlc: Result<Dlc>,
}
pub struct Commit {
pub order_id: OrderId,
}
enum SetupState {
Active {
sender: mpsc::UnboundedSender<wire::SetupMsg>,
@ -342,6 +346,26 @@ impl Actor {
Ok(())
}
// TODO: Duplicated with maker
async fn handle_commit(&mut self, msg: Commit) -> Result<()> {
let mut conn = self.db.acquire().await?;
let cfd = load_cfd_by_order_id(msg.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(
&mut self,
announcements: oracle::Announcements,
@ -441,6 +465,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))
}
}
impl Message for TakeOffer {
type Result = ();
}
@ -458,4 +489,8 @@ impl Message for CfdSetupCompleted {
type Result = ();
}
impl Message for Commit {
type Result = ();
}
impl xtra::Actor for Actor {}

13
daemon/src/to_sse_event.rs

@ -36,6 +36,7 @@ pub struct Cfd {
pub enum CfdAction {
Accept,
Reject,
Commit,
}
impl<'v> FromParam<'v> for CfdAction {
@ -56,6 +57,7 @@ pub enum CfdState {
ContractSetup,
PendingOpen,
Open,
PendingCommit,
OpenCommitted,
MustRefund,
Refunded,
@ -189,6 +191,7 @@ impl From<model::cfd::CfdState> for CfdState {
model::cfd::CfdState::MustRefund { .. } => CfdState::MustRefund,
model::cfd::CfdState::Refunded { .. } => CfdState::Refunded,
model::cfd::CfdState::SetupFailed { .. } => CfdState::SetupFailed,
model::cfd::CfdState::PendingCommit { .. } => CfdState::PendingCommit,
}
}
}
@ -219,10 +222,12 @@ fn into_unix_secs(time: SystemTime) -> u64 {
}
fn actions_for_state(state: model::cfd::CfdState) -> Vec<CfdAction> {
if let model::cfd::CfdState::IncomingOrderRequest { .. } = state {
vec![CfdAction::Accept, CfdAction::Reject]
} else {
vec![]
match state {
model::cfd::CfdState::IncomingOrderRequest { .. } => {
vec![CfdAction::Accept, CfdAction::Reject]
}
model::cfd::CfdState::Open { .. } => vec![CfdAction::Commit],
_ => vec![],
}
}

6
frontend/src/components/Types.tsx

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

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

@ -5,6 +5,7 @@ import {
CloseIcon,
TriangleDownIcon,
TriangleUpIcon,
WarningIcon,
} from "@chakra-ui/icons";
import {
Badge,
@ -186,6 +187,8 @@ function iconForAction(action: Action): any {
return <CheckIcon />;
case Action.REJECT:
return <CloseIcon />;
case Action.COMMIT:
return <WarningIcon />;
}
}
@ -195,6 +198,8 @@ function colorSchemaForAction(action: Action): string {
return "green";
case Action.REJECT:
return "red";
case Action.COMMIT:
return "red";
}
}

Loading…
Cancel
Save