From a1dd85f4819eeb4c73b97edc37200e3702e103e6 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 18 Nov 2021 18:30:06 +1100 Subject: [PATCH] Split `Cfd#handle` function into multiple We always constructed the specific enum variant at the call site. Might as well call a particular function which is simpler. --- daemon/src/cfd_actors.rs | 11 +- daemon/src/maker_cfd.rs | 13 +- daemon/src/model/cfd.rs | 508 +++++++++++++++++++++------------------ daemon/src/taker_cfd.rs | 17 +- 4 files changed, 297 insertions(+), 252 deletions(-) diff --git a/daemon/src/cfd_actors.rs b/daemon/src/cfd_actors.rs index 820504a..6a4692e 100644 --- a/daemon/src/cfd_actors.rs +++ b/daemon/src/cfd_actors.rs @@ -1,5 +1,5 @@ use crate::db::load_cfd_by_order_id; -use crate::model::cfd::{Attestation, Cfd, CfdState, CfdStateChangeEvent, OrderId}; +use crate::model::cfd::{Attestation, Cfd, CfdState, OrderId}; use crate::{db, monitor, oracle, try_continue, wallet}; use anyhow::{bail, Context, Result}; use sqlx::pool::PoolConnection; @@ -50,7 +50,7 @@ where .context("Failed to send transaction")?; tracing::info!("CET published with txid {}", txid); - if cfd.handle(CfdStateChangeEvent::CetSent)?.is_none() { + if cfd.handle_cet_sent()?.is_none() { bail!("If we can get the CET we should be able to transition") } @@ -78,7 +78,7 @@ where let mut cfd = db::load_cfd_by_order_id(order_id, conn).await?; - if cfd.handle(CfdStateChangeEvent::Monitor(event))?.is_none() { + if cfd.handle_monitoring_event(event)?.is_none() { // early exit if there was not state change // this is for cases where we are already in a final state return Ok(()); @@ -122,7 +122,7 @@ where .await? .context("Failed to publish commit tx")?; - if cfd.handle(CfdStateChangeEvent::CommitTxSent)?.is_none() { + if cfd.handle_commit_tx_sent()?.is_none() { bail!("If we can get the commit tx we should be able to transition") } @@ -160,8 +160,7 @@ where cfd.role(), )); - let new_state = - try_continue!(cfd.handle(CfdStateChangeEvent::OracleAttestation(attestation))); + let new_state = try_continue!(cfd.handle_oracle_attestation(attestation)); if new_state.is_none() { // if we don't transition to a new state after oracle attestation we ignore the cfd diff --git a/daemon/src/maker_cfd.rs b/daemon/src/maker_cfd.rs index 2b8673f..78f9a8f 100644 --- a/daemon/src/maker_cfd.rs +++ b/daemon/src/maker_cfd.rs @@ -1,9 +1,8 @@ use crate::cfd_actors::{self, append_cfd_state, insert_cfd_and_send_to_feed}; use crate::db::{insert_order, load_cfd_by_order_id, load_order_by_id}; use crate::model::cfd::{ - Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, CollaborativeSettlement, Dlc, Order, - OrderId, Origin, Role, RollOverProposal, SettlementKind, SettlementProposal, UpdateCfdProposal, - UpdateCfdProposals, + Cfd, CfdState, CfdStateCommon, CollaborativeSettlement, Dlc, Order, OrderId, Origin, Role, + RollOverProposal, SettlementKind, SettlementProposal, UpdateCfdProposal, UpdateCfdProposals, }; use crate::model::{Price, TakerId, Timestamp, Usd}; use crate::monitor::MonitorParams; @@ -875,9 +874,11 @@ where let (tx, sig_maker) = dlc.close_transaction(proposal)?; let own_script_pubkey = dlc.script_pubkey_for(cfd.role()); - cfd.handle(CfdStateChangeEvent::ProposalSigned( - CollaborativeSettlement::new(tx.clone(), own_script_pubkey.clone(), proposal.price)?, - ))?; + cfd.handle_proposal_signed(CollaborativeSettlement::new( + tx.clone(), + own_script_pubkey.clone(), + proposal.price, + )?)?; append_cfd_state(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; let spend_tx = dlc.finalize_spend_transaction((tx, sig_maker), sig_taker)?; diff --git a/daemon/src/model/cfd.rs b/daemon/src/model/cfd.rs index b984987..b657c3e 100644 --- a/daemon/src/model/cfd.rs +++ b/daemon/src/model/cfd.rs @@ -698,18 +698,15 @@ impl Cfd { pub const CET_TIMELOCK: u32 = 12; - pub fn handle(&mut self, event: CfdStateChangeEvent) -> Result> { + pub fn handle_monitoring_event(&mut self, event: monitor::Event) -> Result> { use CfdState::*; - // TODO: Display impl - tracing::info!("Cfd state change event {:?}", event); - let order_id = self.order.id; // early exit if already final if let SetupFailed { .. } | Closed { .. } | Refunded { .. } = self.state.clone() { tracing::trace!( - "Ignoring event {:?} because cfd already in state {}", + "Ignoring monitoring event {:?} because cfd already in state {}", event, self.state ); @@ -717,213 +714,80 @@ impl Cfd { } let new_state = match event { - CfdStateChangeEvent::Monitor(event) => match event { - monitor::Event::LockFinality(_) => { - if let PendingOpen { dlc, .. } = self.state.clone() { - CfdState::Open { - common: CfdStateCommon { - transition_timestamp: Timestamp::now()?, - }, - dlc, - attestation: None, - collaborative_close: None, - } - } else if let Open { - dlc, - attestation, - collaborative_close, - .. - } = self.state.clone() - { - CfdState::Open { - common: CfdStateCommon { - transition_timestamp: Timestamp::now()?, - }, - dlc, - attestation, - collaborative_close, - } - } else { - bail!( - "Cannot transition to Open because of unexpected state {}", - self.state - ) - } - } - monitor::Event::CommitFinality(_) => { - let (dlc, attestation) = if let PendingCommit { - dlc, attestation, .. - } = self.state.clone() - { - (dlc, attestation) - } else if let PendingOpen { - dlc, attestation, .. - } - | Open { - dlc, attestation, .. - } = self.state.clone() - { - tracing::debug!(%order_id, "Was in unexpected state {}, jumping ahead to OpenCommitted", self.state); - (dlc, attestation) - } else { - bail!( - "Cannot transition to OpenCommitted because of unexpected state {}", - self.state - ) - }; - - OpenCommitted { + monitor::Event::LockFinality(_) => { + if let PendingOpen { dlc, .. } = self.state.clone() { + CfdState::Open { common: CfdStateCommon { transition_timestamp: Timestamp::now()?, }, dlc, - cet_status: if let Some(attestation) = attestation { - CetStatus::OracleSigned(attestation) - } else { - CetStatus::Unprepared - }, + attestation: None, + collaborative_close: None, } - } - monitor::Event::CloseFinality(_) => { - let collaborative_close = self.collaborative_close().context("No collaborative close after reaching collaborative close finality")?; - - CfdState::closed(Payout::CollaborativeClose(collaborative_close)) - - }, - monitor::Event::CetTimelockExpired(_) => match self.state.clone() { - CfdState::OpenCommitted { - dlc, - cet_status: CetStatus::Unprepared, - .. - } => CfdState::OpenCommitted { - common: CfdStateCommon { - transition_timestamp: Timestamp::now()?, - }, - dlc, - cet_status: CetStatus::TimelockExpired, - }, - CfdState::OpenCommitted { - dlc, - cet_status: CetStatus::OracleSigned(attestation), - .. - } => CfdState::OpenCommitted { + } else if let Open { + dlc, + attestation, + collaborative_close, + .. + } = self.state.clone() + { + CfdState::Open { common: CfdStateCommon { transition_timestamp: Timestamp::now()?, }, dlc, - cet_status: CetStatus::Ready(attestation), - }, - PendingOpen { - dlc, attestation, .. - } - | Open { - dlc, attestation, .. - } - | PendingCommit { - dlc, attestation, .. - } => { - tracing::debug!(%order_id, "Was in unexpected state {}, jumping ahead to OpenCommitted", self.state); - CfdState::OpenCommitted { - common: CfdStateCommon { - transition_timestamp: Timestamp::now()?, - }, - dlc, - cet_status: match attestation { - None => CetStatus::TimelockExpired, - Some(attestation) => CetStatus::Ready(attestation), - }, - } + attestation, + collaborative_close, } - _ => bail!( - "Cannot transition to OpenCommitted because of unexpected state {}", + } else { + bail!( + "Cannot transition to Open because of unexpected state {}", self.state - ), - }, - monitor::Event::RefundTimelockExpired(_) => { - let dlc = if let OpenCommitted { dlc, .. } = self.state.clone() { - dlc - } else if let Open { dlc, .. } | PendingOpen { dlc, .. } = self.state.clone() { - tracing::debug!(%order_id, "Was in unexpected state {}, jumping ahead to PendingRefund", self.state); - dlc - } else { - bail!( - "Cannot transition to PendingRefund because of unexpected state {}", - self.state - ) - }; - - CfdState::must_refund(dlc) - } - monitor::Event::RefundFinality(_) => { - let dlc = self - .dlc() - .context("No dlc available when reaching refund finality")?; - - CfdState::refunded(dlc) + ) } - monitor::Event::CetFinality(_) => { - let attestation = self - .attestation() - .context("No attestation available when reaching CET finality")?; - - CfdState::closed(Payout::Cet(attestation)) - } - monitor::Event::RevokedTransactionFound(_) => { - todo!("Punish bad counterparty") + } + monitor::Event::CommitFinality(_) => { + let (dlc, attestation) = if let PendingCommit { + dlc, attestation, .. + } = self.state.clone() + { + (dlc, attestation) + } else if let PendingOpen { + dlc, attestation, .. } - }, - CfdStateChangeEvent::CommitTxSent => { - let (dlc, attestation ) = match self.state.clone() { - PendingOpen { - dlc, attestation, .. - } => (dlc, attestation), - Open { - dlc, - attestation, - .. - } => (dlc, attestation), - _ => { - bail!( - "Cannot transition to PendingCommit because of unexpected state {}", - self.state - ) - } + | Open { + dlc, attestation, .. + } = self.state.clone() + { + tracing::debug!(%order_id, "Was in unexpected state {}, jumping ahead to OpenCommitted", self.state); + (dlc, attestation) + } else { + bail!( + "Cannot transition to OpenCommitted because of unexpected state {}", + self.state + ) }; - PendingCommit { + OpenCommitted { common: CfdStateCommon { transition_timestamp: Timestamp::now()?, }, dlc, - attestation, + cet_status: if let Some(attestation) = attestation { + CetStatus::OracleSigned(attestation) + } else { + CetStatus::Unprepared + }, } } - CfdStateChangeEvent::OracleAttestation(attestation) => match self.state.clone() { - CfdState::PendingOpen { dlc, .. } => CfdState::PendingOpen { - common: CfdStateCommon { - transition_timestamp: Timestamp::now()?, - }, - dlc, - attestation: Some(attestation), - }, - CfdState::Open { dlc, .. } => CfdState::Open { - common: CfdStateCommon { - transition_timestamp: Timestamp::now()?, - }, - dlc, - attestation: Some(attestation), - collaborative_close: None, - }, - CfdState::PendingCommit { - dlc, - .. - } => CfdState::PendingCommit { - common: CfdStateCommon { - transition_timestamp: Timestamp::now()?, - }, - dlc, - attestation: Some(attestation), - }, + monitor::Event::CloseFinality(_) => { + let collaborative_close = self.collaborative_close().context( + "No collaborative close after reaching collaborative close finality", + )?; + + CfdState::closed(Payout::CollaborativeClose(collaborative_close)) + } + monitor::Event::CetTimelockExpired(_) => match self.state.clone() { CfdState::OpenCommitted { dlc, cet_status: CetStatus::Unprepared, @@ -933,11 +797,11 @@ impl Cfd { transition_timestamp: Timestamp::now()?, }, dlc, - cet_status: CetStatus::OracleSigned(attestation), + cet_status: CetStatus::TimelockExpired, }, CfdState::OpenCommitted { dlc, - cet_status: CetStatus::TimelockExpired, + cet_status: CetStatus::OracleSigned(attestation), .. } => CfdState::OpenCommitted { common: CfdStateCommon { @@ -946,43 +810,239 @@ impl Cfd { dlc, cet_status: CetStatus::Ready(attestation), }, + PendingOpen { + dlc, attestation, .. + } + | Open { + dlc, attestation, .. + } + | PendingCommit { + dlc, attestation, .. + } => { + tracing::debug!(%order_id, "Was in unexpected state {}, jumping ahead to OpenCommitted", self.state); + CfdState::OpenCommitted { + common: CfdStateCommon { + transition_timestamp: Timestamp::now()?, + }, + dlc, + cet_status: match attestation { + None => CetStatus::TimelockExpired, + Some(attestation) => CetStatus::Ready(attestation), + }, + } + } _ => bail!( "Cannot transition to OpenCommitted because of unexpected state {}", self.state ), }, - CfdStateChangeEvent::CetSent => { - let dlc = self.dlc().context("No DLC available after CET was sent")?; + monitor::Event::RefundTimelockExpired(_) => { + let dlc = if let OpenCommitted { dlc, .. } = self.state.clone() { + dlc + } else if let Open { dlc, .. } | PendingOpen { dlc, .. } = self.state.clone() { + tracing::debug!(%order_id, "Was in unexpected state {}, jumping ahead to PendingRefund", self.state); + dlc + } else { + bail!( + "Cannot transition to PendingRefund because of unexpected state {}", + self.state + ) + }; + + CfdState::must_refund(dlc) + } + monitor::Event::RefundFinality(_) => { + let dlc = self + .dlc() + .context("No dlc available when reaching refund finality")?; + + CfdState::refunded(dlc) + } + monitor::Event::CetFinality(_) => { let attestation = self .attestation() - .context("No attestation available after CET was sent")?; + .context("No attestation available when reaching CET finality")?; - CfdState::PendingCet { - common: CfdStateCommon { - transition_timestamp: Timestamp::now()?, - }, - dlc, - attestation, - } + CfdState::closed(Payout::Cet(attestation)) + } + monitor::Event::RevokedTransactionFound(_) => { + todo!("Punish bad counterparty") + } + }; + + self.state = new_state.clone(); + + Ok(Some(new_state)) + } + + pub fn handle_commit_tx_sent(&mut self) -> Result> { + use CfdState::*; + + // early exit if already final + if let SetupFailed { .. } | Closed { .. } | Refunded { .. } = self.state.clone() { + tracing::trace!( + "Ignoring sent commit transaction because cfd already in state {}", + self.state + ); + return Ok(None); + } + let (dlc, attestation) = match self.state.clone() { + PendingOpen { + dlc, attestation, .. + } => (dlc, attestation), + Open { + dlc, attestation, .. + } => (dlc, attestation), + _ => { + bail!( + "Cannot transition to PendingCommit because of unexpected state {}", + self.state + ) + } + }; + + self.state = PendingCommit { + common: CfdStateCommon { + transition_timestamp: Timestamp::now()?, }, - CfdStateChangeEvent::ProposalSigned(collaborative_close) => match self.state.clone() { - CfdState::Open { - common, - dlc, - attestation, - .. - } => CfdState::Open { - common, - dlc, - attestation, - collaborative_close: Some(collaborative_close), + dlc, + attestation, + }; + + Ok(Some(self.state.clone())) + } + + pub fn handle_oracle_attestation( + &mut self, + attestation: Attestation, + ) -> Result> { + use CfdState::*; + + // early exit if already final + if let SetupFailed { .. } | Closed { .. } | Refunded { .. } = self.state.clone() { + tracing::trace!( + "Ignoring oracle attestation because cfd already in state {}", + self.state + ); + return Ok(None); + } + + let new_state = match self.state.clone() { + CfdState::PendingOpen { dlc, .. } => CfdState::PendingOpen { + common: CfdStateCommon { + transition_timestamp: Timestamp::now()?, }, - _ => bail!( - "Cannot add proposed settlement details to state because of unexpected state {}", - self.state - ), + dlc, + attestation: Some(attestation), }, + CfdState::Open { dlc, .. } => CfdState::Open { + common: CfdStateCommon { + transition_timestamp: Timestamp::now()?, + }, + dlc, + attestation: Some(attestation), + collaborative_close: None, + }, + CfdState::PendingCommit { dlc, .. } => CfdState::PendingCommit { + common: CfdStateCommon { + transition_timestamp: Timestamp::now()?, + }, + dlc, + attestation: Some(attestation), + }, + CfdState::OpenCommitted { + dlc, + cet_status: CetStatus::Unprepared, + .. + } => CfdState::OpenCommitted { + common: CfdStateCommon { + transition_timestamp: Timestamp::now()?, + }, + dlc, + cet_status: CetStatus::OracleSigned(attestation), + }, + CfdState::OpenCommitted { + dlc, + cet_status: CetStatus::TimelockExpired, + .. + } => CfdState::OpenCommitted { + common: CfdStateCommon { + transition_timestamp: Timestamp::now()?, + }, + dlc, + cet_status: CetStatus::Ready(attestation), + }, + _ => bail!( + "Cannot transition to OpenCommitted because of unexpected state {}", + self.state + ), + }; + + self.state = new_state.clone(); + + Ok(Some(new_state)) + } + + pub fn handle_cet_sent(&mut self) -> Result> { + use CfdState::*; + + // early exit if already final + if let SetupFailed { .. } | Closed { .. } | Refunded { .. } = self.state.clone() { + tracing::trace!( + "Ignoring pending CET because cfd already in state {}", + self.state + ); + return Ok(None); + } + + let dlc = self.dlc().context("No DLC available after CET was sent")?; + let attestation = self + .attestation() + .context("No attestation available after CET was sent")?; + + self.state = CfdState::PendingCet { + common: CfdStateCommon { + transition_timestamp: Timestamp::now()?, + }, + dlc, + attestation, + }; + + Ok(Some(self.state.clone())) + } + + pub fn handle_proposal_signed( + &mut self, + collaborative_close: CollaborativeSettlement, + ) -> Result> { + use CfdState::*; + + // early exit if already final + if let SetupFailed { .. } | Closed { .. } | Refunded { .. } = self.state.clone() { + tracing::trace!( + "Ignoring collaborative settlement because cfd already in state {}", + self.state + ); + return Ok(None); + } + + let new_state = match self.state.clone() { + CfdState::Open { + common, + dlc, + attestation, + .. + } => CfdState::Open { + common, + dlc, + attestation, + collaborative_close: Some(collaborative_close), + }, + _ => bail!( + "Cannot add proposed settlement details to state because of unexpected state {}", + self.state + ), }; self.state = new_state.clone(); @@ -1282,18 +1342,6 @@ pub struct NotReadyYet { cet_status: CetStatus, } -#[derive(Debug, Clone)] -pub enum CfdStateChangeEvent { - // 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, - OracleAttestation(Attestation), - CetSent, - /// Settlement proposal was signed by the taker and sent to the maker - ProposalSigned(CollaborativeSettlement), -} - pub trait AsBlocks { /// Calculates the duration in Bitcoin blocks. /// diff --git a/daemon/src/taker_cfd.rs b/daemon/src/taker_cfd.rs index 3ac2ce1..bc7f9a8 100644 --- a/daemon/src/taker_cfd.rs +++ b/daemon/src/taker_cfd.rs @@ -1,9 +1,8 @@ use crate::cfd_actors::{self, append_cfd_state, insert_cfd_and_send_to_feed}; use crate::db::{insert_order, load_cfd_by_order_id, load_order_by_id}; use crate::model::cfd::{ - Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, CollaborativeSettlement, Dlc, Order, - OrderId, Origin, Role, RollOverProposal, SettlementKind, SettlementProposal, UpdateCfdProposal, - UpdateCfdProposals, + Cfd, CfdState, CfdStateCommon, CollaborativeSettlement, Dlc, Order, OrderId, Origin, Role, + RollOverProposal, SettlementKind, SettlementProposal, UpdateCfdProposal, UpdateCfdProposals, }; use crate::model::{BitMexPriceEventId, Price, Timestamp, Usd}; use crate::monitor::{self, MonitorParams}; @@ -660,13 +659,11 @@ where sig_taker, })?; - cfd.handle(CfdStateChangeEvent::ProposalSigned( - CollaborativeSettlement::new( - tx.clone(), - dlc.script_pubkey_for(cfd.role()), - proposal.price, - )?, - ))?; + cfd.handle_proposal_signed(CollaborativeSettlement::new( + tx.clone(), + dlc.script_pubkey_for(cfd.role()), + proposal.price, + )?)?; append_cfd_state(&cfd, &mut conn, &self.cfd_feed_actor_inbox).await?; self.remove_pending_proposal(&order_id)?;