diff --git a/coordinator/src/node/liquidated_positions.rs b/coordinator/src/node/liquidated_positions.rs index 6a5b20490..353f57698 100644 --- a/coordinator/src/node/liquidated_positions.rs +++ b/coordinator/src/node/liquidated_positions.rs @@ -106,7 +106,8 @@ async fn check_if_positions_need_to_get_liquidated( // liquidation. match node .inner - .is_signed_dlc_channel_confirmed_by_trader_id(position.trader) + .check_if_signed_channel_is_confirmed(position.trader) + .await { Ok(true) => { tracing::debug!(trader_id=%position.trader, "Traders dlc channel is confirmed. Continuing with the liquidation"); diff --git a/coordinator/src/trade/mod.rs b/coordinator/src/trade/mod.rs index bb01fe525..5cd23f288 100644 --- a/coordinator/src/trade/mod.rs +++ b/coordinator/src/trade/mod.rs @@ -804,8 +804,13 @@ impl TradeExecutor { trade_params: &TradeParams, resize_action: ResizeAction, ) -> Result<()> { - if !self.node.inner.is_dlc_channel_confirmed(&dlc_channel_id)? { - bail!("Underlying DLC channel not yet confirmed"); + if !self + .node + .inner + .check_if_signed_channel_is_confirmed(position.trader) + .await? + { + bail!("Underlying DLC channel not yet confirmed."); } let peer_id = trade_params.pubkey; @@ -1045,8 +1050,13 @@ impl TradeExecutor { trade_params: &TradeParams, channel_id: DlcChannelId, ) -> Result<()> { - if !self.node.inner.is_dlc_channel_confirmed(&channel_id)? { - bail!("Underlying DLC channel not yet confirmed"); + if !self + .node + .inner + .check_if_signed_channel_is_confirmed(position.trader) + .await? + { + bail!("Underlying DLC channel not yet confirmed."); } let closing_price = trade_params.average_execution_price(); diff --git a/crates/xxi-node/src/node/dlc_channel.rs b/crates/xxi-node/src/node/dlc_channel.rs index bc937891a..f41de394a 100644 --- a/crates/xxi-node/src/node/dlc_channel.rs +++ b/crates/xxi-node/src/node/dlc_channel.rs @@ -581,28 +581,40 @@ impl Result { - let signed_channel = self.get_signed_channel_by_trader_id(trader_id)?; - self.is_dlc_channel_confirmed(&signed_channel.channel_id) + /// Checks if the underlying contract of the signed channel has been confirmed. If not a + /// periodic check is run to ensure we are on the latest state and return the corresponding + /// response. + pub async fn check_if_signed_channel_is_confirmed(&self, trader: PublicKey) -> Result { + let signed_channel = self.get_signed_channel_by_trader_id(trader)?; + if !self.is_dlc_channel_confirmed(&signed_channel.channel_id)? { + self.sync_on_chain_wallet().await?; + spawn_blocking({ + let dlc_manager = self.dlc_manager.clone(); + move || dlc_manager.periodic_check() + }) + .await + .expect("task to complete")?; + + return self.is_dlc_channel_confirmed(&signed_channel.channel_id); + } + + Ok(true) } - // TODO: This API could return the number of required confirmations + the number of current - // confirmations. - pub fn is_dlc_channel_confirmed(&self, dlc_channel_id: &DlcChannelId) -> Result { + fn is_contract_confirmed(&self, contract_id: &ContractId) -> Result { + let contract = self + .get_contract_by_id(contract_id)? + .context("Could not find contract for signed channel in state Established.")?; + Ok(matches!(contract, Contract::Confirmed { .. })) + } + + fn is_dlc_channel_confirmed(&self, dlc_channel_id: &DlcChannelId) -> Result { let channel = self.get_dlc_channel_by_id(dlc_channel_id)?; let confirmed = match channel { Channel::Signed(signed_channel) => match signed_channel.state { SignedChannelState::Established { signed_contract_id, .. - } => { - let contract = self.get_contract_by_id(&signed_contract_id)?.context( - "Could not find contract for signed channel in state Established.", - )?; - matches!(contract, Contract::Confirmed { .. }) - } + } => self.is_contract_confirmed(&signed_contract_id)?, _ => true, }, Channel::Offered(_) diff --git a/mobile/native/src/dlc/mod.rs b/mobile/native/src/dlc/mod.rs index 56e914ee1..a9a1b13f6 100644 --- a/mobile/native/src/dlc/mod.rs +++ b/mobile/native/src/dlc/mod.rs @@ -907,18 +907,17 @@ pub fn delete_dlc_channel(dlc_channel_id: &DlcChannelId) -> Result<()> { Ok(()) } -pub fn is_dlc_channel_confirmed() -> Result { +pub async fn check_if_signed_channel_is_confirmed() -> Result { let node = match state::try_get_node() { Some(node) => node, None => return Ok(false), }; - let dlc_channel = match get_signed_dlc_channel()? { - Some(dlc_channel) => dlc_channel, - None => return Ok(false), - }; + let counterparty = config::get_coordinator_info().pubkey; - node.inner.is_dlc_channel_confirmed(&dlc_channel.channel_id) + node.inner + .check_if_signed_channel_is_confirmed(counterparty) + .await } pub fn get_fee_rate_for_target(target: ConfirmationTarget) -> FeeRate { diff --git a/mobile/native/src/trade/order/handler.rs b/mobile/native/src/trade/order/handler.rs index cbda52730..ea4a808df 100644 --- a/mobile/native/src/trade/order/handler.rs +++ b/mobile/native/src/trade/order/handler.rs @@ -3,7 +3,7 @@ use crate::db; use crate::db::get_order_in_filling; use crate::db::maybe_get_open_orders; use crate::dlc; -use crate::dlc::is_dlc_channel_confirmed; +use crate::dlc::check_if_signed_channel_is_confirmed; use crate::event; use crate::event::BackgroundTask; use crate::event::EventInternal; @@ -42,11 +42,8 @@ pub enum SubmitOrderError { /// Generic problem related to the storage layer (sqlite, sled). #[error("Storage failed: {0}")] Storage(anyhow::Error), - #[error("DLC channel not yet confirmed: has {current_confirmations} confirmations, needs {required_confirmations}")] - UnconfirmedChannel { - current_confirmations: u64, - required_confirmations: u64, - }, + #[error("DLC channel not yet confirmed")] + UnconfirmedChannel, #[error("DLC Channel in invalid state: expected {expected_channel_state}, got {actual_channel_state}")] InvalidChannelState { expected_channel_state: String, @@ -88,7 +85,7 @@ pub async fn submit_order_internal( order: Order, channel_opening_params: Option, ) -> Result { - check_channel_state()?; + check_channel_state().await?; // Having an order in `Filling` should mean that the subchannel is in the midst of an update. // Since we currently only support one subchannel per app, it does not make sense to start @@ -142,7 +139,7 @@ pub async fn submit_order_internal( /// 1. Open position, but no channel in state [`SignedChannelState::Established`] /// 2. Open position and not enough confirmations on the funding txid. /// 3. No position and a channel which is not in state [`SignedChannelState::Settled`] -fn check_channel_state() -> Result<(), SubmitOrderError> { +async fn check_channel_state() -> Result<(), SubmitOrderError> { let channel = dlc::get_signed_dlc_channel().map_err(SubmitOrderError::Storage)?; if position::handler::get_positions() @@ -171,12 +168,11 @@ fn check_channel_state() -> Result<(), SubmitOrderError> { // If we have an open position, we should not allow any further trading until the current // DLC channel is confirmed on-chain. Otherwise we can run into pesky DLC protocol // failures. - if !is_dlc_channel_confirmed().map_err(SubmitOrderError::Storage)? { - // TODO: Do not hard-code confirmations. - return Err(SubmitOrderError::UnconfirmedChannel { - current_confirmations: 0, - required_confirmations: 1, - }); + if !check_if_signed_channel_is_confirmed() + .await + .map_err(SubmitOrderError::Storage)? + { + return Err(SubmitOrderError::UnconfirmedChannel); } } else { match channel { diff --git a/webapp/src/api.rs b/webapp/src/api.rs index 34e00009b..94c85c8fb 100644 --- a/webapp/src/api.rs +++ b/webapp/src/api.rs @@ -18,7 +18,6 @@ use native::api::WalletHistoryItemType; use native::calculations::calculate_pnl; use native::channel_trade_constraints; use native::dlc; -use native::dlc::is_dlc_channel_confirmed; use native::trade::order::FailureReason; use native::trade::order::InvalidSubchannelOffer; use rust_decimal::prelude::ToPrimitive; @@ -358,7 +357,7 @@ pub async fn post_new_order(params: Json) -> Result