From d5fefeb5984ca046860a758d86f3caad8182631f Mon Sep 17 00:00:00 2001 From: Richard Holzeis Date: Wed, 8 May 2024 08:42:35 +0200 Subject: [PATCH 1/9] chore: Add unique reference id to close channel Before we were reusing the same reference id of the channel. --- crates/xxi-node/src/node/dlc_channel.rs | 22 ++++++++++++++++------ crates/xxi-node/src/node/mod.rs | 15 +++++++++++++++ crates/xxi-node/src/tests/dlc_channel.rs | 2 +- crates/xxi-node/src/tests/mod.rs | 16 ---------------- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/crates/xxi-node/src/node/dlc_channel.rs b/crates/xxi-node/src/node/dlc_channel.rs index ab15d95ca..d4536bbfa 100644 --- a/crates/xxi-node/src/node/dlc_channel.rs +++ b/crates/xxi-node/src/node/dlc_channel.rs @@ -10,6 +10,7 @@ use crate::message_handler::TenTenOneRolloverOffer; use crate::message_handler::TenTenOneSettleAccept; use crate::message_handler::TenTenOneSettleOffer; use crate::node::event::NodeEvent; +use crate::node::new_reference_id; use crate::node::Node; use crate::node::Storage as LnDlcStorage; use crate::on_chain_wallet::BdkStorage; @@ -152,17 +153,22 @@ impl Result<()> { + fn force_close_dlc_channel( + &self, + channel: SignedChannel, + reference_id: Option, + ) -> Result<()> { let channel_id = channel.channel_id; let channel_id_hex = hex::encode(channel_id); @@ -172,12 +178,16 @@ impl Result<()> { + async fn propose_dlc_channel_collaborative_close( + &self, + channel: SignedChannel, + reference_id: Option, + ) -> Result<()> { let counterparty = channel.counter_party; match channel.state { @@ -196,7 +206,7 @@ impl { @@ -425,3 +427,16 @@ impl Display for NodeInfo { format!("{scheme}://{}@{}", self.pubkey, self.address).fmt(f) } } + +pub fn new_reference_id() -> ReferenceId { + let uuid = Uuid::new_v4(); + let hex = hex::encode(uuid.as_simple().as_ref()); + let bytes = hex.as_bytes(); + + debug_assert!(bytes.len() == 32, "length must be exactly 32 bytes"); + + let mut array = [0u8; 32]; + array.copy_from_slice(bytes); + + array +} diff --git a/crates/xxi-node/src/tests/dlc_channel.rs b/crates/xxi-node/src/tests/dlc_channel.rs index 1101adb4b..8ac9fb8a7 100644 --- a/crates/xxi-node/src/tests/dlc_channel.rs +++ b/crates/xxi-node/src/tests/dlc_channel.rs @@ -1,6 +1,7 @@ use crate::bitcoin_conversion::to_secp_pk_29; use crate::node::dlc_channel::estimated_dlc_channel_fee_reserve; use crate::node::event::NodeEvent; +use crate::node::new_reference_id; use crate::node::InMemoryStore; use crate::node::Node; use crate::node::RunningNode; @@ -11,7 +12,6 @@ use crate::tests::dummy_contract_input; use crate::tests::dummy_filled_with; use crate::tests::dummy_order; use crate::tests::init_tracing; -use crate::tests::new_reference_id; use crate::tests::wait_until; use bitcoin::Amount; use dlc_manager::channel::signed_channel::SignedChannel; diff --git a/crates/xxi-node/src/tests/mod.rs b/crates/xxi-node/src/tests/mod.rs index f815e78e9..5b3acfe0f 100644 --- a/crates/xxi-node/src/tests/mod.rs +++ b/crates/xxi-node/src/tests/mod.rs @@ -18,7 +18,6 @@ use anyhow::Result; use bitcoin::secp256k1::XOnlyPublicKey; use bitcoin::Amount; use bitcoin::Network; -use bitcoin_old::hashes::hex::ToHex; use dlc_manager::contract::contract_input::ContractInput; use dlc_manager::contract::contract_input::ContractInputInfo; use dlc_manager::contract::contract_input::OracleInput; @@ -30,7 +29,6 @@ use dlc_manager::payout_curve::PayoutPoint; use dlc_manager::payout_curve::PolynomialPayoutCurvePiece; use dlc_manager::payout_curve::RoundingInterval; use dlc_manager::payout_curve::RoundingIntervals; -use dlc_manager::ReferenceId; use futures::Future; use rand::distributions::Alphanumeric; use rand::thread_rng; @@ -48,7 +46,6 @@ use std::sync::Arc; use std::sync::Once; use std::time::Duration; use time::OffsetDateTime; -use uuid::Uuid; mod bitcoind; mod dlc_channel; @@ -437,19 +434,6 @@ fn dummy_contract_input( } } -pub fn new_reference_id() -> ReferenceId { - let uuid = Uuid::new_v4(); - let hex = uuid.as_simple().to_hex(); - let bytes = hex.as_bytes(); - - debug_assert!(bytes.len() == 32, "length must be exactly 32 bytes"); - - let mut array = [0u8; 32]; - array.copy_from_slice(bytes); - - array -} - pub fn dummy_order() -> commons::Order { commons::Order { id: Default::default(), From ebd53b02a414acd28cc1f4338f2e67a36e90dc5d Mon Sep 17 00:00:00 2001 From: Richard Holzeis Date: Wed, 8 May 2024 14:03:29 +0200 Subject: [PATCH 2/9] chore: Optional contract id Closing a dlc channel is unrelated to any contract, hence the contract id can't be mandatory. --- .../down.sql | 1 + .../up.sql | 1 + coordinator/src/db/dlc_protocols.rs | 14 ++++++++------ coordinator/src/dlc_protocol.rs | 17 ++++++++--------- coordinator/src/node/rollover.rs | 2 +- coordinator/src/position/mod.rs | 3 ++- coordinator/src/schema.rs | 2 +- coordinator/src/trade/mod.rs | 8 ++++---- 8 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 coordinator/migrations/2024-05-08-104311_nullable_contract_id_on_dlc_protocols/down.sql create mode 100644 coordinator/migrations/2024-05-08-104311_nullable_contract_id_on_dlc_protocols/up.sql diff --git a/coordinator/migrations/2024-05-08-104311_nullable_contract_id_on_dlc_protocols/down.sql b/coordinator/migrations/2024-05-08-104311_nullable_contract_id_on_dlc_protocols/down.sql new file mode 100644 index 000000000..03115ba56 --- /dev/null +++ b/coordinator/migrations/2024-05-08-104311_nullable_contract_id_on_dlc_protocols/down.sql @@ -0,0 +1 @@ +ALTER TABLE dlc_protocols ALTER COLUMN contract_id SET NOT NULL; diff --git a/coordinator/migrations/2024-05-08-104311_nullable_contract_id_on_dlc_protocols/up.sql b/coordinator/migrations/2024-05-08-104311_nullable_contract_id_on_dlc_protocols/up.sql new file mode 100644 index 000000000..4aac069ec --- /dev/null +++ b/coordinator/migrations/2024-05-08-104311_nullable_contract_id_on_dlc_protocols/up.sql @@ -0,0 +1 @@ +ALTER TABLE dlc_protocols ALTER COLUMN contract_id DROP NOT NULL; diff --git a/coordinator/src/db/dlc_protocols.rs b/coordinator/src/db/dlc_protocols.rs index 799f17fa7..fb2d2b92c 100644 --- a/coordinator/src/db/dlc_protocols.rs +++ b/coordinator/src/db/dlc_protocols.rs @@ -68,7 +68,7 @@ pub(crate) struct DlcProtocol { pub protocol_id: Uuid, pub previous_protocol_id: Option, pub channel_id: String, - pub contract_id: String, + pub contract_id: Option, pub protocol_state: DlcProtocolState, pub trader_pubkey: String, pub timestamp: OffsetDateTime, @@ -115,7 +115,9 @@ pub(crate) fn get_dlc_protocol( id: dlc_protocol.protocol_id.into(), timestamp: dlc_protocol.timestamp, channel_id: DlcChannelId::from_hex(&dlc_protocol.channel_id).expect("valid dlc channel id"), - contract_id: ContractId::from_hex(&dlc_protocol.contract_id).expect("valid contract id"), + contract_id: dlc_protocol + .contract_id + .map(|cid| ContractId::from_hex(cid).expect("valid contract id")), trader: PublicKey::from_str(&dlc_protocol.trader_pubkey).expect("valid public key"), protocol_state: dlc_protocol.protocol_state.into(), protocol_type, @@ -143,14 +145,14 @@ pub(crate) fn set_dlc_protocol_state_to_failed( pub(crate) fn set_dlc_protocol_state_to_success( conn: &mut PgConnection, protocol_id: ProtocolId, - contract_id: &ContractId, + contract_id: Option<&ContractId>, channel_id: &DlcChannelId, ) -> QueryResult<()> { let affected_rows = diesel::update(dlc_protocols::table) .filter(dlc_protocols::protocol_id.eq(protocol_id.to_uuid())) .set(( dlc_protocols::protocol_state.eq(DlcProtocolState::Success), - dlc_protocols::contract_id.eq(hex::encode(contract_id)), + dlc_protocols::contract_id.eq(contract_id.map(hex::encode)), dlc_protocols::channel_id.eq(hex::encode(channel_id)), )) .execute(conn)?; @@ -166,7 +168,7 @@ pub(crate) fn create( conn: &mut PgConnection, protocol_id: ProtocolId, previous_protocol_id: Option, - contract_id: &ContractId, + contract_id: Option<&ContractId>, channel_id: &DlcChannelId, protocol_type: dlc_protocol::DlcProtocolType, trader: &PublicKey, @@ -175,7 +177,7 @@ pub(crate) fn create( .values(&( dlc_protocols::protocol_id.eq(protocol_id.to_uuid()), dlc_protocols::previous_protocol_id.eq(previous_protocol_id.map(|ppid| ppid.to_uuid())), - dlc_protocols::contract_id.eq(hex::encode(contract_id)), + dlc_protocols::contract_id.eq(contract_id.map(hex::encode)), dlc_protocols::channel_id.eq(hex::encode(channel_id)), dlc_protocols::protocol_state.eq(DlcProtocolState::Pending), dlc_protocols::trader_pubkey.eq(trader.to_string()), diff --git a/coordinator/src/dlc_protocol.rs b/coordinator/src/dlc_protocol.rs index 3ab3f5a33..db5afbf25 100644 --- a/coordinator/src/dlc_protocol.rs +++ b/coordinator/src/dlc_protocol.rs @@ -106,7 +106,7 @@ pub struct DlcProtocol { pub id: ProtocolId, pub timestamp: OffsetDateTime, pub channel_id: DlcChannelId, - pub contract_id: ContractId, + pub contract_id: Option, pub trader: PublicKey, pub protocol_state: DlcProtocolState, pub protocol_type: DlcProtocolType, @@ -247,7 +247,7 @@ impl DlcProtocolExecutor { &self, protocol_id: ProtocolId, previous_protocol_id: Option, - contract_id: &ContractId, + contract_id: Option<&ContractId>, channel_id: &DlcChannelId, protocol_type: DlcProtocolType, ) -> Result<()> { @@ -325,15 +325,14 @@ impl DlcProtocolExecutor { ) } DlcProtocolType::Settle { trade_params } => { - let settled_contract = &dlc_protocol.contract_id; - + let settled_contract = dlc_protocol.contract_id; self.finish_close_trade_dlc_protocol( conn, trade_params, protocol_id, // If the contract got settled, we do not get a new contract id, hence we // copy the contract id of the settled contract. - settled_contract, + settled_contract.as_ref(), channel_id, ) } @@ -395,7 +394,7 @@ impl DlcProtocolExecutor { conn: &mut PgConnection, trade_params: &TradeParams, protocol_id: ProtocolId, - settled_contract: &ContractId, + settled_contract: Option<&ContractId>, channel_id: &DlcChannelId, ) -> QueryResult<()> { db::dlc_protocols::set_dlc_protocol_state_to_success( @@ -501,7 +500,7 @@ impl DlcProtocolExecutor { db::dlc_protocols::set_dlc_protocol_state_to_success( conn, protocol_id, - contract_id, + Some(contract_id), channel_id, )?; @@ -547,7 +546,7 @@ impl DlcProtocolExecutor { db::dlc_protocols::set_dlc_protocol_state_to_success( conn, protocol_id, - contract_id, + Some(contract_id), channel_id, )?; @@ -594,7 +593,7 @@ impl DlcProtocolExecutor { db::dlc_protocols::set_dlc_protocol_state_to_success( conn, protocol_id, - contract_id, + Some(contract_id), channel_id, )?; diff --git a/coordinator/src/node/rollover.rs b/coordinator/src/node/rollover.rs index 608f31c99..0636a1fa0 100644 --- a/coordinator/src/node/rollover.rs +++ b/coordinator/src/node/rollover.rs @@ -262,7 +262,7 @@ impl Node { protocol_executor.start_dlc_protocol( protocol_id, previous_id, - &contract_id, + Some(&contract_id), dlc_channel_id, DlcProtocolType::Rollover { trader: rollover.counterparty_pubkey, diff --git a/coordinator/src/position/mod.rs b/coordinator/src/position/mod.rs index ba96dc7b2..f97d67edf 100644 --- a/coordinator/src/position/mod.rs +++ b/coordinator/src/position/mod.rs @@ -92,10 +92,11 @@ impl Node { // confirmations. DlcChannelEvent::Closed(_) | DlcChannelEvent::CounterClosed(_) => { let dlc_protocol = db::dlc_protocols::get_dlc_protocol(&mut conn, protocol_id)?; + let contract_id = &dlc_protocol.contract_id.context("Missing contract id")?; let trader_id = dlc_protocol.trader; let contract = self .inner - .get_contract_by_id(&dlc_protocol.contract_id)? + .get_contract_by_id(contract_id)? .context("Missing contract")?; let position = db::positions::Position::get_position_by_trader( diff --git a/coordinator/src/schema.rs b/coordinator/src/schema.rs index 4453c9803..e161360d2 100644 --- a/coordinator/src/schema.rs +++ b/coordinator/src/schema.rs @@ -203,7 +203,7 @@ diesel::table! { protocol_id -> Uuid, previous_protocol_id -> Nullable, channel_id -> Text, - contract_id -> Text, + contract_id -> Nullable, protocol_state -> ProtocolStateType, trader_pubkey -> Text, timestamp -> Timestamptz, diff --git a/coordinator/src/trade/mod.rs b/coordinator/src/trade/mod.rs index 2397a00b9..cd13b963b 100644 --- a/coordinator/src/trade/mod.rs +++ b/coordinator/src/trade/mod.rs @@ -378,7 +378,7 @@ impl TradeExecutor { protocol_executor.start_dlc_protocol( protocol_id, None, - &temporary_contract_id, + Some(&temporary_contract_id), &temporary_channel_id, DlcProtocolType::open_channel(trade_params, protocol_id), )?; @@ -557,7 +557,7 @@ impl TradeExecutor { protocol_executor.start_dlc_protocol( protocol_id, previous_id, - &temporary_contract_id, + Some(&temporary_contract_id), &channel.get_id(), DlcProtocolType::open_position(trade_params, protocol_id), )?; @@ -730,7 +730,7 @@ impl TradeExecutor { protocol_executor.start_dlc_protocol( protocol_id, previous_id, - &temporary_contract_id, + Some(&temporary_contract_id), &channel.get_id(), DlcProtocolType::resize_position(trade_params, protocol_id, realized_pnl), )?; @@ -886,7 +886,7 @@ impl TradeExecutor { protocol_executor.start_dlc_protocol( protocol_id, previous_id, - &contract_id, + Some(&contract_id), &channel.get_id(), DlcProtocolType::settle(trade_params, protocol_id), )?; From c0f2c08854308011eb1955760a6f66de9577234c Mon Sep 17 00:00:00 2001 From: Richard Holzeis Date: Wed, 8 May 2024 15:34:41 +0200 Subject: [PATCH 3/9] feat: Add collab close dlc protocol record --- coordinator/src/bin/coordinator.rs | 3 +- coordinator/src/db/dlc_protocols.rs | 5 +- coordinator/src/dlc_protocol.rs | 19 +- coordinator/src/node.rs | 27 ++- coordinator/src/node/channel.rs | 234 +++++++++++++++++++++++- coordinator/src/position/mod.rs | 219 ---------------------- coordinator/src/routes/admin.rs | 1 - crates/xxi-node/src/node/dlc_channel.rs | 4 +- mobile/native/src/dlc/mod.rs | 4 +- 9 files changed, 282 insertions(+), 234 deletions(-) diff --git a/coordinator/src/bin/coordinator.rs b/coordinator/src/bin/coordinator.rs index 21e8e0d0f..85427e513 100644 --- a/coordinator/src/bin/coordinator.rs +++ b/coordinator/src/bin/coordinator.rs @@ -285,8 +285,7 @@ async fn main() -> Result<()> { network, ); - node.spawn_shadow_dlc_channels_task(); - node.spawn_watch_closing_channels(); + node.spawn_watch_dlc_channel_events_task(); tokio::spawn({ let node = node.clone(); diff --git a/coordinator/src/db/dlc_protocols.rs b/coordinator/src/db/dlc_protocols.rs index fb2d2b92c..bb6ffda3f 100644 --- a/coordinator/src/db/dlc_protocols.rs +++ b/coordinator/src/db/dlc_protocols.rs @@ -113,6 +113,9 @@ pub(crate) fn get_dlc_protocol( let protocol = dlc_protocol::DlcProtocol { id: dlc_protocol.protocol_id.into(), + previous_id: dlc_protocol + .previous_protocol_id + .map(|previous_id| previous_id.into()), timestamp: dlc_protocol.timestamp, channel_id: DlcChannelId::from_hex(&dlc_protocol.channel_id).expect("valid dlc channel id"), contract_id: dlc_protocol @@ -132,7 +135,7 @@ pub(crate) fn set_dlc_protocol_state_to_failed( ) -> QueryResult<()> { let affected_rows = diesel::update(dlc_protocols::table) .filter(dlc_protocols::protocol_id.eq(protocol_id.to_uuid())) - .set((dlc_protocols::protocol_state.eq(DlcProtocolState::Failed),)) + .set(dlc_protocols::protocol_state.eq(DlcProtocolState::Failed)) .execute(conn)?; if affected_rows == 0 { diff --git a/coordinator/src/dlc_protocol.rs b/coordinator/src/dlc_protocol.rs index db5afbf25..45ad097e5 100644 --- a/coordinator/src/dlc_protocol.rs +++ b/coordinator/src/dlc_protocol.rs @@ -104,6 +104,7 @@ impl From for Uuid { pub struct DlcProtocol { pub id: ProtocolId, + pub previous_id: Option, pub timestamp: OffsetDateTime, pub channel_id: DlcChannelId, pub contract_id: Option, @@ -348,7 +349,10 @@ impl DlcProtocolExecutor { channel_id, ) } - DlcProtocolType::Close { .. } | DlcProtocolType::ForceClose { .. } => { + DlcProtocolType::Close { .. } => { + self.finish_close_channel_dlc_protocol(conn, trader_id, protocol_id, channel_id) + } + DlcProtocolType::ForceClose { .. } => { debug_assert!(false, "Finishing unexpected dlc protocol types"); Ok(()) } @@ -600,6 +604,19 @@ impl DlcProtocolExecutor { db::positions::Position::set_position_to_open(conn, trader.to_string(), *contract_id)?; Ok(()) } + + /// Completes the rollover dlc protocol as successful and updates the 10101 meta data + /// accordingly in a single database transaction. + fn finish_close_channel_dlc_protocol( + &self, + conn: &mut PgConnection, + trader: &PublicKey, + protocol_id: ProtocolId, + channel_id: &DlcChannelId, + ) -> QueryResult<()> { + tracing::debug!(%trader, %protocol_id, "Finalizing channel close"); + db::dlc_protocols::set_dlc_protocol_state_to_success(conn, protocol_id, None, channel_id) + } } #[cfg(test)] diff --git a/coordinator/src/node.rs b/coordinator/src/node.rs index 2750902a9..d0773f226 100644 --- a/coordinator/src/node.rs +++ b/coordinator/src/node.rs @@ -1,5 +1,6 @@ use crate::db; use crate::dlc_protocol; +use crate::dlc_protocol::DlcProtocolType; use crate::dlc_protocol::ProtocolId; use crate::message::OrderbookMessage; use crate::node::storage::NodeStorage; @@ -74,7 +75,7 @@ pub struct Node { _running: Arc, pub pool: Pool>, pub settings: Arc>, - tx_position_feed: Sender, + pub tx_position_feed: Sender, trade_notifier: mpsc::Sender, } @@ -224,8 +225,6 @@ impl Node { "Received message" ); - self.verify_collab_close_offer(&node_id, msg)?; - let inbound_msg = { let mut conn = self.pool.get()?; let serialized_inbound_message = SerializedDlcMessage::try_from(msg)?; @@ -239,6 +238,8 @@ impl Node { } }; + self.verify_collab_close_offer(&node_id, msg)?; + let resp = self .inner .process_tentenone_message(msg.clone(), node_id) @@ -364,7 +365,7 @@ impl Node { tracing::info!( channel_id = hex::encode(close_offer.channel_id), node_id = node_id.to_string(), - "Received an offer to collaboratively close a channel" + "Accepting offer to collaboratively close a channel" ); self.inner @@ -576,6 +577,24 @@ impl Node { _ => {} }; + let protocol_id = close_offer.reference_id.context("Missing reference id")?; + let protocol_id = ProtocolId::try_from(protocol_id)?; + + let previous_id = channel.get_reference_id(); + let previous_id = match previous_id { + Some(previous_id) => Some(ProtocolId::try_from(previous_id)?), + None => None, + }; + + let protocol_executor = dlc_protocol::DlcProtocolExecutor::new(self.pool.clone()); + protocol_executor.start_dlc_protocol( + protocol_id, + previous_id, + None, + &channel.get_id(), + DlcProtocolType::Close { trader: *node_id }, + )?; + Ok(()) } } diff --git a/coordinator/src/node/channel.rs b/coordinator/src/node/channel.rs index 657233c66..c3a3c6ace 100644 --- a/coordinator/src/node/channel.rs +++ b/coordinator/src/node/channel.rs @@ -1,12 +1,20 @@ use crate::db; +use crate::dlc_protocol; use crate::dlc_protocol::DlcProtocolType; use crate::dlc_protocol::ProtocolId; use crate::node::Node; +use crate::position::models::PositionState; use anyhow::bail; +use anyhow::Context; use anyhow::Result; use bitcoin::secp256k1::PublicKey; use bitcoin::Amount; +use bitcoin::ScriptBuf; use bitcoin::Txid; +use bitcoin_old::Transaction; +use diesel::r2d2::ConnectionManager; +use diesel::r2d2::PooledConnection; +use diesel::PgConnection; use dlc_manager::channel::signed_channel::SignedChannel; use dlc_manager::channel::signed_channel::SignedChannelState; use dlc_manager::channel::Channel; @@ -14,7 +22,11 @@ use dlc_manager::channel::ClosedChannel; use dlc_manager::channel::ClosedPunishedChannel; use dlc_manager::channel::ClosingChannel; use dlc_manager::channel::SettledClosingChannel; +use dlc_manager::contract::ClosedContract; +use dlc_manager::contract::Contract; +use dlc_manager::contract::PreClosedContract; use dlc_manager::DlcChannelId; +use rust_decimal::Decimal; use time::OffsetDateTime; use tokio::sync::broadcast::error::RecvError; use xxi_node::bitcoin_conversion::to_secp_pk_30; @@ -50,7 +62,39 @@ pub struct DlcChannel { } impl Node { - pub fn spawn_shadow_dlc_channels_task(&self) { + pub async fn close_dlc_channel( + &self, + channel_id: DlcChannelId, + is_force_close: bool, + ) -> Result<()> { + let channel = self.inner.get_dlc_channel_by_id(&channel_id)?; + let previous_id = channel.get_reference_id(); + let previous_id = match previous_id { + Some(previous_id) => Some(ProtocolId::try_from(previous_id)?), + None => None, + }; + + let reference_id = self + .inner + .close_dlc_channel(channel_id, is_force_close) + .await?; + let protocol_id = ProtocolId::try_from(reference_id)?; + + let protocol_executor = dlc_protocol::DlcProtocolExecutor::new(self.pool.clone()); + protocol_executor.start_dlc_protocol( + protocol_id, + previous_id, + None, + &channel.get_id(), + DlcProtocolType::Close { + trader: to_secp_pk_30(channel.get_counter_party_id()), + }, + )?; + + Ok(()) + } + + pub fn spawn_watch_dlc_channel_events_task(&self) { let mut receiver = self.inner.event_handler.subscribe(); tokio::spawn({ @@ -59,12 +103,21 @@ impl Node { loop { match receiver.recv().await { Ok(NodeEvent::DlcChannelEvent { dlc_channel_event }) => { - if let Err(e) = node.process_dlc_channel_event(dlc_channel_event) { + if let Err(e) = node.shadow_dlc_channel(dlc_channel_event) { tracing::error!( ?dlc_channel_event, "Failed to process DLC channel event. Error: {e:#}" ); } + + if let Err(e) = + node.check_for_dlc_channel_closures(dlc_channel_event).await + { + tracing::error!( + ?dlc_channel_event, + "Failed to run check for dlc channel closures. Error: {e:}" + ); + } } Ok(NodeEvent::Connected { .. }) | Ok(NodeEvent::SendDlcMessage { .. }) @@ -83,7 +136,7 @@ impl Node { }); } - pub fn process_dlc_channel_event(&self, dlc_channel_event: DlcChannelEvent) -> Result<()> { + pub fn shadow_dlc_channel(&self, dlc_channel_event: DlcChannelEvent) -> Result<()> { let mut conn = self.pool.get()?; let protocol_id = match dlc_channel_event.get_reference_id() { @@ -285,4 +338,179 @@ impl Node { Ok(()) } + + /// Checks if the dlc channel got closed and updates a potential open position or dlc protocol. + /// + /// If the dlc channel is closing the position will be set to `Closing`, if the dlc channel is + /// closed or counter closed the closing position will be set to closed with a closing price + /// (from the attestation and a trader realized pnl calculated from the cet payout and the + /// last trader reserve) + /// + /// If the dlc channel is `CollaborativelyClosed` we finish the corresponding dlc_protocol. + async fn check_for_dlc_channel_closures( + &self, + dlc_channel_event: DlcChannelEvent, + ) -> Result<()> { + let mut conn = self.pool.get()?; + + let reference_id = dlc_channel_event.get_reference_id().with_context(|| format!("Can't process dlc channel event without reference id. dlc_channel_event = {dlc_channel_event:?}"))?; + let protocol_id = ProtocolId::try_from(reference_id)?; + + match dlc_channel_event { + // If a channel is set to closing it means the buffer transaction got broadcasted, which + // will only happen if the channel got force closed while the user had an open position. + DlcChannelEvent::Closing(_) => { + let channel = &self.inner.get_dlc_channel_by_reference_id(reference_id)?; + let trader_id = channel.get_counter_party_id(); + + // we do not know the price yet, since we have to wait for the position to expire. + if db::positions::Position::set_open_position_to_closing( + &mut conn, + &to_secp_pk_30(trader_id), + None, + )? > 0 + { + tracing::info!(%trader_id, "Set open position to closing after the dlc channel got force closed."); + } + } + // A dlc channel is set to `Closed` or `CounterClosed` if the CET got broadcasted. The + // underlying contract is either `PreClosed` or `Closed` depending on the CET + // confirmations. + DlcChannelEvent::Closed(_) | DlcChannelEvent::CounterClosed(_) => { + let dlc_protocol = db::dlc_protocols::get_dlc_protocol(&mut conn, protocol_id)?; + let contract_id = &dlc_protocol.contract_id.context("Missing contract id")?; + let trader_id = dlc_protocol.trader; + let contract = self + .inner + .get_contract_by_id(contract_id)? + .context("Missing contract")?; + + let position = db::positions::Position::get_position_by_trader( + &mut conn, + trader_id, + /* the closing price doesn't matter here. */ + vec![PositionState::Closing { closing_price: 0.0 }], + )? + .with_context(|| { + format!("Couldn't find closing position for trader. trader_id = {trader_id}") + })?; + + let (closing_price, trader_realized_pnl_sat) = match contract { + Contract::PreClosed(PreClosedContract { + // We assume a closed contract does always have an attestation + attestations: Some(attestations), + signed_cet, + .. + }) + | Contract::Closed(ClosedContract { + // We assume a closed contract does always have an attestation + attestations: Some(attestations), + signed_cet: Some(signed_cet), + .. + }) => { + let trader_realized_pnl_sat = self.calculate_trader_realized_pnl_from_cet( + &mut conn, + &dlc_protocol.channel_id, + signed_cet, + )?; + + let closing_price = Decimal::from_str_radix( + &attestations + .first() + .context("at least one attestation")? + .outcomes + .join(""), + 2, + )?; + + (closing_price, trader_realized_pnl_sat) + } + contract => { + bail!("Contract in unexpected state. Expected PreClosed or Closed Got: {:?}, trader_id = {trader_id}", contract) + } + }; + + tracing::debug!( + ?position, + %trader_id, + "Finalize closing position after force closure", + ); + + if db::positions::Position::set_position_to_closed_with_pnl( + &mut conn, + position.id, + trader_realized_pnl_sat, + closing_price, + )? > 0 + { + tracing::info!(%trader_id, "Set closing position to closed after the dlc channel got force closed."); + } else { + tracing::warn!(%trader_id, "Failed to set closing position to closed after the dlc channel got force closed."); + } + } + DlcChannelEvent::CollaborativelyClosed(_) => { + let channel = &self.inner.get_dlc_channel_by_reference_id(reference_id)?; + let protocol_executor = dlc_protocol::DlcProtocolExecutor::new(self.pool.clone()); + protocol_executor.finish_dlc_protocol( + protocol_id, + &to_secp_pk_30(channel.get_counter_party_id()), + None, + &channel.get_id(), + self.tx_position_feed.clone(), + )?; + } + DlcChannelEvent::Offered(_) + | DlcChannelEvent::Accepted(_) + | DlcChannelEvent::Established(_) + | DlcChannelEvent::SettledOffered(_) + | DlcChannelEvent::SettledReceived(_) + | DlcChannelEvent::SettledAccepted(_) + | DlcChannelEvent::SettledConfirmed(_) + | DlcChannelEvent::Settled(_) + | DlcChannelEvent::SettledClosing(_) + | DlcChannelEvent::RenewOffered(_) + | DlcChannelEvent::RenewAccepted(_) + | DlcChannelEvent::RenewConfirmed(_) + | DlcChannelEvent::RenewFinalized(_) + | DlcChannelEvent::CollaborativeCloseOffered(_) + | DlcChannelEvent::ClosedPunished(_) + | DlcChannelEvent::FailedAccept(_) + | DlcChannelEvent::FailedSign(_) + | DlcChannelEvent::Cancelled(_) + | DlcChannelEvent::Deleted(_) => {} // ignored + } + + Ok(()) + } + + /// Calculates the trader realized pnl from the cet outputs which do not belong to us. + /// 1. Sum the trader payouts + /// 2. Subtract the trader reserve sats from the trader payout + fn calculate_trader_realized_pnl_from_cet( + &self, + conn: &mut PooledConnection>, + channel_id: &DlcChannelId, + signed_cet: Transaction, + ) -> Result { + let trader_payout: u64 = signed_cet + .output + .iter() + .filter(|output| { + !self + .inner + .is_mine(&ScriptBuf::from_bytes(output.script_pubkey.to_bytes())) + }) + .map(|output| output.value) + .sum(); + + let dlc_channel = + db::dlc_channels::get_dlc_channel(conn, channel_id)?.with_context(|| { + format!("Couldn't find dlc channel by channel id = {:?}", channel_id) + })?; + + let trader_realized_pnl_sat = + trader_payout as i64 - dlc_channel.trader_reserve_sats.to_sat() as i64; + + Ok(trader_realized_pnl_sat) + } } diff --git a/coordinator/src/position/mod.rs b/coordinator/src/position/mod.rs index f97d67edf..c446ac883 100644 --- a/coordinator/src/position/mod.rs +++ b/coordinator/src/position/mod.rs @@ -1,220 +1 @@ -use crate::db; -use crate::dlc_protocol::ProtocolId; -use crate::node::Node; -use crate::position::models::PositionState; -use anyhow::bail; -use anyhow::Context; -use anyhow::Result; -use bitcoin::ScriptBuf; -use bitcoin_old::Transaction; -use diesel::r2d2::ConnectionManager; -use diesel::r2d2::PooledConnection; -use diesel::PgConnection; -use dlc_manager::contract::ClosedContract; -use dlc_manager::contract::Contract; -use dlc_manager::contract::PreClosedContract; -use dlc_manager::DlcChannelId; -use rust_decimal::Decimal; -use tokio::sync::broadcast::error::RecvError; -use xxi_node::bitcoin_conversion::to_secp_pk_30; -use xxi_node::node::event::NodeEvent; -use xxi_node::storage::DlcChannelEvent; - pub mod models; - -impl Node { - pub fn spawn_watch_closing_channels(&self) { - let mut receiver = self.inner.event_handler.subscribe(); - - tokio::spawn({ - let node = self.clone(); - async move { - loop { - match receiver.recv().await { - Ok(NodeEvent::DlcChannelEvent { dlc_channel_event }) => { - if let Err(e) = node - .update_position_after_dlc_channel_event(dlc_channel_event) - .await - { - tracing::error!(?dlc_channel_event, "Failed to update position after dlc channel event. Error: {e:}") - } - } - Ok(NodeEvent::Connected { .. }) - | Ok(NodeEvent::SendDlcMessage { .. }) - | Ok(NodeEvent::StoreDlcMessage { .. }) - | Ok(NodeEvent::SendLastDlcMessage { .. }) => {} // ignored - Err(RecvError::Lagged(skipped)) => { - tracing::warn!("Skipped {skipped} messages"); - } - Err(RecvError::Closed) => { - tracing::error!("Lost connection to sender!"); - break; - } - } - } - } - }); - } - - /// Checks if the dlc channel got force closed and updates a potential open position. If the dlc - /// channel is closing the position will be set to `Closing`, if the dlc channel is closed or - /// counter closed the closing position will be set to closed with a closing price (from the - /// attestation and a trader realized pnl calculated from the cet payout and the last trader - /// reserve) - async fn update_position_after_dlc_channel_event( - &self, - dlc_channel_event: DlcChannelEvent, - ) -> Result<()> { - let mut conn = self.pool.get()?; - - let reference_id = dlc_channel_event.get_reference_id().with_context(|| format!("Can't process dlc channel event without reference id. dlc_channel_event = {dlc_channel_event:?}"))?; - let protocol_id = ProtocolId::try_from(reference_id)?; - - match dlc_channel_event { - // If a channel is set to closing it means the buffer transaction got broadcasted, which - // will only happen if the channel got force closed while the user had an open position. - DlcChannelEvent::Closing(_) => { - let channel = &self.inner.get_dlc_channel_by_reference_id(reference_id)?; - let trader_id = channel.get_counter_party_id(); - - // we do not know the price yet, since we have to wait for the position to expire. - if db::positions::Position::set_open_position_to_closing( - &mut conn, - &to_secp_pk_30(trader_id), - None, - )? > 0 - { - tracing::info!(%trader_id, "Set open position to closing after the dlc channel got force closed."); - } - } - // A dlc channel is set to `Closed` or `CounterClosed` if the CET got broadcasted. The - // underlying contract is either `PreClosed` or `Closed` depending on the CET - // confirmations. - DlcChannelEvent::Closed(_) | DlcChannelEvent::CounterClosed(_) => { - let dlc_protocol = db::dlc_protocols::get_dlc_protocol(&mut conn, protocol_id)?; - let contract_id = &dlc_protocol.contract_id.context("Missing contract id")?; - let trader_id = dlc_protocol.trader; - let contract = self - .inner - .get_contract_by_id(contract_id)? - .context("Missing contract")?; - - let position = db::positions::Position::get_position_by_trader( - &mut conn, - trader_id, - /* the closing price doesn't matter here. */ - vec![PositionState::Closing { closing_price: 0.0 }], - )? - .with_context(|| { - format!("Couldn't find closing position for trader. trader_id = {trader_id}") - })?; - - let (closing_price, trader_realized_pnl_sat) = match contract { - Contract::PreClosed(PreClosedContract { - // We assume a closed contract does always have an attestation - attestations: Some(attestations), - signed_cet, - .. - }) - | Contract::Closed(ClosedContract { - // We assume a closed contract does always have an attestation - attestations: Some(attestations), - signed_cet: Some(signed_cet), - .. - }) => { - let trader_realized_pnl_sat = self.calculate_trader_realized_pnl_from_cet( - &mut conn, - &dlc_protocol.channel_id, - signed_cet, - )?; - - let closing_price = Decimal::from_str_radix( - &attestations - .first() - .context("at least one attestation")? - .outcomes - .join(""), - 2, - )?; - - (closing_price, trader_realized_pnl_sat) - } - contract => { - bail!("Contract in unexpected state. Expected PreClosed or Closed Got: {:?}, trader_id = {trader_id}", contract) - } - }; - - tracing::debug!( - ?position, - %trader_id, - "Finalize closing position after force closure", - ); - - if db::positions::Position::set_position_to_closed_with_pnl( - &mut conn, - position.id, - trader_realized_pnl_sat, - closing_price, - )? > 0 - { - tracing::info!(%trader_id, "Set closing position to closed after the dlc channel got force closed."); - } else { - tracing::warn!(%trader_id, "Failed to set closing position to closed after the dlc channel got force closed."); - } - } - DlcChannelEvent::Offered(_) - | DlcChannelEvent::Accepted(_) - | DlcChannelEvent::Established(_) - | DlcChannelEvent::SettledOffered(_) - | DlcChannelEvent::SettledReceived(_) - | DlcChannelEvent::SettledAccepted(_) - | DlcChannelEvent::SettledConfirmed(_) - | DlcChannelEvent::Settled(_) - | DlcChannelEvent::SettledClosing(_) - | DlcChannelEvent::RenewOffered(_) - | DlcChannelEvent::RenewAccepted(_) - | DlcChannelEvent::RenewConfirmed(_) - | DlcChannelEvent::RenewFinalized(_) - | DlcChannelEvent::CollaborativeCloseOffered(_) - | DlcChannelEvent::ClosedPunished(_) - | DlcChannelEvent::CollaborativelyClosed(_) - | DlcChannelEvent::FailedAccept(_) - | DlcChannelEvent::FailedSign(_) - | DlcChannelEvent::Cancelled(_) - | DlcChannelEvent::Deleted(_) => {} // ignored - } - - Ok(()) - } - - /// Calculates the trader realized pnl from the cet outputs which do not belong to us. - /// 1. Sum the trader payouts - /// 2. Subtract the trader reserve sats from the trader payout - fn calculate_trader_realized_pnl_from_cet( - &self, - conn: &mut PooledConnection>, - channel_id: &DlcChannelId, - signed_cet: Transaction, - ) -> Result { - let trader_payout: u64 = signed_cet - .output - .iter() - .filter(|output| { - !self - .inner - .is_mine(&ScriptBuf::from_bytes(output.script_pubkey.to_bytes())) - }) - .map(|output| output.value) - .sum(); - - let dlc_channel = - db::dlc_channels::get_dlc_channel(conn, channel_id)?.with_context(|| { - format!("Couldn't find dlc channel by channel id = {:?}", channel_id) - })?; - - let trader_realized_pnl_sat = - trader_payout as i64 - dlc_channel.trader_reserve_sats.to_sat() as i64; - - Ok(trader_realized_pnl_sat) - } -} diff --git a/coordinator/src/routes/admin.rs b/coordinator/src/routes/admin.rs index 4c1661313..8d2cacb14 100644 --- a/coordinator/src/routes/admin.rs +++ b/coordinator/src/routes/admin.rs @@ -298,7 +298,6 @@ pub async fn close_channel( state .node - .inner .close_dlc_channel(channel_id, params.force.unwrap_or_default()) .await .map_err(|e| AppError::InternalServerError(format!("{e:#}")))?; diff --git a/crates/xxi-node/src/node/dlc_channel.rs b/crates/xxi-node/src/node/dlc_channel.rs index d4536bbfa..daaa9fca1 100644 --- a/crates/xxi-node/src/node/dlc_channel.rs +++ b/crates/xxi-node/src/node/dlc_channel.rs @@ -140,7 +140,7 @@ impl Result<()> { + ) -> Result { let channel_id_hex = hex::encode(channel_id); tracing::info!( @@ -161,7 +161,7 @@ impl Result<()> { node.inner .close_dlc_channel(channel_details.channel_id, is_force_close) - .await + .await?; + + Ok(()) } pub fn get_signed_dlc_channels() -> Result> { From dfaa3deda4053cc405bca93a86d77265c9254aa9 Mon Sep 17 00:00:00 2001 From: Richard Holzeis Date: Wed, 8 May 2024 15:35:42 +0200 Subject: [PATCH 4/9] chore: Remove unused code --- mobile/lib/common/domain/background_task.dart | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/mobile/lib/common/domain/background_task.dart b/mobile/lib/common/domain/background_task.dart index 37fad582e..dfbcbaf1c 100644 --- a/mobile/lib/common/domain/background_task.dart +++ b/mobile/lib/common/domain/background_task.dart @@ -62,39 +62,6 @@ class BackgroundTask { return bridge.BackgroundTask_Rollover(TaskStatus.apiDummy()); } - static BackgroundTask fromApi(bridge.BackgroundTask task) { - final taskType = getTaskType(task); - - final (taskStatus, error) = TaskStatus.fromApi(task.field0); - return BackgroundTask(type: taskType, status: taskStatus, error: error); - } - - static TaskType getTaskType(bridge.BackgroundTask task) { - if (task is bridge.BackgroundTask_RecoverDlc) { - return TaskType.recover; - } - if (task is bridge.BackgroundTask_Rollover) { - return TaskType.rollover; - } - if (task is bridge.BackgroundTask_CollabRevert) { - return TaskType.collaborativeRevert; - } - if (task is bridge.BackgroundTask_FullSync) { - return TaskType.fullSync; - } - if (task is bridge.BackgroundTask_AsyncTrade) { - return TaskType.asyncTrade; - } - if (task is bridge.BackgroundTask_Expire) { - return TaskType.expire; - } - if (task is bridge.BackgroundTask_Liquidate) { - return TaskType.liquidate; - } - - return TaskType.unknown; - } - @override String toString() { return "$type ($status)"; From 7ac674866322c6d463644616fdb93009e2295282 Mon Sep 17 00:00:00 2001 From: Richard Holzeis Date: Wed, 8 May 2024 15:36:46 +0200 Subject: [PATCH 5/9] feat: Show task dialog when closing channel --- .../background_task_change_notifier.dart | 5 ++++ .../common/background_task_dialog_screen.dart | 18 +++++++++++++ mobile/lib/common/domain/background_task.dart | 1 + .../common/settings/collab_close_screen.dart | 6 +---- mobile/native/src/api.rs | 25 ++++++++++++++++++- mobile/native/src/dlc/node.rs | 15 ++++++++++- mobile/native/src/event/api.rs | 5 ++++ mobile/native/src/event/mod.rs | 1 + 8 files changed, 69 insertions(+), 7 deletions(-) diff --git a/mobile/lib/common/background_task_change_notifier.dart b/mobile/lib/common/background_task_change_notifier.dart index 5aa7a3be6..d1123016c 100644 --- a/mobile/lib/common/background_task_change_notifier.dart +++ b/mobile/lib/common/background_task_change_notifier.dart @@ -64,6 +64,11 @@ class BackgroundTaskChangeNotifier extends ChangeNotifier implements Subscriber events.push(BackgroundTask(type: TaskType.liquidate, status: taskStatus, error: error)); notifyListeners(); } + + if (event.field0 is bridge.BackgroundTask_CloseChannel) { + events.push(BackgroundTask(type: TaskType.closeChannel, status: taskStatus, error: error)); + notifyListeners(); + } } } } diff --git a/mobile/lib/common/background_task_dialog_screen.dart b/mobile/lib/common/background_task_dialog_screen.dart index 1856701b8..ef015833a 100644 --- a/mobile/lib/common/background_task_dialog_screen.dart +++ b/mobile/lib/common/background_task_dialog_screen.dart @@ -3,7 +3,9 @@ import 'package:flutter/services.dart'; import 'package:get_10101/common/background_task_change_notifier.dart'; import 'package:get_10101/common/domain/background_task.dart'; import 'package:get_10101/common/task_status_dialog.dart'; +import 'package:get_10101/features/wallet/wallet_screen.dart'; import 'package:get_10101/logger/logger.dart'; +import 'package:go_router/go_router.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'dart:convert'; import 'package:get_10101/bridge_generated/bridge_definitions.dart' as bridge; @@ -158,6 +160,22 @@ class _BackgroundTaskDialogScreenState extends State TaskStatus.failed => const Text("Oops, something went wrong!") }, onClose: () => activeTask = null), + TaskType.closeChannel => TaskStatusDialog( + task: task!, + content: switch (task.status) { + TaskStatus.pending => const Text( + "Your channel is getting closed collaboratively.\n\nPlease do not close the app while the order is executed."), + TaskStatus.success => const Text( + "Your channel has been closed collaboratively.\n\nIf you don't see your funds as incoming on-chain transaction, try to run a full-sync (Settings > Wallet Settings)"), + TaskStatus.failed => const Text("Oops, something went wrong!") + }, + onClose: () { + activeTask = null; + // we need to delay routing a bit as we might still be processing the addPostFrameCallback. + Future.delayed(const Duration(milliseconds: 250), () { + GoRouter.of(context).go(WalletScreen.route); + }); + }), TaskType.unknown || null => null }; } diff --git a/mobile/lib/common/domain/background_task.dart b/mobile/lib/common/domain/background_task.dart index dfbcbaf1c..50ab83a43 100644 --- a/mobile/lib/common/domain/background_task.dart +++ b/mobile/lib/common/domain/background_task.dart @@ -8,6 +8,7 @@ enum TaskType { collaborativeRevert, fullSync, recover, + closeChannel, unknown } diff --git a/mobile/lib/common/settings/collab_close_screen.dart b/mobile/lib/common/settings/collab_close_screen.dart index 7db3c2dea..b70e820cc 100644 --- a/mobile/lib/common/settings/collab_close_screen.dart +++ b/mobile/lib/common/settings/collab_close_screen.dart @@ -4,7 +4,6 @@ import 'package:get_10101/common/dlc_channel_change_notifier.dart'; import 'package:get_10101/common/settings/settings_screen.dart'; import 'package:get_10101/common/snack_bar.dart'; import 'package:get_10101/features/trade/trade_screen.dart'; -import 'package:get_10101/features/wallet/wallet_screen.dart'; import 'package:go_router/go_router.dart'; import 'package:get_10101/ffi.dart' as rust; import 'package:provider/provider.dart'; @@ -106,10 +105,7 @@ class _CollabCloseScreenState extends State { ), onConfirmation: () async { final messenger = ScaffoldMessenger.of(context); - rust.api - .closeChannel() - .then((value) => GoRouter.of(context).go(WalletScreen.route)) - .catchError((e) { + rust.api.closeChannel().catchError((e) { showSnackBar( messenger, e.toString(), diff --git a/mobile/native/src/api.rs b/mobile/native/src/api.rs index 8a64e8268..ab6323bcd 100644 --- a/mobile/native/src/api.rs +++ b/mobile/native/src/api.rs @@ -16,6 +16,9 @@ pub use crate::dlc_channel::SignedChannelState; use crate::emergency_kit; use crate::event; use crate::event::api::FlutterSubscriber; +use crate::event::BackgroundTask; +use crate::event::EventInternal; +use crate::event::TaskStatus; use crate::health; use crate::logger; use crate::max_quantity::max_quantity; @@ -41,6 +44,7 @@ use std::backtrace::Backtrace; use std::fmt; use std::path::Path; use std::path::PathBuf; +use std::time::Duration; use time::OffsetDateTime; use tokio::sync::broadcast; use tokio::sync::broadcast::channel; @@ -511,7 +515,26 @@ pub fn get_new_address() -> Result { #[tokio::main(flavor = "current_thread")] pub async fn close_channel() -> Result<()> { - dlc::close_channel(false).await + event::publish(&EventInternal::BackgroundNotification( + BackgroundTask::CloseChannel(TaskStatus::Pending), + )); + + let fail = |e: &anyhow::Error| { + event::publish(&EventInternal::BackgroundNotification( + BackgroundTask::CloseChannel(TaskStatus::Failed(format!("{e:#}"))), + )) + }; + + dlc::close_channel(false).await.inspect_err(fail)?; + // wait a bit so that the sync can find the the broadcasted transaction. + tokio::time::sleep(Duration::from_millis(500)).await; + dlc::refresh_wallet_info().await.inspect_err(fail)?; + + event::publish(&EventInternal::BackgroundNotification( + BackgroundTask::CloseChannel(TaskStatus::Success), + )); + + Ok(()) } #[tokio::main(flavor = "current_thread")] diff --git a/mobile/native/src/dlc/node.rs b/mobile/native/src/dlc/node.rs index e105c8525..2f7382cb9 100644 --- a/mobile/native/src/dlc/node.rs +++ b/mobile/native/src/dlc/node.rs @@ -413,6 +413,10 @@ impl Node { TenTenOneMessage::CollaborativeCloseOffer(TenTenOneCollaborativeCloseOffer { collaborative_close_offer: CollaborativeCloseOffer { channel_id, .. }, }) => { + event::publish(&EventInternal::BackgroundNotification( + BackgroundTask::CloseChannel(TaskStatus::Pending), + )); + let channel_id_hex_string = hex::encode(channel_id); tracing::info!( channel_id = channel_id_hex_string, @@ -422,7 +426,16 @@ impl Node { // TODO(bonomat): we should verify that the proposed amount is acceptable self.inner - .accept_dlc_channel_collaborative_close(&channel_id)?; + .accept_dlc_channel_collaborative_close(&channel_id) + .inspect_err(|e| { + event::publish(&EventInternal::BackgroundNotification( + BackgroundTask::CloseChannel(TaskStatus::Failed(format!("{e:#}"))), + )) + })?; + + event::publish(&EventInternal::BackgroundNotification( + BackgroundTask::CloseChannel(TaskStatus::Success), + )); } _ => (), } diff --git a/mobile/native/src/event/api.rs b/mobile/native/src/event/api.rs index 41d1c0982..e675cf798 100644 --- a/mobile/native/src/event/api.rs +++ b/mobile/native/src/event/api.rs @@ -50,6 +50,8 @@ pub enum BackgroundTask { CollabRevert(TaskStatus), /// The app is performing a full sync of the on-chain wallet. FullSync(TaskStatus), + /// The app is closing its dlc channel + CloseChannel(TaskStatus), } impl From for Event { @@ -150,6 +152,9 @@ impl From for BackgroundTask { BackgroundTask::CollabRevert(status.into()) } event::BackgroundTask::FullSync(status) => BackgroundTask::FullSync(status.into()), + event::BackgroundTask::CloseChannel(status) => { + BackgroundTask::CloseChannel(status.into()) + } } } } diff --git a/mobile/native/src/event/mod.rs b/mobile/native/src/event/mod.rs index a3ee38c45..183992712 100644 --- a/mobile/native/src/event/mod.rs +++ b/mobile/native/src/event/mod.rs @@ -50,6 +50,7 @@ pub enum BackgroundTask { CollabRevert(TaskStatus), RecoverDlc(TaskStatus), FullSync(TaskStatus), + CloseChannel(TaskStatus), } #[derive(Clone, Debug)] From 4d50d3b8cee166e4dbaddabdb3d504faa45935d4 Mon Sep 17 00:00:00 2001 From: Richard Holzeis Date: Wed, 8 May 2024 15:40:16 +0200 Subject: [PATCH 6/9] fix: Pop all events from stack It can happen that the dialog is not able to render before the next event is received. Before that patch that could have lead to some random dangling events that get shown once the root screen gets rebuild. This patch puts new events in a last in last out order and on rendering the task dialog pops all events from the queue. This way we prevent having dangling events. fixes https://github.com/get10101/10101/issues/2506 --- mobile/lib/common/background_task_change_notifier.dart | 2 +- mobile/lib/common/background_task_dialog_screen.dart | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mobile/lib/common/background_task_change_notifier.dart b/mobile/lib/common/background_task_change_notifier.dart index d1123016c..fe6d7e941 100644 --- a/mobile/lib/common/background_task_change_notifier.dart +++ b/mobile/lib/common/background_task_change_notifier.dart @@ -7,7 +7,7 @@ import 'package:get_10101/logger/logger.dart'; class Stack { final _list = []; - void push(E value) => _list.add(value); + void push(E value) => _list.insert(0, value); E pop() => _list.removeLast(); diff --git a/mobile/lib/common/background_task_dialog_screen.dart b/mobile/lib/common/background_task_dialog_screen.dart index ef015833a..184996d8e 100644 --- a/mobile/lib/common/background_task_dialog_screen.dart +++ b/mobile/lib/common/background_task_dialog_screen.dart @@ -64,7 +64,13 @@ class _BackgroundTaskDialogScreenState extends State pageBuilder: (context, _, __) { // watch task updates from within the dialog. try { - final task = context.watch().events.pop(); + final events = context.watch().events; + var task = events.pop(); + while (events.isNotEmpty) { + // we might have received very fast events. In that case we pop until we are at + // the latest event. + task = events.pop(); + } if (activeTask != null && task.type != activeTask!.type) { logger.w("Received another task event $task while $activeTask is still active!"); } From 3cdb0add929a37c186dd24bd0a47ab391a5869b0 Mon Sep 17 00:00:00 2001 From: Richard Holzeis Date: Thu, 9 May 2024 13:12:23 +0200 Subject: [PATCH 7/9] chore: Move protocol id to xxi node crate --- coordinator/src/db/dlc_channels.rs | 2 +- coordinator/src/db/dlc_protocols.rs | 2 +- coordinator/src/db/trade_params.rs | 2 +- coordinator/src/dlc_protocol.rs | 82 +----------------------- coordinator/src/node.rs | 11 ++-- coordinator/src/node/channel.rs | 14 ++-- coordinator/src/node/rollover.rs | 2 +- coordinator/src/routes/admin.rs | 2 +- coordinator/src/trade/mod.rs | 10 +-- crates/xxi-node/src/node/dlc_channel.rs | 32 ++++----- crates/xxi-node/src/node/mod.rs | 77 +++++++++++++++++++--- crates/xxi-node/src/tests/dlc_channel.rs | 8 +-- 12 files changed, 112 insertions(+), 132 deletions(-) diff --git a/coordinator/src/db/dlc_channels.rs b/coordinator/src/db/dlc_channels.rs index c07a969d7..991ab21e4 100644 --- a/coordinator/src/db/dlc_channels.rs +++ b/coordinator/src/db/dlc_channels.rs @@ -1,4 +1,3 @@ -use crate::dlc_protocol::ProtocolId; use crate::node::channel; use crate::schema::dlc_channels; use crate::schema::sql_types::DlcChannelStateType; @@ -24,6 +23,7 @@ use std::any::TypeId; use std::str::FromStr; use time::OffsetDateTime; use uuid::Uuid; +use xxi_node::node::ProtocolId; #[derive(Debug, Clone, Copy, PartialEq, FromSqlRow, AsExpression)] #[diesel(sql_type = DlcChannelStateType)] diff --git a/coordinator/src/db/dlc_protocols.rs b/coordinator/src/db/dlc_protocols.rs index bb6ffda3f..316ae2ced 100644 --- a/coordinator/src/db/dlc_protocols.rs +++ b/coordinator/src/db/dlc_protocols.rs @@ -1,6 +1,5 @@ use crate::db; use crate::dlc_protocol; -use crate::dlc_protocol::ProtocolId; use crate::schema::dlc_protocols; use crate::schema::sql_types::ProtocolStateType; use crate::schema::sql_types::ProtocolTypeType; @@ -21,6 +20,7 @@ use std::any::TypeId; use std::str::FromStr; use time::OffsetDateTime; use uuid::Uuid; +use xxi_node::node::ProtocolId; #[derive(Debug, Clone, Copy, PartialEq, FromSqlRow, AsExpression, Eq, Hash)] #[diesel(sql_type = ProtocolStateType)] diff --git a/coordinator/src/db/trade_params.rs b/coordinator/src/db/trade_params.rs index 0a3f06b82..6f0b7f7d8 100644 --- a/coordinator/src/db/trade_params.rs +++ b/coordinator/src/db/trade_params.rs @@ -1,5 +1,4 @@ use crate::dlc_protocol; -use crate::dlc_protocol::ProtocolId; use crate::orderbook::db::custom_types::Direction; use crate::schema::trade_params; use bitcoin::secp256k1::PublicKey; @@ -14,6 +13,7 @@ use diesel::RunQueryDsl; use std::str::FromStr; use uuid::Uuid; use xxi_node::commons; +use xxi_node::node::ProtocolId; #[derive(Queryable, Debug)] #[diesel(table_name = trade_params)] diff --git a/coordinator/src/dlc_protocol.rs b/coordinator/src/dlc_protocol.rs index 45ad097e5..791c541e9 100644 --- a/coordinator/src/dlc_protocol.rs +++ b/coordinator/src/dlc_protocol.rs @@ -14,93 +14,16 @@ use diesel::Connection; use diesel::PgConnection; use diesel::QueryResult; use dlc_manager::ContractId; -use dlc_manager::ReferenceId; use rust_decimal::prelude::FromPrimitive; use rust_decimal::prelude::ToPrimitive; use rust_decimal::Decimal; -use std::fmt::Display; -use std::fmt::Formatter; -use std::str::from_utf8; use time::OffsetDateTime; use tokio::sync::broadcast::Sender; -use uuid::Uuid; use xxi_node::cfd::calculate_pnl; use xxi_node::commons; use xxi_node::commons::Direction; use xxi_node::node::rust_dlc_manager::DlcChannelId; - -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct ProtocolId(Uuid); - -impl ProtocolId { - pub fn new() -> Self { - ProtocolId(Uuid::new_v4()) - } - - pub fn to_uuid(&self) -> Uuid { - self.0 - } -} - -impl Default for ProtocolId { - fn default() -> Self { - Self::new() - } -} - -impl Display for ProtocolId { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - self.0.to_string().fmt(f) - } -} - -impl From for ReferenceId { - fn from(value: ProtocolId) -> Self { - let uuid = value.to_uuid(); - - // 16 bytes. - let uuid_bytes = uuid.as_bytes(); - - // 32-digit hex string. - let hex = hex::encode(uuid_bytes); - - // Derived `ReferenceId`: 32-bytes. - let hex_bytes = hex.as_bytes(); - - let mut array = [0u8; 32]; - array.copy_from_slice(hex_bytes); - - array - } -} - -impl TryFrom for ProtocolId { - type Error = anyhow::Error; - - fn try_from(value: ReferenceId) -> Result { - // 32-digit hex string. - let hex = from_utf8(&value)?; - - // 16 bytes. - let uuid_bytes = hex::decode(hex)?; - - let uuid = Uuid::from_slice(&uuid_bytes)?; - - Ok(ProtocolId(uuid)) - } -} - -impl From for ProtocolId { - fn from(value: Uuid) -> Self { - ProtocolId(value) - } -} - -impl From for Uuid { - fn from(value: ProtocolId) -> Self { - value.0 - } -} +use xxi_node::node::ProtocolId; pub struct DlcProtocol { pub id: ProtocolId, @@ -605,8 +528,7 @@ impl DlcProtocolExecutor { Ok(()) } - /// Completes the rollover dlc protocol as successful and updates the 10101 meta data - /// accordingly in a single database transaction. + /// Completes the collab close dlc protocol as successful fn finish_close_channel_dlc_protocol( &self, conn: &mut PgConnection, diff --git a/coordinator/src/node.rs b/coordinator/src/node.rs index d0773f226..fc22040aa 100644 --- a/coordinator/src/node.rs +++ b/coordinator/src/node.rs @@ -1,7 +1,6 @@ use crate::db; use crate::dlc_protocol; use crate::dlc_protocol::DlcProtocolType; -use crate::dlc_protocol::ProtocolId; use crate::message::OrderbookMessage; use crate::node::storage::NodeStorage; use crate::position::models::PositionState; @@ -45,6 +44,7 @@ use xxi_node::message_handler::TenTenOneSignChannel; use xxi_node::node; use xxi_node::node::event::NodeEvent; use xxi_node::node::tentenone_message_name; +use xxi_node::node::ProtocolId; use xxi_node::node::RunningNode; pub mod channel; @@ -580,11 +580,10 @@ impl Node { let protocol_id = close_offer.reference_id.context("Missing reference id")?; let protocol_id = ProtocolId::try_from(protocol_id)?; - let previous_id = channel.get_reference_id(); - let previous_id = match previous_id { - Some(previous_id) => Some(ProtocolId::try_from(previous_id)?), - None => None, - }; + let previous_id = channel + .get_reference_id() + .map(ProtocolId::try_from) + .transpose()?; let protocol_executor = dlc_protocol::DlcProtocolExecutor::new(self.pool.clone()); protocol_executor.start_dlc_protocol( diff --git a/coordinator/src/node/channel.rs b/coordinator/src/node/channel.rs index c3a3c6ace..addf7f422 100644 --- a/coordinator/src/node/channel.rs +++ b/coordinator/src/node/channel.rs @@ -1,7 +1,6 @@ use crate::db; use crate::dlc_protocol; use crate::dlc_protocol::DlcProtocolType; -use crate::dlc_protocol::ProtocolId; use crate::node::Node; use crate::position::models::PositionState; use anyhow::bail; @@ -32,6 +31,7 @@ use tokio::sync::broadcast::error::RecvError; use xxi_node::bitcoin_conversion::to_secp_pk_30; use xxi_node::bitcoin_conversion::to_txid_30; use xxi_node::node::event::NodeEvent; +use xxi_node::node::ProtocolId; use xxi_node::storage::DlcChannelEvent; pub enum DlcChannelState { @@ -68,17 +68,15 @@ impl Node { is_force_close: bool, ) -> Result<()> { let channel = self.inner.get_dlc_channel_by_id(&channel_id)?; - let previous_id = channel.get_reference_id(); - let previous_id = match previous_id { - Some(previous_id) => Some(ProtocolId::try_from(previous_id)?), - None => None, - }; + let previous_id = channel + .get_reference_id() + .map(ProtocolId::try_from) + .transpose()?; - let reference_id = self + let protocol_id = self .inner .close_dlc_channel(channel_id, is_force_close) .await?; - let protocol_id = ProtocolId::try_from(reference_id)?; let protocol_executor = dlc_protocol::DlcProtocolExecutor::new(self.pool.clone()); protocol_executor.start_dlc_protocol( diff --git a/coordinator/src/node/rollover.rs b/coordinator/src/node/rollover.rs index 0636a1fa0..9d3746bbb 100644 --- a/coordinator/src/node/rollover.rs +++ b/coordinator/src/node/rollover.rs @@ -3,7 +3,6 @@ use crate::db; use crate::db::positions; use crate::dlc_protocol; use crate::dlc_protocol::DlcProtocolType; -use crate::dlc_protocol::ProtocolId; use crate::node::Node; use crate::notifications::Notification; use crate::notifications::NotificationKind; @@ -37,6 +36,7 @@ use xxi_node::bitcoin_conversion::to_xonly_pk_30; use xxi_node::commons; use xxi_node::commons::ContractSymbol; use xxi_node::node::event::NodeEvent; +use xxi_node::node::ProtocolId; #[derive(Debug, Clone)] struct Rollover { diff --git a/coordinator/src/routes/admin.rs b/coordinator/src/routes/admin.rs index 8d2cacb14..68422b97b 100644 --- a/coordinator/src/routes/admin.rs +++ b/coordinator/src/routes/admin.rs @@ -1,6 +1,5 @@ use crate::collaborative_revert; use crate::db; -use crate::dlc_protocol::ProtocolId; use crate::parse_dlc_channel_id; use crate::referrals; use crate::routes::AppState; @@ -42,6 +41,7 @@ use xxi_node::bitcoin_conversion::to_secp_pk_30; use xxi_node::bitcoin_conversion::to_txid_30; use xxi_node::commons; use xxi_node::commons::CollaborativeRevertCoordinatorRequest; +use xxi_node::node::ProtocolId; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Balance { diff --git a/coordinator/src/trade/mod.rs b/coordinator/src/trade/mod.rs index cd13b963b..321052aae 100644 --- a/coordinator/src/trade/mod.rs +++ b/coordinator/src/trade/mod.rs @@ -3,7 +3,6 @@ use crate::db; use crate::decimal_from_f32; use crate::dlc_protocol; use crate::dlc_protocol::DlcProtocolType; -use crate::dlc_protocol::ProtocolId; use crate::message::OrderbookMessage; use crate::node::Node; use crate::orderbook::db::matches; @@ -52,6 +51,7 @@ use xxi_node::commons::TradeAndChannelParams; use xxi_node::commons::TradeParams; use xxi_node::node::event::NodeEvent; use xxi_node::node::signed_channel_state_name; +use xxi_node::node::ProtocolId; pub mod models; pub mod websocket; @@ -369,7 +369,7 @@ impl TradeExecutor { trade_params.filled_with.clone(), contract_input, trade_params.pubkey, - protocol_id.into(), + protocol_id, ) .await .context("Could not propose DLC channel")?; @@ -548,7 +548,7 @@ impl TradeExecutor { trade_params.filled_with.clone(), &dlc_channel_id, contract_input, - protocol_id.into(), + protocol_id, ) .await .context("Could not propose reopen DLC channel update")?; @@ -721,7 +721,7 @@ impl TradeExecutor { trade_params.filled_with.clone(), &dlc_channel_id, contract_input, - protocol_id.into(), + protocol_id, ) .await .context("Could not propose resize DLC channel update")?; @@ -878,7 +878,7 @@ impl TradeExecutor { trade_params.filled_with.clone(), &channel_id, settlement_amount_trader, - protocol_id.into(), + protocol_id, ) .await?; diff --git a/crates/xxi-node/src/node/dlc_channel.rs b/crates/xxi-node/src/node/dlc_channel.rs index daaa9fca1..87951c3d8 100644 --- a/crates/xxi-node/src/node/dlc_channel.rs +++ b/crates/xxi-node/src/node/dlc_channel.rs @@ -10,8 +10,8 @@ use crate::message_handler::TenTenOneRolloverOffer; use crate::message_handler::TenTenOneSettleAccept; use crate::message_handler::TenTenOneSettleOffer; use crate::node::event::NodeEvent; -use crate::node::new_reference_id; use crate::node::Node; +use crate::node::ProtocolId; use crate::node::Storage as LnDlcStorage; use crate::on_chain_wallet::BdkStorage; use crate::storage::TenTenOneStorage; @@ -46,7 +46,7 @@ impl Result<(ContractId, DlcChannelId)> { tracing::info!( trader_id = %counterparty, @@ -90,7 +90,7 @@ impl Result { + ) -> Result { let channel_id_hex = hex::encode(channel_id); tracing::info!( @@ -153,21 +153,21 @@ impl, + protocol_id: ProtocolId, ) -> Result<()> { let channel_id = channel.channel_id; let channel_id_hex = hex::encode(channel_id); @@ -178,7 +178,7 @@ impl, + protocol_id: ProtocolId, ) -> Result<()> { let counterparty = channel.counter_party; @@ -206,7 +206,7 @@ impl Result<()> { let channel_id_hex = hex::encode(channel_id); @@ -260,7 +260,7 @@ impl Result { tracing::info!(channel_id = %hex::encode(dlc_channel_id), "Proposing a DLC channel reopen or resize"); spawn_blocking({ @@ -336,7 +336,7 @@ impl ReferenceId { - let uuid = Uuid::new_v4(); - let hex = hex::encode(uuid.as_simple().as_ref()); - let bytes = hex.as_bytes(); +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct ProtocolId(Uuid); - debug_assert!(bytes.len() == 32, "length must be exactly 32 bytes"); +impl ProtocolId { + pub fn new() -> Self { + ProtocolId(Uuid::new_v4()) + } + + pub fn to_uuid(&self) -> Uuid { + self.0 + } +} + +impl Default for ProtocolId { + fn default() -> Self { + Self::new() + } +} + +impl Display for ProtocolId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.to_string().fmt(f) + } +} + +impl From for ReferenceId { + fn from(value: ProtocolId) -> Self { + let uuid = value.to_uuid(); + + // 16 bytes. + let uuid_bytes = uuid.as_bytes(); + + // 32-digit hex string. + let hex = hex::encode(uuid_bytes); + + // Derived `ReferenceId`: 32-bytes. + let hex_bytes = hex.as_bytes(); - let mut array = [0u8; 32]; - array.copy_from_slice(bytes); + let mut array = [0u8; 32]; + array.copy_from_slice(hex_bytes); - array + array + } +} + +impl TryFrom for ProtocolId { + type Error = anyhow::Error; + + fn try_from(value: ReferenceId) -> Result { + // 32-digit hex string. + let hex = from_utf8(&value)?; + + // 16 bytes. + let uuid_bytes = hex::decode(hex)?; + + let uuid = Uuid::from_slice(&uuid_bytes)?; + + Ok(ProtocolId(uuid)) + } +} + +impl From for ProtocolId { + fn from(value: Uuid) -> Self { + ProtocolId(value) + } +} + +impl From for Uuid { + fn from(value: ProtocolId) -> Self { + value.0 + } } diff --git a/crates/xxi-node/src/tests/dlc_channel.rs b/crates/xxi-node/src/tests/dlc_channel.rs index 8ac9fb8a7..3e4532170 100644 --- a/crates/xxi-node/src/tests/dlc_channel.rs +++ b/crates/xxi-node/src/tests/dlc_channel.rs @@ -1,9 +1,9 @@ use crate::bitcoin_conversion::to_secp_pk_29; use crate::node::dlc_channel::estimated_dlc_channel_fee_reserve; use crate::node::event::NodeEvent; -use crate::node::new_reference_id; use crate::node::InMemoryStore; use crate::node::Node; +use crate::node::ProtocolId; use crate::node::RunningNode; use crate::on_chain_wallet; use crate::storage::TenTenOneInMemoryStorage; @@ -43,7 +43,7 @@ async fn can_open_and_settle_offchain() { filled_with.clone(), &coordinator_signed_channel.channel_id, contract_input, - new_reference_id(), + ProtocolId::new(), ) .await .unwrap(); @@ -464,7 +464,7 @@ async fn open_channel_and_position_and_settle_position( filled_with.clone(), contract_input, app.info.pubkey, - new_reference_id(), + ProtocolId::new(), ) .await .unwrap(); @@ -590,7 +590,7 @@ async fn open_channel_and_position_and_settle_position( filled_with.clone(), &coordinator_signed_channel.channel_id, coordinator_dlc_collateral.to_sat() / 2, - new_reference_id(), + ProtocolId::new(), ) .await .unwrap(); From a957310e0fbdae58abe66388b49f72173a165e5f Mon Sep 17 00:00:00 2001 From: Richard Holzeis Date: Thu, 9 May 2024 13:51:41 +0200 Subject: [PATCH 8/9] refactor: Use only a single process dlc event function --- coordinator/src/db/dlc_channels.rs | 2 +- coordinator/src/node/channel.rs | 338 +++++++++++++---------------- 2 files changed, 154 insertions(+), 186 deletions(-) diff --git a/coordinator/src/db/dlc_channels.rs b/coordinator/src/db/dlc_channels.rs index 991ab21e4..598224e30 100644 --- a/coordinator/src/db/dlc_channels.rs +++ b/coordinator/src/db/dlc_channels.rs @@ -191,7 +191,7 @@ pub(crate) fn set_channel_collab_closing( .execute(conn) } -pub(crate) fn set_channel_collab_closed( +pub(crate) fn set_channel_closed( conn: &mut PgConnection, channel_id: &DlcChannelId, close_txid: Txid, diff --git a/coordinator/src/node/channel.rs b/coordinator/src/node/channel.rs index addf7f422..75e653de2 100644 --- a/coordinator/src/node/channel.rs +++ b/coordinator/src/node/channel.rs @@ -11,8 +11,6 @@ use bitcoin::Amount; use bitcoin::ScriptBuf; use bitcoin::Txid; use bitcoin_old::Transaction; -use diesel::r2d2::ConnectionManager; -use diesel::r2d2::PooledConnection; use diesel::PgConnection; use dlc_manager::channel::signed_channel::SignedChannel; use dlc_manager::channel::signed_channel::SignedChannelState; @@ -25,6 +23,7 @@ use dlc_manager::contract::ClosedContract; use dlc_manager::contract::Contract; use dlc_manager::contract::PreClosedContract; use dlc_manager::DlcChannelId; +use dlc_manager::ReferenceId; use rust_decimal::Decimal; use time::OffsetDateTime; use tokio::sync::broadcast::error::RecvError; @@ -101,21 +100,12 @@ impl Node { loop { match receiver.recv().await { Ok(NodeEvent::DlcChannelEvent { dlc_channel_event }) => { - if let Err(e) = node.shadow_dlc_channel(dlc_channel_event) { + if let Err(e) = node.process_dlc_channel_event(dlc_channel_event) { tracing::error!( ?dlc_channel_event, "Failed to process DLC channel event. Error: {e:#}" ); } - - if let Err(e) = - node.check_for_dlc_channel_closures(dlc_channel_event).await - { - tracing::error!( - ?dlc_channel_event, - "Failed to run check for dlc channel closures. Error: {e:}" - ); - } } Ok(NodeEvent::Connected { .. }) | Ok(NodeEvent::SendDlcMessage { .. }) @@ -134,7 +124,7 @@ impl Node { }); } - pub fn shadow_dlc_channel(&self, dlc_channel_event: DlcChannelEvent) -> Result<()> { + pub fn process_dlc_channel_event(&self, dlc_channel_event: DlcChannelEvent) -> Result<()> { let mut conn = self.pool.get()?; let protocol_id = match dlc_channel_event.get_reference_id() { @@ -238,29 +228,7 @@ impl Node { claim_transaction.map(|tx| to_txid_30(tx.txid())), )?; } - DlcChannelEvent::Closing(_) => { - let buffer_transaction = match channel { - Channel::Signed(SignedChannel { - state: - SignedChannelState::Closing { - buffer_transaction, .. - }, - .. - }) => buffer_transaction, - Channel::Closing(ClosingChannel { - buffer_transaction, .. - }) => buffer_transaction, - channel => { - bail!("DLC channel in unexpected state. dlc_channel = {channel:?}") - } - }; - - db::dlc_channels::set_channel_force_closing( - &mut conn, - &channel.get_id(), - to_txid_30(buffer_transaction.txid()), - )?; - } + DlcChannelEvent::Closing(_) => self.handle_closing_event(&mut conn, channel)?, DlcChannelEvent::ClosedPunished(_) => { let punish_txid = match channel { Channel::ClosedPunished(ClosedPunishedChannel { punish_txid, .. }) => { @@ -294,25 +262,11 @@ impl Node { to_txid_30(close_transaction.txid()), )?; } - DlcChannelEvent::Closed(_) - | DlcChannelEvent::CounterClosed(_) - | DlcChannelEvent::CollaborativelyClosed(_) => { - let close_txid = match channel { - Channel::Closed(ClosedChannel { closing_txid, .. }) => closing_txid, - Channel::CounterClosed(ClosedChannel { closing_txid, .. }) => closing_txid, - Channel::CollaborativelyClosed(ClosedChannel { closing_txid, .. }) => { - closing_txid - } - channel => { - bail!("DLC channel in unexpected state. dlc_channel = {channel:?}") - } - }; - - db::dlc_channels::set_channel_collab_closed( - &mut conn, - &channel.get_id(), - to_txid_30(*close_txid), - )?; + DlcChannelEvent::Closed(_) | DlcChannelEvent::CounterClosed(_) => { + self.handle_force_closed_event(&mut conn, channel, protocol_id)? + } + DlcChannelEvent::CollaborativelyClosed(_) => { + self.handle_collaboratively_closed_event(&mut conn, channel, protocol_id)? } DlcChannelEvent::FailedAccept(_) | DlcChannelEvent::FailedSign(_) => { let protocol_id = ProtocolId::try_from(protocol_id)?; @@ -337,147 +291,161 @@ impl Node { Ok(()) } - /// Checks if the dlc channel got closed and updates a potential open position or dlc protocol. - /// - /// If the dlc channel is closing the position will be set to `Closing`, if the dlc channel is - /// closed or counter closed the closing position will be set to closed with a closing price - /// (from the attestation and a trader realized pnl calculated from the cet payout and the - /// last trader reserve) - /// - /// If the dlc channel is `CollaborativelyClosed` we finish the corresponding dlc_protocol. - async fn check_for_dlc_channel_closures( - &self, - dlc_channel_event: DlcChannelEvent, - ) -> Result<()> { - let mut conn = self.pool.get()?; + fn handle_closing_event(&self, conn: &mut PgConnection, channel: &Channel) -> Result<()> { + // If a channel is set to closing it means the buffer transaction got broadcasted, + // which will only happen if the channel got force closed while the + // user had an open position. + let trader_id = channel.get_counter_party_id(); - let reference_id = dlc_channel_event.get_reference_id().with_context(|| format!("Can't process dlc channel event without reference id. dlc_channel_event = {dlc_channel_event:?}"))?; - let protocol_id = ProtocolId::try_from(reference_id)?; + // we do not know the price yet, since we have to wait for the position to expire. + if db::positions::Position::set_open_position_to_closing( + conn, + &to_secp_pk_30(trader_id), + None, + )? > 0 + { + tracing::info!(%trader_id, "Set open position to closing after the dlc channel got force closed."); + } - match dlc_channel_event { - // If a channel is set to closing it means the buffer transaction got broadcasted, which - // will only happen if the channel got force closed while the user had an open position. - DlcChannelEvent::Closing(_) => { - let channel = &self.inner.get_dlc_channel_by_reference_id(reference_id)?; - let trader_id = channel.get_counter_party_id(); - - // we do not know the price yet, since we have to wait for the position to expire. - if db::positions::Position::set_open_position_to_closing( - &mut conn, - &to_secp_pk_30(trader_id), - None, - )? > 0 - { - tracing::info!(%trader_id, "Set open position to closing after the dlc channel got force closed."); - } + let buffer_transaction = match channel { + Channel::Signed(SignedChannel { + state: + SignedChannelState::Closing { + buffer_transaction, .. + }, + .. + }) => buffer_transaction, + Channel::Closing(ClosingChannel { + buffer_transaction, .. + }) => buffer_transaction, + channel => { + bail!("DLC channel in unexpected state. dlc_channel = {channel:?}") } - // A dlc channel is set to `Closed` or `CounterClosed` if the CET got broadcasted. The - // underlying contract is either `PreClosed` or `Closed` depending on the CET - // confirmations. - DlcChannelEvent::Closed(_) | DlcChannelEvent::CounterClosed(_) => { - let dlc_protocol = db::dlc_protocols::get_dlc_protocol(&mut conn, protocol_id)?; - let contract_id = &dlc_protocol.contract_id.context("Missing contract id")?; - let trader_id = dlc_protocol.trader; - let contract = self - .inner - .get_contract_by_id(contract_id)? - .context("Missing contract")?; + }; - let position = db::positions::Position::get_position_by_trader( - &mut conn, - trader_id, - /* the closing price doesn't matter here. */ - vec![PositionState::Closing { closing_price: 0.0 }], - )? - .with_context(|| { - format!("Couldn't find closing position for trader. trader_id = {trader_id}") - })?; - - let (closing_price, trader_realized_pnl_sat) = match contract { - Contract::PreClosed(PreClosedContract { - // We assume a closed contract does always have an attestation - attestations: Some(attestations), - signed_cet, - .. - }) - | Contract::Closed(ClosedContract { - // We assume a closed contract does always have an attestation - attestations: Some(attestations), - signed_cet: Some(signed_cet), - .. - }) => { - let trader_realized_pnl_sat = self.calculate_trader_realized_pnl_from_cet( - &mut conn, - &dlc_protocol.channel_id, - signed_cet, - )?; + db::dlc_channels::set_channel_force_closing( + conn, + &channel.get_id(), + to_txid_30(buffer_transaction.txid()), + )?; - let closing_price = Decimal::from_str_radix( - &attestations - .first() - .context("at least one attestation")? - .outcomes - .join(""), - 2, - )?; + Ok(()) + } - (closing_price, trader_realized_pnl_sat) - } - contract => { - bail!("Contract in unexpected state. Expected PreClosed or Closed Got: {:?}, trader_id = {trader_id}", contract) - } - }; + fn handle_force_closed_event( + &self, + conn: &mut PgConnection, + channel: &Channel, + reference_id: ReferenceId, + ) -> Result<()> { + let protocol_id = ProtocolId::try_from(reference_id)?; + let dlc_protocol = db::dlc_protocols::get_dlc_protocol(conn, protocol_id)?; + let contract_id = &dlc_protocol.contract_id.context("Missing contract id")?; + let trader_id = dlc_protocol.trader; + let contract = self + .inner + .get_contract_by_id(contract_id)? + .context("Missing contract")?; + + let position = db::positions::Position::get_position_by_trader( + conn, + trader_id, + /* the closing price doesn't matter here. */ + vec![PositionState::Closing { closing_price: 0.0 }], + )? + .with_context(|| { + format!("Couldn't find closing position for trader. trader_id = {trader_id}") + })?; + + let (closing_price, trader_realized_pnl_sat) = match contract { + Contract::PreClosed(PreClosedContract { + // We assume a closed contract does always have an attestation + attestations: Some(attestations), + signed_cet, + .. + }) + | Contract::Closed(ClosedContract { + // We assume a closed contract does always have an attestation + attestations: Some(attestations), + signed_cet: Some(signed_cet), + .. + }) => { + let trader_realized_pnl_sat = self.calculate_trader_realized_pnl_from_cet( + conn, + &dlc_protocol.channel_id, + signed_cet, + )?; - tracing::debug!( - ?position, - %trader_id, - "Finalize closing position after force closure", - ); + let closing_price = Decimal::from_str_radix( + &attestations + .first() + .context("at least one attestation")? + .outcomes + .join(""), + 2, + )?; - if db::positions::Position::set_position_to_closed_with_pnl( - &mut conn, - position.id, - trader_realized_pnl_sat, - closing_price, - )? > 0 - { - tracing::info!(%trader_id, "Set closing position to closed after the dlc channel got force closed."); - } else { - tracing::warn!(%trader_id, "Failed to set closing position to closed after the dlc channel got force closed."); - } + (closing_price, trader_realized_pnl_sat) } - DlcChannelEvent::CollaborativelyClosed(_) => { - let channel = &self.inner.get_dlc_channel_by_reference_id(reference_id)?; - let protocol_executor = dlc_protocol::DlcProtocolExecutor::new(self.pool.clone()); - protocol_executor.finish_dlc_protocol( - protocol_id, - &to_secp_pk_30(channel.get_counter_party_id()), - None, - &channel.get_id(), - self.tx_position_feed.clone(), - )?; + contract => { + bail!("Contract in unexpected state. Expected PreClosed or Closed Got: {:?}, trader_id = {trader_id}", contract) } - DlcChannelEvent::Offered(_) - | DlcChannelEvent::Accepted(_) - | DlcChannelEvent::Established(_) - | DlcChannelEvent::SettledOffered(_) - | DlcChannelEvent::SettledReceived(_) - | DlcChannelEvent::SettledAccepted(_) - | DlcChannelEvent::SettledConfirmed(_) - | DlcChannelEvent::Settled(_) - | DlcChannelEvent::SettledClosing(_) - | DlcChannelEvent::RenewOffered(_) - | DlcChannelEvent::RenewAccepted(_) - | DlcChannelEvent::RenewConfirmed(_) - | DlcChannelEvent::RenewFinalized(_) - | DlcChannelEvent::CollaborativeCloseOffered(_) - | DlcChannelEvent::ClosedPunished(_) - | DlcChannelEvent::FailedAccept(_) - | DlcChannelEvent::FailedSign(_) - | DlcChannelEvent::Cancelled(_) - | DlcChannelEvent::Deleted(_) => {} // ignored + }; + + tracing::debug!( + ?position, + %trader_id, + "Finalize closing position after force closure", + ); + + if db::positions::Position::set_position_to_closed_with_pnl( + conn, + position.id, + trader_realized_pnl_sat, + closing_price, + )? > 0 + { + tracing::info!(%trader_id, "Set closing position to closed after the dlc channel got force closed."); + } else { + tracing::warn!(%trader_id, "Failed to set closing position to closed after the dlc channel got force closed."); } + let close_txid = match channel { + Channel::Closed(ClosedChannel { closing_txid, .. }) => closing_txid, + Channel::CounterClosed(ClosedChannel { closing_txid, .. }) => closing_txid, + channel => { + bail!("DLC channel in unexpected state. dlc_channel = {channel:?}") + } + }; + + db::dlc_channels::set_channel_closed(conn, &channel.get_id(), to_txid_30(*close_txid))?; + Ok(()) + } + + fn handle_collaboratively_closed_event( + &self, + conn: &mut PgConnection, + channel: &Channel, + reference_id: ReferenceId, + ) -> Result<()> { + let protocol_executor = dlc_protocol::DlcProtocolExecutor::new(self.pool.clone()); + protocol_executor.finish_dlc_protocol( + ProtocolId::try_from(reference_id)?, + &to_secp_pk_30(channel.get_counter_party_id()), + None, + &channel.get_id(), + self.tx_position_feed.clone(), + )?; + + let close_txid = match channel { + Channel::CollaborativelyClosed(ClosedChannel { closing_txid, .. }) => closing_txid, + channel => { + bail!("DLC channel in unexpected state. dlc_channel = {channel:?}") + } + }; + + db::dlc_channels::set_channel_closed(conn, &channel.get_id(), to_txid_30(*close_txid))?; + Ok(()) } @@ -486,7 +454,7 @@ impl Node { /// 2. Subtract the trader reserve sats from the trader payout fn calculate_trader_realized_pnl_from_cet( &self, - conn: &mut PooledConnection>, + conn: &mut PgConnection, channel_id: &DlcChannelId, signed_cet: Transaction, ) -> Result { From a39a6b1cbde9477e769f58d7663382408219205e Mon Sep 17 00:00:00 2001 From: Richard Holzeis Date: Thu, 9 May 2024 14:02:10 +0200 Subject: [PATCH 9/9] chore: Add updated generated files --- mobile/ios/Podfile.lock | 20 +++++++++++++++++++ .../Flutter/GeneratedPluginRegistrant.swift | 2 ++ 2 files changed, 22 insertions(+) diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 9feac5c96..c1dacdf7c 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -32,6 +32,13 @@ PODS: - GoogleUtilities/UserDefaults (~> 7.8) - nanopb (< 2.30910.0, >= 2.30908.0) - Flutter (1.0.0) + - flutter_inappwebview_ios (0.0.1): + - Flutter + - flutter_inappwebview_ios/Core (= 0.0.1) + - OrderedSet (~> 5.0) + - flutter_inappwebview_ios/Core (0.0.1): + - Flutter + - OrderedSet (~> 5.0) - flutter_local_notifications (0.0.1): - Flutter - flutter_native_splash (0.0.1): @@ -63,6 +70,7 @@ PODS: - nanopb/encode (= 2.30909.0) - nanopb/decode (2.30909.0) - nanopb/encode (2.30909.0) + - OrderedSet (5.0.0) - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): @@ -81,11 +89,14 @@ PODS: - Flutter - url_launcher_ios (0.0.1): - Flutter + - webview_flutter_wkwebview (0.0.1): + - Flutter DEPENDENCIES: - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - Flutter (from `Flutter`) + - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) @@ -95,6 +106,7 @@ DEPENDENCIES: - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - social_share (from `.symlinks/plugins/social_share/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) SPEC REPOS: trunk: @@ -107,6 +119,7 @@ SPEC REPOS: - GoogleUtilities - MTBBarcodeScanner - nanopb + - OrderedSet - PromisesObjC EXTERNAL SOURCES: @@ -116,6 +129,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/firebase_messaging/ios" Flutter: :path: Flutter + flutter_inappwebview_ios: + :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" flutter_local_notifications: :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_native_splash: @@ -134,6 +149,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/social_share/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" + webview_flutter_wkwebview: + :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" SPEC CHECKSUMS: Firebase: 07150e75d142fb9399f6777fa56a187b17f833a0 @@ -144,12 +161,14 @@ SPEC CHECKSUMS: FirebaseInstallations: cae95cab0f965ce05b805189de1d4c70b11c76fb FirebaseMessaging: bb2c4f6422a753038fe137d90ae7c1af57251316 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0 flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef GoogleDataTransport: 54dee9d48d14580407f8f5fbf2f496e92437a2f2 GoogleUtilities: 13e2c67ede716b8741c7989e26893d151b2b2084 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 + OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 @@ -158,6 +177,7 @@ SPEC CHECKSUMS: shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 social_share: 702a5e3842addd22db515aa9e1e00a4b80a0296d url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 + webview_flutter_wkwebview: be0f0d33777f1bfd0c9fdcb594786704dbf65f36 PODFILE CHECKSUM: cc1f88378b4bfcf93a6ce00d2c587857c6008d3b diff --git a/mobile/macos/Flutter/GeneratedPluginRegistrant.swift b/mobile/macos/Flutter/GeneratedPluginRegistrant.swift index e37187323..e21a78c9c 100644 --- a/mobile/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/mobile/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,6 +7,7 @@ import Foundation import firebase_core import firebase_messaging +import flutter_inappwebview_macos import flutter_local_notifications import package_info_plus import path_provider_foundation @@ -17,6 +18,7 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) + InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))