diff --git a/coordinator/src/admin.rs b/coordinator/src/admin.rs index d22d58db1..791a9f88c 100644 --- a/coordinator/src/admin.rs +++ b/coordinator/src/admin.rs @@ -405,7 +405,7 @@ pub async fn close_channel( } #[derive(Debug, Deserialize)] -pub struct DeleteDlcChannel { +pub struct Confirmation { #[serde(default, deserialize_with = "empty_string_as_none")] i_know_what_i_am_doing: Option, } @@ -413,10 +413,10 @@ pub struct DeleteDlcChannel { /// This function deletes a DLC channel from our database irreversible! /// If you want to close a channel instead, use `close_channel` #[instrument(skip_all, err(Debug))] -pub async fn delete_dlc_channels( +pub async fn delete_dlc_channel( Path(channel_id_string): Path, State(state): State>, - Query(params): Query, + Query(params): Query, ) -> Result<(), AppError> { if !params.i_know_what_i_am_doing.unwrap_or_default() { let error_message = @@ -447,6 +447,50 @@ pub async fn delete_dlc_channels( Ok(()) } +/// This function attempts to roll back a DLC channel to the last stable state! +/// The action is irreversible, only use if you know what you are doing! +#[instrument(skip_all, err(Debug))] +pub async fn roll_back_dlc_channel( + Path(channel_id_string): Path, + State(state): State>, + Query(params): Query, +) -> Result<(), AppError> { + if !params.i_know_what_i_am_doing.unwrap_or_default() { + let error_message = + "Looks like you don't know what you are doing! Go and ask your supervisor for help!"; + tracing::warn!(error_message); + return Err(AppError::BadRequest(error_message.to_string())); + } + + let channel_id = parse_dlc_channel_id(&channel_id_string) + .map_err(|_| AppError::BadRequest("Provided channel ID was invalid".to_string()))?; + + tracing::info!(channel_id = %channel_id_string, "Attempting to roll back dlc channel to last stable state"); + + let channel = state + .node + .inner + .get_dlc_channel_by_id(&channel_id) + .map_err(|e| AppError::BadRequest(format!("Couldn't find channel. {e:#}")))?; + if let Channel::Signed(signed_channel) = channel { + state + .node + .inner + .roll_back_channel(&signed_channel) + .map_err(|e| { + AppError::InternalServerError(format!("Failed to roll back channel. {e:#}")) + })? + } else { + return Err(AppError::BadRequest( + "It's only possible to rollback a channel in state signed".to_string(), + )); + } + + tracing::info!(channel_id = %channel_id_string, "Rolled back dlc channel"); + + Ok(()) +} + #[instrument(skip_all, err(Debug))] pub async fn sign_message( Path(msg): Path, diff --git a/coordinator/src/routes.rs b/coordinator/src/routes.rs index f690bc009..49a52c641 100644 --- a/coordinator/src/routes.rs +++ b/coordinator/src/routes.rs @@ -1,7 +1,7 @@ use crate::admin::close_channel; use crate::admin::collaborative_revert; use crate::admin::connect_to_peer; -use crate::admin::delete_dlc_channels; +use crate::admin::delete_dlc_channel; use crate::admin::get_balance; use crate::admin::get_fee_rate_estimation; use crate::admin::get_utxos; @@ -9,6 +9,7 @@ use crate::admin::is_connected; use crate::admin::list_dlc_channels; use crate::admin::list_on_chain_transactions; use crate::admin::list_peers; +use crate::admin::roll_back_dlc_channel; use crate::admin::sign_message; use crate::backup::SledBackup; use crate::campaign::post_push_campaign; @@ -170,7 +171,11 @@ pub fn router( .route("/api/admin/dlc_channels", get(list_dlc_channels)) .route( "/api/admin/dlc_channels/:channel_id", - delete(delete_dlc_channels), + delete(delete_dlc_channel), + ) + .route( + "/api/admin/dlc_channels/rollback/:channel_id", + post(roll_back_dlc_channel), ) .route("/api/admin/transactions", get(list_on_chain_transactions)) .route("/api/admin/sign/:msg", get(sign_message)) diff --git a/mobile/lib/common/settings/emergency_kit_screen.dart b/mobile/lib/common/settings/emergency_kit_screen.dart index 5095a801f..971f9e448 100644 --- a/mobile/lib/common/settings/emergency_kit_screen.dart +++ b/mobile/lib/common/settings/emergency_kit_screen.dart @@ -181,6 +181,25 @@ class _EmergencyKitScreenState extends State { goRouter.pop(); }), const SizedBox(height: 30), + EmergencyKitButton( + icon: const Icon(FontAwesomeIcons.backwardStep), + title: "Rollback channel state", + onPressed: () async { + final messenger = ScaffoldMessenger.of(context); + final orderChangeNotifier = context.read(); + final goRouter = GoRouter.of(context); + + try { + await rust.api.rollBackChannelState(); + await orderChangeNotifier.initialize(); + showSnackBar(messenger, "Successfully rolled back channel state"); + } catch (e) { + showSnackBar(messenger, "Failed to rollback channel state. Error: $e"); + } + + goRouter.pop(); + }), + const SizedBox(height: 30), Visibility( visible: config.network == "regtest", child: EmergencyKitButton( diff --git a/mobile/native/src/api.rs b/mobile/native/src/api.rs index 8e6a5eb9e..7149d5f0c 100644 --- a/mobile/native/src/api.rs +++ b/mobile/native/src/api.rs @@ -819,3 +819,10 @@ pub fn get_new_random_name() -> SyncReturn { pub async fn update_nickname(nickname: String) -> Result<()> { users::update_username(nickname).await } + +pub fn roll_back_channel_state() -> Result<()> { + tracing::warn!( + "Executing emergency kit! Attempting to rollback channel state to last stable state" + ); + ln_dlc::roll_back_channel_state() +} diff --git a/mobile/native/src/ln_dlc/mod.rs b/mobile/native/src/ln_dlc/mod.rs index 9a59e352a..01ae2ace5 100644 --- a/mobile/native/src/ln_dlc/mod.rs +++ b/mobile/native/src/ln_dlc/mod.rs @@ -1057,3 +1057,14 @@ fn confirmation_status_to_status_and_timestamp( (status, timestamp.unix_timestamp() as u64) } + +pub fn roll_back_channel_state() -> Result<()> { + let node = state::get_node(); + + let counterparty_pubkey = config::get_coordinator_info().pubkey; + let signed_channel = node + .inner + .get_signed_channel_by_trader_id(counterparty_pubkey)?; + + node.inner.roll_back_channel(&signed_channel) +}