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)?; let (tx, sig_maker) = dlc.close_transaction(proposal)?;
cfd.handle(CfdStateChangeEvent::ProposalSigned( 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?; 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( cfd.handle(CfdStateChangeEvent::CloseSent(TimestampedTransaction::new(
spend_tx, spend_tx,
dlc.script_pubkey_for(cfd.role()),
)))?; )))?;
insert_new_cfd_state_by_order_id(cfd.order.id, cfd.state, &mut conn).await?; 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 { Closed {
common: CfdStateCommon, common: CfdStateCommon,
// TODO: Use an enum of either Attestation or CollaborativeSettlement // TODO: Use an enum of either Attestation or CollaborativeSettlement
attestation: Option<Attestation>, payout: Payout,
}, },
// TODO: Can be extended with CetStatus // 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)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Attestation { pub struct Attestation {
pub id: BitMexPriceEventId, pub id: BitMexPriceEventId,
@ -323,10 +329,7 @@ impl Attestation {
let txid = cet.tx.txid(); let txid = cet.tx.txid();
let our_script_pubkey = match role { let our_script_pubkey = dlc.script_pubkey_for(role);
Role::Maker => dlc.maker_address.script_pubkey(),
Role::Taker => dlc.taker_address.script_pubkey(),
};
let payout = cet let payout = cet
.tx .tx
.output .output
@ -723,11 +726,16 @@ impl Cfd {
}, },
} }
} }
monitor::Event::CloseFinality(_) => CfdState::Closed { monitor::Event::CloseFinality(_) => {
common: CfdStateCommon { let collaborative_close = self.collaborative_close().context("No collaborative close after reaching collaborative close finality")?;
transition_timestamp: SystemTime::now(),
}, CfdState::Closed {
attestation: None common: CfdStateCommon {
transition_timestamp: SystemTime::now(),
},
payout: Payout::CollaborativeClose(collaborative_close)
}
}, },
monitor::Event::CetTimelockExpired(_) => match self.state.clone() { monitor::Event::CetTimelockExpired(_) => match self.state.clone() {
CfdState::OpenCommitted { CfdState::OpenCommitted {
@ -819,7 +827,7 @@ impl Cfd {
common: CfdStateCommon { common: CfdStateCommon {
transition_timestamp: SystemTime::now(), transition_timestamp: SystemTime::now(),
}, },
attestation: Some(attestation), payout: Payout::Cet(attestation)
} }
} }
monitor::Event::RevokedTransactionFound(_) => { monitor::Event::RevokedTransactionFound(_) => {
@ -1180,7 +1188,7 @@ impl Cfd {
} }
| CfdState::PendingCet { attestation, .. } | CfdState::PendingCet { attestation, .. }
| CfdState::Closed { | CfdState::Closed {
attestation: Some(attestation), payout: Payout::Cet(attestation),
.. ..
} => Some(attestation), } => Some(attestation),
@ -1200,6 +1208,60 @@ impl Cfd {
| CfdState::SetupFailed { .. } => None, | 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)] #[derive(thiserror::Error, Debug, Clone)]
@ -1664,6 +1726,13 @@ impl Dlc {
.map(|output| Amount::from_sat(output.value)) .map(|output| Amount::from_sat(output.value))
.unwrap_or_default() .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 /// Information which we need to remember in order to construct a
@ -1691,13 +1760,37 @@ pub struct RevokedCommit {
pub struct TimestampedTransaction { pub struct TimestampedTransaction {
pub tx: Transaction, pub tx: Transaction,
pub timestamp: SystemTime, pub timestamp: SystemTime,
#[serde(with = "::bdk::bitcoin::util::amount::serde::as_sat")]
payout: Amount,
} }
impl TimestampedTransaction { 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 { Self {
tx, tx,
timestamp: SystemTime::now(), timestamp: SystemTime::now(),
payout,
} }
} }
pub fn payout(&self) -> Amount {
self.payout
}
} }

5
daemon/src/taker_cfd.rs

@ -370,10 +370,13 @@ impl Actor {
.await?; .await?;
cfd.handle(CfdStateChangeEvent::ProposalSigned( 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?; 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)?; self.remove_pending_proposal(&order_id)?;
Ok(()) Ok(())

89
daemon/src/to_sse_event.rs

@ -1,5 +1,5 @@
use crate::model::cfd::{ 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::model::{Leverage, Position, TradingPair, Usd};
use crate::{bitmex_price_feed, model}; use crate::{bitmex_price_feed, model};
@ -86,6 +86,10 @@ impl TxUrlBuilder {
TxUrl::new(txid, self.network, TxLabel::Cet) 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 { pub fn refund(&self, dlc: &Dlc) -> TxUrl {
TxUrl::new(dlc.refund.0.txid(), self.network, TxLabel::Refund) TxUrl::new(dlc.refund.0.txid(), self.network, TxLabel::Refund)
} }
@ -97,6 +101,7 @@ pub enum TxLabel {
Commit, Commit,
Cet, Cet,
Refund, Refund,
Collaborative,
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[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 pending_proposal = self.pending_proposals.get(&cfd.order.id);
let state = to_cfd_state(&cfd.state, pending_proposal); 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 { Cfd {
order_id: cfd.order.id, order_id: cfd.order.id,
initial_price: cfd.order.price, 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 // 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 // in the DB internally) and does not have to be calculated
margin: cfd.margin().unwrap(), margin: cfd.margin().unwrap(),
details: to_cfd_details(cfd.state.clone(), cfd.role(), network), details,
} }
}) })
.collect::<Vec<Cfd>>(); .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::*; use model::cfd::CfdState::*;
let tx_ub = TxUrlBuilder::new(network); let tx_ub = TxUrlBuilder::new(network);
let (txs, payout) = match state { match state {
PendingOpen { PendingOpen { dlc, .. } | Open { dlc, .. } => {
dlc, attestation, .. vec![tx_ub.lock(&dlc)]
} }
| Open { PendingCommit { dlc, .. } => vec![tx_ub.lock(&dlc), tx_ub.commit(&dlc)],
dlc, attestation, .. OpenCommitted { dlc, .. } => vec![tx_ub.lock(&dlc), tx_ub.commit(&dlc)],
} => (
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()),
),
PendingCet { PendingCet {
dlc, attestation, .. dlc, attestation, ..
} => ( } => vec![
vec![ tx_ub.lock(&dlc),
tx_ub.lock(&dlc), tx_ub.commit(&dlc),
tx_ub.commit(&dlc), tx_ub.cet(attestation.txid()),
tx_ub.cet(attestation.txid()), ],
],
Some(attestation.payout()),
),
Closed { Closed {
attestation: Some(attestation), payout: Payout::Cet(attestation),
.. ..
} => ( } => vec![tx_ub.cet(attestation.txid())],
vec![tx_ub.cet(attestation.txid())],
Some(attestation.payout()),
),
Closed { Closed {
attestation: None, .. payout: Payout::CollaborativeClose(collaborative_close),
..
} => { } => {
// TODO: Provide CfdDetails about collaborative settlement vec![tx_ub.collaborative_close(collaborative_close.tx.txid())]
(vec![], None)
} }
MustRefund { dlc, .. } => ( MustRefund { dlc, .. } => vec![tx_ub.lock(&dlc), tx_ub.commit(&dlc), tx_ub.refund(&dlc)],
vec![tx_ub.lock(&dlc), tx_ub.commit(&dlc), tx_ub.refund(&dlc)], Refunded { dlc, .. } => vec![tx_ub.refund(&dlc)],
Some(dlc.refund_amount(role)),
),
Refunded { dlc, .. } => (vec![tx_ub.refund(&dlc)], Some(dlc.refund_amount(role))),
OutgoingOrderRequest { .. } OutgoingOrderRequest { .. }
| IncomingOrderRequest { .. } | IncomingOrderRequest { .. }
| PendingClose { .. } | PendingClose { .. }
| Accepted { .. } | Accepted { .. }
| Rejected { .. } | Rejected { .. }
| ContractSetup { .. } | ContractSetup { .. }
| SetupFailed { .. } => (vec![], None), | SetupFailed { .. } => vec![],
};
CfdDetails {
tx_url_list: txs,
payout,
} }
} }

Loading…
Cancel
Save