Browse Source

Merge #302

302: Collaborative close payout r=da-kami a=da-kami

Distinguish between collaborative close and cet closing.
Moved the payout on the `Cfd` which decides what the payout of the `Cfd` is, where

- `None` means the payout is not decided yet (we can optimize that and potentially combine this with `profit` in another iteration)
- `Some(payout)` can be a refund, collaborative or cet payout

Refund is handled in an early exit guard based on the state.
Collaborative settlement has priority over cet payout (attestation). In case there is a collaborative settlement present we always return payout based on that.

Note: I am not totally sure if collaborative close payout should always have priority over attestation payout. We could do a more complex reasoning about the state as well, but I felt for now this is probably good enough and we can adapt based on experience. Happy for other suggestions :)

Co-authored-by: Daniel Karzel <daniel@comit.network>
refactor/no-log-handler
bors[bot] 3 years ago
committed by GitHub
parent
commit
dab7fefa88
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      daemon/src/maker_cfd.rs
  2. 119
      daemon/src/model/cfd.rs
  3. 5
      daemon/src/taker_cfd.rs
  4. 89
      daemon/src/to_sse_event.rs

3
daemon/src/maker_cfd.rs

@ -304,7 +304,7 @@ impl Actor {
let (tx, sig_maker) = dlc.close_transaction(proposal)?;
cfd.handle(CfdStateChangeEvent::ProposalSigned(
TimestampedTransaction::new(tx.clone()),
TimestampedTransaction::new(tx.clone(), dlc.script_pubkey_for(cfd.role())),
))?;
insert_new_cfd_state_by_order_id(cfd.order.id, cfd.state.clone(), &mut conn).await?;
@ -319,6 +319,7 @@ impl Actor {
cfd.handle(CfdStateChangeEvent::CloseSent(TimestampedTransaction::new(
spend_tx,
dlc.script_pubkey_for(cfd.role()),
)))?;
insert_new_cfd_state_by_order_id(cfd.order.id, cfd.state, &mut conn).await?;

119
daemon/src/model/cfd.rs

@ -273,7 +273,7 @@ pub enum CfdState {
Closed {
common: CfdStateCommon,
// TODO: Use an enum of either Attestation or CollaborativeSettlement
attestation: Option<Attestation>,
payout: Payout,
},
// TODO: Can be extended with CetStatus
@ -297,6 +297,12 @@ pub enum CfdState {
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Payout {
CollaborativeClose(TimestampedTransaction),
Cet(Attestation),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Attestation {
pub id: BitMexPriceEventId,
@ -323,10 +329,7 @@ impl Attestation {
let txid = cet.tx.txid();
let our_script_pubkey = match role {
Role::Maker => dlc.maker_address.script_pubkey(),
Role::Taker => dlc.taker_address.script_pubkey(),
};
let our_script_pubkey = dlc.script_pubkey_for(role);
let payout = cet
.tx
.output
@ -723,11 +726,16 @@ impl Cfd {
},
}
}
monitor::Event::CloseFinality(_) => CfdState::Closed {
common: CfdStateCommon {
transition_timestamp: SystemTime::now(),
},
attestation: None
monitor::Event::CloseFinality(_) => {
let collaborative_close = self.collaborative_close().context("No collaborative close after reaching collaborative close finality")?;
CfdState::Closed {
common: CfdStateCommon {
transition_timestamp: SystemTime::now(),
},
payout: Payout::CollaborativeClose(collaborative_close)
}
},
monitor::Event::CetTimelockExpired(_) => match self.state.clone() {
CfdState::OpenCommitted {
@ -819,7 +827,7 @@ impl Cfd {
common: CfdStateCommon {
transition_timestamp: SystemTime::now(),
},
attestation: Some(attestation),
payout: Payout::Cet(attestation)
}
}
monitor::Event::RevokedTransactionFound(_) => {
@ -1180,7 +1188,7 @@ impl Cfd {
}
| CfdState::PendingCet { attestation, .. }
| CfdState::Closed {
attestation: Some(attestation),
payout: Payout::Cet(attestation),
..
} => Some(attestation),
@ -1200,6 +1208,60 @@ impl Cfd {
| CfdState::SetupFailed { .. } => None,
}
}
pub fn collaborative_close(&self) -> Option<TimestampedTransaction> {
match self.state.clone() {
CfdState::Open {
collaborative_close: Some(collaborative_close),
..
}
| CfdState::PendingClose {
collaborative_close,
..
}
| CfdState::Closed {
payout: Payout::CollaborativeClose(collaborative_close),
..
} => Some(collaborative_close),
CfdState::OutgoingOrderRequest { .. }
| CfdState::IncomingOrderRequest { .. }
| CfdState::Accepted { .. }
| CfdState::Rejected { .. }
| CfdState::ContractSetup { .. }
| CfdState::PendingOpen { .. }
| CfdState::Open { .. }
| CfdState::PendingCommit { .. }
| CfdState::PendingCet { .. }
| CfdState::Closed { .. }
| CfdState::OpenCommitted { .. }
| CfdState::MustRefund { .. }
| CfdState::Refunded { .. }
| CfdState::SetupFailed { .. } => None,
}
}
/// Returns the payout of the Cfd
///
/// In case the cfd's payout is not fixed yet (because we don't have attestation or
/// collaborative close transaction None is returned, which means that the payout is still
/// undecided
pub fn payout(&self) -> Option<Amount> {
// early exit in case of refund scenario
if let CfdState::MustRefund { dlc, .. } | CfdState::Refunded { dlc, .. } =
self.state.clone()
{
return Some(dlc.refund_amount(self.role()));
}
// decision between attestation and collaborative close payout
match (self.attestation(), self.collaborative_close()) {
(Some(_attestation), Some(collaborative_close)) => Some(collaborative_close.payout()),
(None, Some(collaborative_close)) => Some(collaborative_close.payout()),
(Some(attestation), None) => Some(attestation.payout()),
(None, None) => None,
}
}
}
#[derive(thiserror::Error, Debug, Clone)]
@ -1664,6 +1726,13 @@ impl Dlc {
.map(|output| Amount::from_sat(output.value))
.unwrap_or_default()
}
pub fn script_pubkey_for(&self, role: Role) -> Script {
match role {
Role::Maker => self.maker_address.script_pubkey(),
Role::Taker => self.taker_address.script_pubkey(),
}
}
}
/// Information which we need to remember in order to construct a
@ -1691,13 +1760,37 @@ pub struct RevokedCommit {
pub struct TimestampedTransaction {
pub tx: Transaction,
pub timestamp: SystemTime,
#[serde(with = "::bdk::bitcoin::util::amount::serde::as_sat")]
payout: Amount,
}
impl TimestampedTransaction {
pub fn new(tx: Transaction) -> Self {
pub fn new(tx: Transaction, own_script_pubkey: Script) -> Self {
// Falls back to Amount::ZERO in case we don't find an output that matches out script pubkey
// The assumption is, that this can happen for cases where we were liuqidated
let payout = match tx
.output
.iter()
.find(|output| output.script_pubkey == own_script_pubkey)
.map(|output| Amount::from_sat(output.value))
{
Some(payout) => payout,
None => {
tracing::error!(
"Collaborative settlement with a zero amount, this should really not happen!"
);
Amount::ZERO
}
};
Self {
tx,
timestamp: SystemTime::now(),
payout,
}
}
pub fn payout(&self) -> Amount {
self.payout
}
}

5
daemon/src/taker_cfd.rs

@ -370,10 +370,13 @@ impl Actor {
.await?;
cfd.handle(CfdStateChangeEvent::ProposalSigned(
TimestampedTransaction::new(tx),
TimestampedTransaction::new(tx, dlc.script_pubkey_for(cfd.role())),
))?;
insert_new_cfd_state_by_order_id(cfd.order.id, cfd.state, &mut conn).await?;
self.cfd_feed_actor_inbox
.send(load_all_cfds(&mut conn).await?)?;
self.remove_pending_proposal(&order_id)?;
Ok(())

89
daemon/src/to_sse_event.rs

@ -1,5 +1,5 @@
use crate::model::cfd::{
CetStatus, Dlc, OrderId, Role, SettlementKind, UpdateCfdProposal, UpdateCfdProposals,
Dlc, OrderId, Payout, Role, SettlementKind, UpdateCfdProposal, UpdateCfdProposals,
};
use crate::model::{Leverage, Position, TradingPair, Usd};
use crate::{bitmex_price_feed, model};
@ -86,6 +86,10 @@ impl TxUrlBuilder {
TxUrl::new(txid, self.network, TxLabel::Cet)
}
pub fn collaborative_close(&self, txid: Txid) -> TxUrl {
TxUrl::new(txid, self.network, TxLabel::Collaborative)
}
pub fn refund(&self, dlc: &Dlc) -> TxUrl {
TxUrl::new(dlc.refund.0.txid(), self.network, TxLabel::Refund)
}
@ -97,6 +101,7 @@ pub enum TxLabel {
Commit,
Cet,
Refund,
Collaborative,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
@ -225,6 +230,11 @@ impl ToSseEvent for CfdsWithAuxData {
let pending_proposal = self.pending_proposals.get(&cfd.order.id);
let state = to_cfd_state(&cfd.state, pending_proposal);
let details = CfdDetails {
tx_url_list: to_tx_url_list(cfd.state.clone(), network),
payout: cfd.payout(),
};
Cfd {
order_id: cfd.order.id,
initial_price: cfd.order.price,
@ -247,7 +257,7 @@ impl ToSseEvent for CfdsWithAuxData {
// TODO: Depending on the state the margin might be set (i.e. in Open we save it
// in the DB internally) and does not have to be calculated
margin: cfd.margin().unwrap(),
details: to_cfd_details(cfd.state.clone(), cfd.role(), network),
details,
}
})
.collect::<Vec<Cfd>>();
@ -344,80 +354,43 @@ fn to_cfd_state(
}
}
fn to_cfd_details(state: model::cfd::CfdState, role: Role, network: Network) -> CfdDetails {
fn to_tx_url_list(state: model::cfd::CfdState, network: Network) -> Vec<TxUrl> {
use model::cfd::CfdState::*;
let tx_ub = TxUrlBuilder::new(network);
let (txs, payout) = match state {
PendingOpen {
dlc, attestation, ..
match state {
PendingOpen { dlc, .. } | Open { dlc, .. } => {
vec![tx_ub.lock(&dlc)]
}
| Open {
dlc, attestation, ..
} => (
vec![tx_ub.lock(&dlc)],
attestation.map(|attestation| attestation.payout()),
),
PendingCommit {
dlc, attestation, ..
} => (
vec![tx_ub.lock(&dlc), tx_ub.commit(&dlc)],
attestation.map(|attestation| attestation.payout()),
),
OpenCommitted {
dlc,
cet_status: CetStatus::Unprepared | CetStatus::TimelockExpired,
..
} => (vec![tx_ub.lock(&dlc), tx_ub.commit(&dlc)], None),
OpenCommitted {
dlc,
cet_status: CetStatus::OracleSigned(attestation) | CetStatus::Ready(attestation),
..
} => (
vec![tx_ub.lock(&dlc), tx_ub.commit(&dlc)],
Some(attestation.payout()),
),
PendingCommit { dlc, .. } => vec![tx_ub.lock(&dlc), tx_ub.commit(&dlc)],
OpenCommitted { dlc, .. } => vec![tx_ub.lock(&dlc), tx_ub.commit(&dlc)],
PendingCet {
dlc, attestation, ..
} => (
vec![
tx_ub.lock(&dlc),
tx_ub.commit(&dlc),
tx_ub.cet(attestation.txid()),
],
Some(attestation.payout()),
),
} => vec![
tx_ub.lock(&dlc),
tx_ub.commit(&dlc),
tx_ub.cet(attestation.txid()),
],
Closed {
attestation: Some(attestation),
payout: Payout::Cet(attestation),
..
} => (
vec![tx_ub.cet(attestation.txid())],
Some(attestation.payout()),
),
} => vec![tx_ub.cet(attestation.txid())],
Closed {
attestation: None, ..
payout: Payout::CollaborativeClose(collaborative_close),
..
} => {
// TODO: Provide CfdDetails about collaborative settlement
(vec![], None)
vec![tx_ub.collaborative_close(collaborative_close.tx.txid())]
}
MustRefund { dlc, .. } => (
vec![tx_ub.lock(&dlc), tx_ub.commit(&dlc), tx_ub.refund(&dlc)],
Some(dlc.refund_amount(role)),
),
Refunded { dlc, .. } => (vec![tx_ub.refund(&dlc)], Some(dlc.refund_amount(role))),
MustRefund { dlc, .. } => vec![tx_ub.lock(&dlc), tx_ub.commit(&dlc), tx_ub.refund(&dlc)],
Refunded { dlc, .. } => vec![tx_ub.refund(&dlc)],
OutgoingOrderRequest { .. }
| IncomingOrderRequest { .. }
| PendingClose { .. }
| Accepted { .. }
| Rejected { .. }
| ContractSetup { .. }
| SetupFailed { .. } => (vec![], None),
};
CfdDetails {
tx_url_list: txs,
payout,
| SetupFailed { .. } => vec![],
}
}

Loading…
Cancel
Save