From c9f951d3bc4dec9cfcc771a31336601ea062a6e7 Mon Sep 17 00:00:00 2001 From: Richard Holzeis Date: Tue, 16 Jan 2024 16:08:40 +0100 Subject: [PATCH 1/2] chore: Publish recover dlc background task This ensures that the app will show a dialog that the dlc protocol got interrupted and is in the process of recovery. --- mobile/native/src/dlc_handler.rs | 34 +++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/mobile/native/src/dlc_handler.rs b/mobile/native/src/dlc_handler.rs index 811b64c4d..c3806cbcd 100644 --- a/mobile/native/src/dlc_handler.rs +++ b/mobile/native/src/dlc_handler.rs @@ -1,4 +1,8 @@ use crate::db; +use crate::event; +use crate::event::BackgroundTask; +use crate::event::EventInternal; +use crate::event::TaskStatus; use crate::ln_dlc::node::NodeStorage; use crate::storage::TenTenOneNodeStorage; use anyhow::Result; @@ -94,10 +98,19 @@ impl DlcHandler { }) => { tracing::info!("Accepting pending dlc channel offer."); // Pending dlc channel offer not yet confirmed on-chain + + event::publish(&EventInternal::BackgroundNotification( + BackgroundTask::RecoverDlc(TaskStatus::Pending), + )); + if let Err(e) = self.node.accept_dlc_channel_offer(temporary_channel_id) { tracing::error!("Failed to accept pending dlc channel offer. {e:#}"); tracing::warn!("Rejecting pending dlc channel offer!"); self.node.reject_dlc_channel_offer(temporary_channel_id)?; + + event::publish(&EventInternal::BackgroundNotification( + BackgroundTask::RecoverDlc(TaskStatus::Success), + )); } return Ok(()); @@ -110,6 +123,11 @@ impl DlcHandler { tracing::info!("Accepting pending dlc channel settle offer."); // Pending dlc channel settle offer with a dlc channel already confirmed // on-chain + + event::publish(&EventInternal::BackgroundNotification( + BackgroundTask::RecoverDlc(TaskStatus::Pending), + )); + self.node .accept_dlc_channel_collaborative_settlement(channel_id)?; @@ -142,8 +160,22 @@ impl DlcHandler { return Ok(()); } + Channel::Signed(signed_channel) => { + // If the signed channel state is anything else but `Established`, `Settled` or + // `Closing` at reconnect. It means the protocol got interrupted. + if !matches!( + signed_channel.state, + SignedChannelState::Established { .. } + | SignedChannelState::Settled { .. } + | SignedChannelState::Closing { .. } + ) { + event::publish(&EventInternal::BackgroundNotification( + BackgroundTask::RecoverDlc(TaskStatus::Pending), + )); + } + } _ => {} - } + }; } let mut conn = db::connection()?; From 5442e106819f3973451f0a782d06388c5faf34ff Mon Sep 17 00:00:00 2001 From: Richard Holzeis Date: Tue, 16 Jan 2024 16:09:11 +0100 Subject: [PATCH 2/2] chore: Delete old recovery and sync code This is not needed anymore due to the new recovery logic. --- mobile/native/src/ln_dlc/mod.rs | 47 +- mobile/native/src/ln_dlc/recover_rollover.rs | 100 ---- .../src/ln_dlc/sync_position_to_subchannel.rs | 548 ------------------ 3 files changed, 1 insertion(+), 694 deletions(-) delete mode 100644 mobile/native/src/ln_dlc/recover_rollover.rs delete mode 100644 mobile/native/src/ln_dlc/sync_position_to_subchannel.rs diff --git a/mobile/native/src/ln_dlc/mod.rs b/mobile/native/src/ln_dlc/mod.rs index aeb6cb025..2d08a9bf1 100644 --- a/mobile/native/src/ln_dlc/mod.rs +++ b/mobile/native/src/ln_dlc/mod.rs @@ -100,8 +100,6 @@ use trade::ContractSymbol; mod lightning_subscriber; pub mod node; -mod recover_rollover; -mod sync_position_to_subchannel; pub mod channel_status; @@ -115,7 +113,6 @@ const PROCESS_INCOMING_DLC_MESSAGES_INTERVAL: Duration = Duration::from_millis(2 const UPDATE_WALLET_HISTORY_INTERVAL: Duration = Duration::from_secs(5); const CHECK_OPEN_ORDERS_INTERVAL: Duration = Duration::from_secs(60); const ON_CHAIN_SYNC_INTERVAL: Duration = Duration::from_secs(300); -const WAIT_FOR_CONNECTING_TO_COORDINATOR: Duration = Duration::from_secs(2); /// Defines a constant from which we treat a transaction as confirmed const NUMBER_OF_CONFIRMATION_FOR_BEING_CONFIRMED: u64 = 1; @@ -383,9 +380,8 @@ pub fn run(seed_dir: String, runtime: &Runtime) -> Result<()> { let node = node.clone(); async move { node.listen_for_lightning_events(event_receiver).await } }); - let coordinator_info = config::get_coordinator_info(); - let coordinator_pk = coordinator_info.pubkey; + let coordinator_info = config::get_coordinator_info(); runtime.spawn({ let node = node.clone(); async move { node.keep_connected(coordinator_info).await } @@ -419,47 +415,6 @@ pub fn run(seed_dir: String, runtime: &Runtime) -> Result<()> { runtime.spawn(track_channel_status(node.clone())); - let inner_node = node.clone(); - - runtime.spawn_blocking(move || { - let node = inner_node.clone(); - async move { - let mut iteration_count = 0; - - while !node - .inner - .peer_manager - .get_peer_node_ids() - .iter() - .any(|(a, _)| a == &coordinator_pk) - { - tracing::trace!( - "Not yet connecting to coordinator. Waiting with recovery for a connection" - ); - - tokio::time::sleep(WAIT_FOR_CONNECTING_TO_COORDINATOR).await; - - iteration_count += 1; - - if iteration_count >= 30 { - // After 30 retries (randonly chosen) we give up and continue with the - // function nevertheless. Which means, we might see - // an error. - break; - } - } - if let Err(e) = node.sync_position_with_subchannel_state().await { - tracing::error!("Failed to sync position with subchannel state. Error: {e:#}"); - } - - if let Err(e) = node.recover_rollover().await { - tracing::error!( - "Failed to check and recover from a stuck rollover state. Error: {e:#}" - ); - } - } - }); - state::set_node(node); event::publish(&EventInternal::Init("10101 is ready.".to_string())); diff --git a/mobile/native/src/ln_dlc/recover_rollover.rs b/mobile/native/src/ln_dlc/recover_rollover.rs deleted file mode 100644 index b3578e089..000000000 --- a/mobile/native/src/ln_dlc/recover_rollover.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::ln_dlc::node::Node; -use anyhow::Result; -use bitcoin::hashes::hex::ToHex; -use ln_dlc_node::node::rust_dlc_manager::channel::signed_channel::SignedChannelState; -use ln_dlc_node::node::rust_dlc_manager::channel::Channel; -use ln_dlc_node::node::rust_dlc_manager::Storage; - -impl Node { - /// Checks and recovers from a potential stuck rollover. - pub async fn recover_rollover(&self) -> Result<()> { - tracing::debug!("Checking if DLC channel got stuck during rollover"); - - let channels = self.inner.channel_manager.list_channels(); - let channel_details = match channels.first() { - Some(channel_details) => channel_details, - None => { - tracing::debug!("No need to recover rollover: no LN channel found"); - return Ok(()); - } - }; - - let subchannels = self.inner.dlc_manager.get_store().get_sub_channels()?; - let subchannel = match subchannels - .iter() - .find(|dlc_channel| dlc_channel.channel_id == channel_details.channel_id) - { - Some(subchannel) => subchannel, - None => { - tracing::debug!("No need to recover rollover: no subchannel found"); - return Ok(()); - } - }; - - let channel_id_hex = subchannel.channel_id.to_hex(); - - let dlc_channel_id = match subchannel.get_dlc_channel_id(0) { - Some(dlc_channel_id) => dlc_channel_id, - None => { - tracing::debug!( - channel_id=%channel_id_hex, - "Cannot consider subchannel for rollover recovery without DLC channel ID" - ); - return Ok(()); - } - }; - - let dlc_channel_id_hex = dlc_channel_id.to_hex(); - - let dlc_channel = self - .inner - .dlc_manager - .get_store() - .get_channel(&dlc_channel_id)?; - - let signed_channel = match dlc_channel { - Some(Channel::Signed(signed_channel)) => signed_channel, - Some(_) => { - tracing::debug!( - channel_id=%channel_id_hex, - "No need to recover rollover: DLC channel not signed" - ); - return Ok(()); - } - None => { - tracing::warn!( - channel_id=%channel_id_hex, - dlc_channel_id=%dlc_channel_id_hex, - "Expected DLC channel associated with subchannel not found" - ); - return Ok(()); - } - }; - - match signed_channel.state { - SignedChannelState::RenewOffered { .. } - | SignedChannelState::RenewAccepted { .. } - | SignedChannelState::RenewConfirmed { .. } - | SignedChannelState::RenewFinalized { .. } => { - let state = ln_dlc_node::node::signed_channel_state_name(&signed_channel); - - tracing::warn!( - state, - "Found signed DLC channel in an intermediate rollover state. \ - Rolling back and expecting coordinator to retry rollover" - ); - - self.inner.rollback_channel(&signed_channel)?; - } - _ => { - tracing::debug!( - signed_channel_state=%signed_channel.state, - "No need to recover rollover: DLC channel is not in an \ - intermediate rollover state" - ) - } - } - - Ok(()) - } -} diff --git a/mobile/native/src/ln_dlc/sync_position_to_subchannel.rs b/mobile/native/src/ln_dlc/sync_position_to_subchannel.rs deleted file mode 100644 index 130887a35..000000000 --- a/mobile/native/src/ln_dlc/sync_position_to_subchannel.rs +++ /dev/null @@ -1,548 +0,0 @@ -use crate::db; -use crate::event; -use crate::event::BackgroundTask; -use crate::event::EventInternal; -use crate::event::TaskStatus; -use crate::ln_dlc::node::decide_subchannel_offer_action; -use crate::ln_dlc::node::Node; -use crate::trade::order; -use crate::trade::position; -use anyhow::Context; -use anyhow::Result; -use commons::order_matching_fee_taker; -use lightning::ln::ChannelId; -use ln_dlc_node::node::rust_dlc_manager::contract::Contract; -use ln_dlc_node::node::rust_dlc_manager::subchannel; -use ln_dlc_node::node::rust_dlc_manager::Storage; -use rust_decimal::Decimal; -use std::time::Duration; -use time::OffsetDateTime; - -#[derive(PartialEq, Clone, Debug)] -enum Action { - ContinueSubchannelProtocol, - CreatePosition(ChannelId), - RemovePosition, - ProcessPendingOffer, -} - -impl Node { - /// Sync the position with the subchannel state. - /// - /// TODO(holzeis): With https://github.com/get10101/10101/issues/530 we should not require this - /// logic anymore. - /// - /// - [`SubChannelState::Signed`] but no position: create position from `Filling` order. - /// - /// - [`SubChannelState::OffChainClosed`] and a position exists: delete the position. - /// - /// - [`SubChannelState::CloseOffered`] or [`SubChannelState::CloseAccepted`]: inform the UI - /// that the subchannel is being recovered. - /// - /// - [`SubChannelState::Offered`], [`SubChannelState::Accepted`] or - /// [`SubChannelState::Finalized`]: inform the UI that the subchannel is being recovered. - /// - /// - Subchannel in any other state, with position: delete position because the channel might - /// have been force-closed. - pub async fn sync_position_with_subchannel_state(&self) -> Result<()> { - tracing::info!("Syncing position with subchannel state"); - - let channels = self.inner.channel_manager.list_channels(); - - let positions = db::get_positions()?; - let first_position = positions.first(); - - let channel_details = match channels.first() { - Some(channel_details) => channel_details, - None => { - // If we don't have a channel but we do have a position, we can safely close said - // position. - tracing::warn!("We found a position but no channel... closing position"); - if first_position.is_some() { - close_position_with_order()?; - } - return Ok(()); - } - }; - - let subchannels = self.inner.dlc_manager.get_store().get_sub_channels()?; - let orig_subchannel = subchannels - .iter() - .find(|subchannel| subchannel.channel_id == channel_details.channel_id); - - let position = first_position.map(Position::from); - let subchannel = orig_subchannel.map(SubChannel::from); - - match determine_sync_position_to_subchannel_action(position, subchannel) { - Some(Action::ContinueSubchannelProtocol) => self.recover_subchannel().await?, - Some(Action::CreatePosition(channel_id)) => match order::handler::order_filled() { - Ok(order) => { - let execution_price = order - .execution_price() - .expect("filled order to have a price"); - let open_position_fee = order_matching_fee_taker( - order.quantity, - Decimal::try_from(execution_price)?, - ); - - let (accept_collateral, expiry_timestamp) = self - .inner - .get_collateral_and_expiry_for_confirmed_contract(channel_id)?; - - position::handler::update_position_after_dlc_creation( - order, - accept_collateral - open_position_fee.to_sat(), - expiry_timestamp, - )?; - - tracing::info!("Successfully recovered position from order"); - } - Err(e) => { - tracing::error!( - "Could not recover position from order as no filling order was found. \ - Error: {e:#}" - ); - } - }, - Some(Action::RemovePosition) => { - close_position_with_order()?; - } - None => { - tracing::debug!("No action needed"); - } - Some(Action::ProcessPendingOffer) => { - let channel = - orig_subchannel.context("No subchannel found, can't process pending offer")?; - if let subchannel::SubChannelState::Offered(_) = &channel.state { - let contract = self.inner.get_contract_by_dlc_channel_id( - &channel - .get_dlc_channel_id(0) - .expect("Expect to get dlc_channel id"), - )?; - if let Contract::Offered(offer) = contract { - tracing::debug!(?offer, "We are having a pending subchannel offer."); - let contract_info = offer - .contract_info - .first() - .context("Subchannel offer nit not have contract info")?; - - let oracle_announcement = contract_info - .oracle_announcements - .first() - .context("oracle announcement to exist on an offered contract")?; - - let expiry_timestamp = OffsetDateTime::from_unix_timestamp( - oracle_announcement.oracle_event.event_maturity_epoch as i64, - )?; - - let action = decide_subchannel_offer_action(expiry_timestamp); - - match self.process_subchannel_offer(channel.channel_id, action) { - Ok(_) => { - tracing::info!("Successfully processed subchannel offer"); - } - Err(error) => { - tracing::error!("Could not process subchannel offer {error:#}"); - } - } - } - } - } - } - - Ok(()) - } - - /// Sends a [`BackgroundNotification::RecoverDlc`] to the UI, so that the UI can convey that the - /// subchannel protocol is still in progress. Also triggers the `periodic_check` on the - /// `SubChannelManager` to process any relevant actions that might have been created after the - /// channel reestablishment. - /// - /// FIXME(holzeis): We currently use different events to inform about the recovery of a - /// subchannel and to inform about the wait for an order execution in the happy case (without a - /// restart in between). Those events and dialogs should be aligned. - async fn recover_subchannel(&self) -> Result<()> { - tracing::warn!("App probably closed whilst executing subchannel protocol"); - event::publish(&EventInternal::BackgroundNotification( - BackgroundTask::RecoverDlc(TaskStatus::Pending), - )); - - // HACK(holzeis): We are manually calling the periodic check here to speed up the - // processing of pending actions. - // - // Note: this might not speed up the process, as the coordinator might have to first resend - // a message to continue the protocol. This should be fixed in `rust-dlc` and any pending - // actions should be processed immediately once the channel is ready instead of periodically - // checking if a pending action needs to be processed. - // - // Note: pending actions can only get created on channel reestablishment, hence we are - // waiting for an arbitrary 5 seconds here to increase the likelihood that the channel has - // been reestablished. - tokio::time::sleep(Duration::from_secs(5)).await; - if let Err(e) = self.inner.sub_channel_manager_periodic_check().await { - tracing::error!("Failed to process periodic check! Error: {e:#}"); - } - - Ok(()) - } -} - -/// Determine what needs to be done, if anything, to keep the [`Position`] and [`SubChannel`] in -/// sync. -/// -/// ### Returns -/// -/// - [`Action::ContinueSubchannelProtocol`] if the subchannel is in an intermediate state. -/// -/// - [`Action::CreatePosition`] if the subchannel is in [`SubChannelState::Signed`], but no -/// position exists. -/// -/// - [`Action::RemovePosition`] if a position exists and the subchannel is _not_ in -/// [`SubChannelState::Signed`] or an intermediate state. -/// -/// - `None` otherwise. -fn determine_sync_position_to_subchannel_action( - position: Option, - subchannel: Option, -) -> Option { - use SubChannel::*; - - tracing::debug!( - ?position, - ?subchannel, - "Checking if position and subchannel state are out of sync" - ); - - if let Some(Position::Resizing) = position { - let action = match subchannel { - // Ongoing resize protocol. - Some(Offered) | Some(Accepted) | Some(Confirmed) | Some(Finalized) - | Some(CloseOffered) | Some(CloseAccepted) | Some(CloseConfirmed) - | Some(OffChainClosed) => { - tracing::debug!("Letting subchannel resize protocol continue"); - Some(Action::ContinueSubchannelProtocol) - } - // Closed on-chain. - Some(Closing) - | Some(OnChainClosed) - | Some(CounterOnChainClosed) - | Some(ClosedPunished) => { - tracing::warn!("Deleting resizing position as subchannel is closed on-chain"); - Some(Action::RemovePosition) - } - // This is _not_ the same as having an `OffChainClosed` subchannel. - None => { - tracing::warn!("Deleting resizing position as subchannel does not exist"); - Some(Action::RemovePosition) - } - // The offer to reopen the subchannel was rejected. It's probably best to remove the - // position. - Some(Rejected) => { - tracing::warn!("Deleting resizing position as subchannel reopen was rejected"); - Some(Action::RemovePosition) - } - // This is weird. - // - // TODO: Consider setting the `Position` back to `Open`. - Some(Signed { .. }) => { - tracing::warn!("Subchannel is signed but position is resizing"); - None - } - }; - - return action; - } - - match (position, subchannel) { - (Some(_), Some(subchannel)) => { - match subchannel { - Signed { .. } => { - tracing::debug!("Subchannel and position are in sync"); - None - } - CloseOffered | CloseAccepted => { - tracing::debug!("Letting subchannel close protocol continue"); - Some(Action::ContinueSubchannelProtocol) - } - OffChainClosed => { - tracing::warn!("Deleting position as subchannel is already closed"); - Some(Action::RemovePosition) - } - Offered | Accepted | Confirmed | Finalized | Closing | OnChainClosed - | CounterOnChainClosed | CloseConfirmed | ClosedPunished | Rejected => { - tracing::warn!( - "The subchannel is in a state that cannot be recovered. Removing position" - ); - // Maybe a leftover after a force-closure. - Some(Action::RemovePosition) - } - } - } - (None, Some(subchannel)) => match subchannel { - OffChainClosed => { - tracing::debug!("Subchannel and position are in sync"); - None - } - Signed { channel_id } => { - tracing::warn!("Trying to recover position from order"); - Some(Action::CreatePosition(channel_id)) - } - Offered => { - tracing::warn!("We are in an offered state, we need to either accept or reject"); - Some(Action::ProcessPendingOffer) - } - Accepted | Finalized => { - tracing::debug!("Letting subchannel open protocol continue"); - Some(Action::ContinueSubchannelProtocol) - } - Confirmed | Closing | OnChainClosed | CounterOnChainClosed | CloseOffered - | CloseAccepted | CloseConfirmed | ClosedPunished => { - tracing::warn!("The subchannel is in a state that cannot be recovered"); - None - } - Rejected => { - tracing::debug!("The subchannel is in rejected state."); - None - } - }, - (Some(_), None) => { - tracing::warn!("Found position without subchannel. Removing position"); - Some(Action::RemovePosition) - } - _ => None, - } -} - -fn close_position_with_order() -> Result<()> { - let filled_order = order::handler::order_filled().ok(); - position::handler::update_position_after_dlc_closure(filled_order)?; - - Ok(()) -} - -#[derive(Clone, Copy, Debug)] -enum Position { - Open, - Closing, - Rollover, - Resizing, -} - -#[derive(Clone, Copy, Debug)] -enum SubChannel { - Offered, - Accepted, - Confirmed, - Finalized, - Signed { channel_id: ChannelId }, - Closing, - OnChainClosed, - CounterOnChainClosed, - CloseOffered, - CloseAccepted, - CloseConfirmed, - OffChainClosed, - ClosedPunished, - Rejected, -} - -impl From<&position::Position> for Position { - fn from(value: &position::Position) -> Self { - match value.position_state { - position::PositionState::Open => Self::Open, - position::PositionState::Closing => Self::Closing, - position::PositionState::Rollover => Self::Rollover, - position::PositionState::Resizing => Self::Resizing, - } - } -} - -impl From<&crate::ln_dlc::SubChannel> for SubChannel { - fn from(value: &crate::ln_dlc::SubChannel) -> Self { - match value.state { - crate::ln_dlc::SubChannelState::Offered(_) => Self::Offered, - crate::ln_dlc::SubChannelState::Accepted(_) => Self::Accepted, - crate::ln_dlc::SubChannelState::Confirmed(_) => Self::Confirmed, - crate::ln_dlc::SubChannelState::Finalized(_) => Self::Finalized, - crate::ln_dlc::SubChannelState::Signed(_) => Self::Signed { - channel_id: value.channel_id, - }, - crate::ln_dlc::SubChannelState::Closing(_) => Self::Closing, - crate::ln_dlc::SubChannelState::OnChainClosed => Self::OnChainClosed, - crate::ln_dlc::SubChannelState::CounterOnChainClosed => Self::CounterOnChainClosed, - crate::ln_dlc::SubChannelState::CloseOffered(_) => Self::CloseOffered, - crate::ln_dlc::SubChannelState::CloseAccepted(_) => Self::CloseAccepted, - crate::ln_dlc::SubChannelState::CloseConfirmed(_) => Self::CloseConfirmed, - crate::ln_dlc::SubChannelState::OffChainClosed => Self::OffChainClosed, - crate::ln_dlc::SubChannelState::ClosedPunished(_) => Self::ClosedPunished, - crate::ln_dlc::SubChannelState::Rejected => Self::Rejected, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_none_position_and_none_subchannel() { - let action = determine_sync_position_to_subchannel_action(None, None); - assert!(action.is_none()); - } - - #[test] - fn test_some_position_and_none_subchannel() { - let action = determine_sync_position_to_subchannel_action(Some(Position::Open), None); - assert_eq!(Some(Action::RemovePosition), action); - } - - #[test] - fn test_some_position_and_offchainclosed_subchannel() { - let action = determine_sync_position_to_subchannel_action(Some(Position::Open), None); - assert_eq!(Some(Action::RemovePosition), action); - } - - #[test] - fn test_some_position_and_signed_subchannel() { - let action = determine_sync_position_to_subchannel_action( - Some(Position::Open), - Some(SubChannel::Signed { - channel_id: dummy_channel_id(), - }), - ); - assert!(action.is_none()); - } - - #[test] - fn test_some_position_and_close_offered_subchannel() { - let action = determine_sync_position_to_subchannel_action( - Some(Position::Open), - Some(SubChannel::CloseOffered), - ); - assert_eq!(Some(Action::ContinueSubchannelProtocol), action); - } - - #[test] - fn test_some_position_and_close_accepted_subchannel() { - let action = determine_sync_position_to_subchannel_action( - Some(Position::Open), - Some(SubChannel::CloseAccepted), - ); - assert_eq!(Some(Action::ContinueSubchannelProtocol), action); - } - - #[test] - fn test_none_position_and_offchainclosed_subchannel() { - let action = - determine_sync_position_to_subchannel_action(None, Some(SubChannel::OffChainClosed)); - assert!(action.is_none()); - } - - #[test] - fn test_none_position_and_signed_subchannel() { - let action = determine_sync_position_to_subchannel_action( - None, - Some(SubChannel::Signed { - channel_id: dummy_channel_id(), - }), - ); - assert!(matches!(action, Some(Action::CreatePosition(_)))); - } - - #[test] - fn test_none_position_and_offered_subchannel() { - let action = determine_sync_position_to_subchannel_action(None, Some(SubChannel::Offered)); - assert_eq!(Some(Action::ProcessPendingOffer), action); - } - - #[test] - fn test_none_position_and_accepted_subchannel() { - let action = determine_sync_position_to_subchannel_action(None, Some(SubChannel::Accepted)); - assert_eq!(Some(Action::ContinueSubchannelProtocol), action); - } - - #[test] - fn test_none_position_and_finalized_subchannel() { - let action = - determine_sync_position_to_subchannel_action(None, Some(SubChannel::Finalized)); - assert_eq!(Some(Action::ContinueSubchannelProtocol), action); - } - - #[test] - fn test_none_position_and_other_subchannel_state() { - let action = - determine_sync_position_to_subchannel_action(None, Some(SubChannel::OnChainClosed)); - assert!(action.is_none()); - } - - #[test] - fn test_some_position_and_other_subchannel_state() { - let action = determine_sync_position_to_subchannel_action( - Some(Position::Open), - Some(SubChannel::OnChainClosed), - ); - assert_eq!(Some(Action::RemovePosition), action); - } - - #[test] - fn test_resizing_position() { - use SubChannel::*; - - let position = Some(Position::Resizing); - - let action = determine_sync_position_to_subchannel_action(position, Some(Offered)); - assert_eq!(Some(Action::ContinueSubchannelProtocol), action); - - let action = determine_sync_position_to_subchannel_action(position, Some(Accepted)); - assert_eq!(Some(Action::ContinueSubchannelProtocol), action); - - let action = determine_sync_position_to_subchannel_action(position, Some(Confirmed)); - assert_eq!(Some(Action::ContinueSubchannelProtocol), action); - - let action = determine_sync_position_to_subchannel_action(position, Some(Finalized)); - assert_eq!(Some(Action::ContinueSubchannelProtocol), action); - - let action = determine_sync_position_to_subchannel_action(position, Some(CloseOffered)); - assert_eq!(Some(Action::ContinueSubchannelProtocol), action); - - let action = determine_sync_position_to_subchannel_action(position, Some(CloseAccepted)); - assert_eq!(Some(Action::ContinueSubchannelProtocol), action); - - let action = determine_sync_position_to_subchannel_action(position, Some(CloseConfirmed)); - assert_eq!(Some(Action::ContinueSubchannelProtocol), action); - - let action = determine_sync_position_to_subchannel_action(position, Some(OffChainClosed)); - assert_eq!(Some(Action::ContinueSubchannelProtocol), action); - - let action = determine_sync_position_to_subchannel_action(position, Some(Closing)); - assert_eq!(Some(Action::RemovePosition), action); - - let action = determine_sync_position_to_subchannel_action(position, Some(OnChainClosed)); - assert_eq!(Some(Action::RemovePosition), action); - - let action = - determine_sync_position_to_subchannel_action(position, Some(CounterOnChainClosed)); - assert_eq!(Some(Action::RemovePosition), action); - - let action = determine_sync_position_to_subchannel_action(position, Some(ClosedPunished)); - assert_eq!(Some(Action::RemovePosition), action); - - let action = determine_sync_position_to_subchannel_action(position, None); - assert_eq!(Some(Action::RemovePosition), action); - - let action = determine_sync_position_to_subchannel_action(position, Some(Rejected)); - assert_eq!(Some(Action::RemovePosition), action); - - let action = determine_sync_position_to_subchannel_action( - position, - Some(Signed { - channel_id: dummy_channel_id(), - }), - ); - assert!(action.is_none()); - } - - fn dummy_channel_id() -> ChannelId { - ChannelId([0; 32]) - } -}