diff --git a/lightning-liquidity/src/lsps2/service.rs b/lightning-liquidity/src/lsps2/service.rs index abab51366ff..94eb6965edf 100644 --- a/lightning-liquidity/src/lsps2/service.rs +++ b/lightning-liquidity/src/lsps2/service.rs @@ -11,6 +11,7 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; +use bitcoin::Transaction; use core::ops::Deref; use core::sync::atomic::{AtomicUsize, Ordering}; @@ -106,6 +107,79 @@ struct ForwardPaymentAction(ChannelId, FeePayment); #[derive(Debug, PartialEq)] struct ForwardHTLCsAction(ChannelId, Vec); +#[derive(Debug, Clone)] +enum TrustModel { + ClientTrustsLsp { + funding_tx_broadcast_safe: bool, + htlc_claimed: bool, + funding_tx: Option, + }, + LspTrustsClient, +} + +impl TrustModel { + fn should_broadcast(&self) -> bool { + match self { + TrustModel::ClientTrustsLsp { funding_tx_broadcast_safe, htlc_claimed, funding_tx } => { + *funding_tx_broadcast_safe && *htlc_claimed && funding_tx.is_some() + }, + TrustModel::LspTrustsClient => false, + } + } + + fn new(client_trusts_lsp: bool) -> Self { + if client_trusts_lsp { + return TrustModel::ClientTrustsLsp { + funding_tx_broadcast_safe: false, + htlc_claimed: false, + funding_tx: None, + }; + } else { + return TrustModel::LspTrustsClient; + }; + } + + fn set_funding_tx(&mut self, funding_tx: Transaction) { + match self { + TrustModel::ClientTrustsLsp { funding_tx: tx, .. } => { + *tx = Some(funding_tx); + }, + TrustModel::LspTrustsClient => { + // No-op + }, + } + } + + fn set_funding_tx_broadcast_safe(&mut self, funding_tx_broadcast_safe: bool) { + match self { + TrustModel::ClientTrustsLsp { funding_tx_broadcast_safe: safe, .. } => { + *safe = funding_tx_broadcast_safe; + }, + TrustModel::LspTrustsClient => { + // No-op + }, + } + } + + fn set_htlc_claimed(&mut self, htlc_claimed: bool) { + match self { + TrustModel::ClientTrustsLsp { htlc_claimed: claimed, .. } => { + *claimed = htlc_claimed; + }, + TrustModel::LspTrustsClient => { + // No-op + }, + } + } + + fn get_funding_tx(&self) -> Option { + match self { + TrustModel::ClientTrustsLsp { funding_tx, .. } => funding_tx.clone(), + TrustModel::LspTrustsClient => None, + } + } +} + /// The different states a requested JIT channel can be in. #[derive(Debug)] enum OutboundJITChannelState { @@ -114,7 +188,11 @@ enum OutboundJITChannelState { PendingInitialPayment { payment_queue: PaymentQueue }, /// An initial payment of sufficient size was intercepted to the JIT channel SCID, triggering the /// opening of the channel. We are awaiting the completion of the channel establishment. - PendingChannelOpen { payment_queue: PaymentQueue, opening_fee_msat: u64 }, + PendingChannelOpen { + payment_queue: PaymentQueue, + opening_fee_msat: u64, + trust_model: TrustModel, + }, /// The channel is open and a payment was forwarded while skimming the JIT channel fee. /// No further payments can be forwarded until the pending payment succeeds or fails, as we need /// to know whether the JIT channel fee needs to be skimmed from a next payment or not. @@ -122,15 +200,21 @@ enum OutboundJITChannelState { payment_queue: PaymentQueue, opening_fee_msat: u64, channel_id: ChannelId, + trust_model: TrustModel, }, /// The channel is open, no payment is currently being forwarded, and the JIT channel fee still /// needs to be paid. This state can occur when the initial payment fails, e.g. due to a /// prepayment probe. We are awaiting a next payment of sufficient size to forward and skim the /// JIT channel fee. - PendingPayment { payment_queue: PaymentQueue, opening_fee_msat: u64, channel_id: ChannelId }, + PendingPayment { + payment_queue: PaymentQueue, + opening_fee_msat: u64, + channel_id: ChannelId, + trust_model: TrustModel, + }, /// The channel is open and a payment was successfully forwarded while skimming the JIT channel /// fee. Any subsequent HTLCs can be forwarded without additional logic. - PaymentForwarded { channel_id: ChannelId }, + PaymentForwarded { channel_id: ChannelId, trust_model: TrustModel }, } impl OutboundJITChannelState { @@ -140,7 +224,7 @@ impl OutboundJITChannelState { fn htlc_intercepted( &mut self, opening_fee_params: &LSPS2OpeningFeeParams, payment_size_msat: &Option, - htlc: InterceptedHTLC, + htlc: InterceptedHTLC, client_trusts_lsp: bool, ) -> Result, ChannelStateError> { match self { OutboundJITChannelState::PendingInitialPayment { payment_queue } => { @@ -192,6 +276,7 @@ impl OutboundJITChannelState { *self = OutboundJITChannelState::PendingChannelOpen { payment_queue: core::mem::take(payment_queue), opening_fee_msat, + trust_model: TrustModel::new(client_trusts_lsp), }; let open_channel = HTLCInterceptedAction::OpenChannel(OpenChannelParams { opening_fee_msat, @@ -211,12 +296,17 @@ impl OutboundJITChannelState { } } }, - OutboundJITChannelState::PendingChannelOpen { payment_queue, opening_fee_msat } => { + OutboundJITChannelState::PendingChannelOpen { + payment_queue, + opening_fee_msat, + trust_model, + } => { let mut payment_queue = core::mem::take(payment_queue); payment_queue.add_htlc(htlc); *self = OutboundJITChannelState::PendingChannelOpen { payment_queue, opening_fee_msat: *opening_fee_msat, + trust_model: trust_model.clone(), }; Ok(None) }, @@ -224,6 +314,7 @@ impl OutboundJITChannelState { payment_queue, opening_fee_msat, channel_id, + trust_model, } => { let mut payment_queue = core::mem::take(payment_queue); payment_queue.add_htlc(htlc); @@ -231,6 +322,7 @@ impl OutboundJITChannelState { payment_queue, opening_fee_msat: *opening_fee_msat, channel_id: *channel_id, + trust_model: trust_model.clone(), }; Ok(None) }, @@ -238,6 +330,7 @@ impl OutboundJITChannelState { payment_queue, opening_fee_msat, channel_id, + trust_model, } => { let mut payment_queue = core::mem::take(payment_queue); payment_queue.add_htlc(htlc); @@ -252,6 +345,7 @@ impl OutboundJITChannelState { payment_queue, opening_fee_msat: *opening_fee_msat, channel_id: *channel_id, + trust_model: trust_model.clone(), }; Ok(Some(forward_payment)) } else { @@ -259,13 +353,17 @@ impl OutboundJITChannelState { payment_queue, opening_fee_msat: *opening_fee_msat, channel_id: *channel_id, + trust_model: trust_model.clone(), }; Ok(None) } }, - OutboundJITChannelState::PaymentForwarded { channel_id } => { + OutboundJITChannelState::PaymentForwarded { channel_id, trust_model } => { let forward = HTLCInterceptedAction::ForwardHTLC(*channel_id); - *self = OutboundJITChannelState::PaymentForwarded { channel_id: *channel_id }; + *self = OutboundJITChannelState::PaymentForwarded { + channel_id: *channel_id, + trust_model: trust_model.clone(), + }; Ok(Some(forward)) }, } @@ -275,7 +373,11 @@ impl OutboundJITChannelState { &mut self, channel_id: ChannelId, ) -> Result { match self { - OutboundJITChannelState::PendingChannelOpen { payment_queue, opening_fee_msat } => { + OutboundJITChannelState::PendingChannelOpen { + payment_queue, + opening_fee_msat, + trust_model, + } => { if let Some((_payment_hash, htlcs)) = payment_queue.pop_greater_than_msat(*opening_fee_msat) { @@ -283,10 +385,12 @@ impl OutboundJITChannelState { channel_id, FeePayment { opening_fee_msat: *opening_fee_msat, htlcs }, ); + *self = OutboundJITChannelState::PendingPaymentForward { payment_queue: core::mem::take(payment_queue), opening_fee_msat: *opening_fee_msat, channel_id, + trust_model: trust_model.clone(), }; Ok(forward_payment) } else { @@ -309,6 +413,7 @@ impl OutboundJITChannelState { payment_queue, opening_fee_msat, channel_id, + trust_model, } => { if let Some((_payment_hash, htlcs)) = payment_queue.pop_greater_than_msat(*opening_fee_msat) @@ -321,6 +426,7 @@ impl OutboundJITChannelState { payment_queue: core::mem::take(payment_queue), opening_fee_msat: *opening_fee_msat, channel_id: *channel_id, + trust_model: trust_model.clone(), }; Ok(Some(forward_payment)) } else { @@ -328,6 +434,7 @@ impl OutboundJITChannelState { payment_queue: core::mem::take(payment_queue), opening_fee_msat: *opening_fee_msat, channel_id: *channel_id, + trust_model: trust_model.clone(), }; Ok(None) } @@ -336,16 +443,21 @@ impl OutboundJITChannelState { payment_queue, opening_fee_msat, channel_id, + trust_model, } => { *self = OutboundJITChannelState::PendingPayment { payment_queue: core::mem::take(payment_queue), opening_fee_msat: *opening_fee_msat, channel_id: *channel_id, + trust_model: trust_model.clone(), }; Ok(None) }, - OutboundJITChannelState::PaymentForwarded { channel_id } => { - *self = OutboundJITChannelState::PaymentForwarded { channel_id: *channel_id }; + OutboundJITChannelState::PaymentForwarded { channel_id, trust_model } => { + *self = OutboundJITChannelState::PaymentForwarded { + channel_id: *channel_id, + trust_model: trust_model.clone(), + }; Ok(None) }, state => Err(ChannelStateError(format!( @@ -358,15 +470,26 @@ impl OutboundJITChannelState { fn payment_forwarded(&mut self) -> Result, ChannelStateError> { match self { OutboundJITChannelState::PendingPaymentForward { - payment_queue, channel_id, .. + payment_queue, + channel_id, + trust_model, + .. } => { let mut payment_queue = core::mem::take(payment_queue); let forward_htlcs = ForwardHTLCsAction(*channel_id, payment_queue.clear()); - *self = OutboundJITChannelState::PaymentForwarded { channel_id: *channel_id }; + trust_model.set_htlc_claimed(true); + *self = OutboundJITChannelState::PaymentForwarded { + channel_id: *channel_id, + trust_model: trust_model.clone(), + }; Ok(Some(forward_htlcs)) }, - OutboundJITChannelState::PaymentForwarded { channel_id } => { - *self = OutboundJITChannelState::PaymentForwarded { channel_id: *channel_id }; + OutboundJITChannelState::PaymentForwarded { channel_id, trust_model } => { + trust_model.set_htlc_claimed(true); + *self = OutboundJITChannelState::PaymentForwarded { + channel_id: *channel_id, + trust_model: trust_model.clone(), + }; Ok(None) }, state => Err(ChannelStateError(format!( @@ -375,6 +498,66 @@ impl OutboundJITChannelState { ))), } } + + fn store_funding_transaction( + &mut self, funding_tx: Transaction, + ) -> Result<(), ChannelStateError> { + match self { + OutboundJITChannelState::PendingChannelOpen { trust_model, .. } + | OutboundJITChannelState::PaymentForwarded { trust_model, .. } + | OutboundJITChannelState::PendingPaymentForward { trust_model, .. } + | OutboundJITChannelState::PendingPayment { trust_model, .. } => { + trust_model.set_funding_tx(funding_tx); + + Ok(()) + }, + state => Err(ChannelStateError(format!( + "Store funding transaction when JIT Channel was in state: {:?}", + state + ))), + } + } + + fn store_funding_transaction_broadcast_safe( + &mut self, funding_tx_broadcast_safe: bool, + ) -> Result<(), ChannelStateError> { + match self { + OutboundJITChannelState::PendingChannelOpen { trust_model, .. } + | OutboundJITChannelState::PaymentForwarded { trust_model, .. } + | OutboundJITChannelState::PendingPaymentForward { trust_model, .. } + | OutboundJITChannelState::PendingPayment { trust_model, .. } => { + trust_model.set_funding_tx_broadcast_safe(funding_tx_broadcast_safe); + + Ok(()) + }, + state => Err(ChannelStateError(format!( + "Store funding transaction broadcast safe when JIT Channel was in state: {:?}", + state + ))), + } + } + + fn should_broadcast_funding_transaction(&self) -> bool { + match self { + OutboundJITChannelState::PendingChannelOpen { trust_model, .. } + | OutboundJITChannelState::PaymentForwarded { trust_model, .. } + | OutboundJITChannelState::PendingPaymentForward { trust_model, .. } + | OutboundJITChannelState::PendingPayment { trust_model, .. } => trust_model.should_broadcast(), + OutboundJITChannelState::PendingInitialPayment { .. } => false, + } + } + + fn client_trusts_lsp(&self) -> bool { + match self { + OutboundJITChannelState::PendingChannelOpen { trust_model, .. } + | OutboundJITChannelState::PaymentForwarded { trust_model, .. } + | OutboundJITChannelState::PendingPaymentForward { trust_model, .. } + | OutboundJITChannelState::PendingPayment { trust_model, .. } => { + matches!(trust_model, TrustModel::ClientTrustsLsp { .. }) + }, + OutboundJITChannelState::PendingInitialPayment { .. } => false, + } + } } struct OutboundJITChannel { @@ -382,26 +565,32 @@ struct OutboundJITChannel { user_channel_id: u128, opening_fee_params: LSPS2OpeningFeeParams, payment_size_msat: Option, + client_trusts_lsp: bool, } impl OutboundJITChannel { fn new( payment_size_msat: Option, opening_fee_params: LSPS2OpeningFeeParams, - user_channel_id: u128, + user_channel_id: u128, client_trusts_lsp: bool, ) -> Self { Self { user_channel_id, state: OutboundJITChannelState::new(), opening_fee_params, payment_size_msat, + client_trusts_lsp, } } fn htlc_intercepted( &mut self, htlc: InterceptedHTLC, ) -> Result, LightningError> { - let action = - self.state.htlc_intercepted(&self.opening_fee_params, &self.payment_size_msat, htlc)?; + let action = self.state.htlc_intercepted( + &self.opening_fee_params, + &self.payment_size_msat, + htlc, + self.client_trusts_lsp, + )?; Ok(action) } @@ -432,6 +621,38 @@ impl OutboundJITChannel { let is_expired = is_expired_opening_fee_params(&self.opening_fee_params); self.is_pending_initial_payment() && is_expired } + + fn set_funding_tx(&mut self, funding_tx: Transaction) -> Result<(), LightningError> { + self.state + .store_funding_transaction(funding_tx) + .map_err(|e| LightningError::from(ChannelStateError(e.0))) + } + + fn set_funding_tx_broadcast_safe( + &mut self, funding_tx_broadcast_safe: bool, + ) -> Result<(), LightningError> { + self.state + .store_funding_transaction_broadcast_safe(funding_tx_broadcast_safe) + .map_err(|e| LightningError::from(ChannelStateError(e.0))) + } + + fn should_broadcast_funding_transaction(&self) -> bool { + self.state.should_broadcast_funding_transaction() + } + + fn get_funding_tx(&self) -> Option { + match &self.state { + OutboundJITChannelState::PendingChannelOpen { trust_model, .. } + | OutboundJITChannelState::PaymentForwarded { trust_model, .. } + | OutboundJITChannelState::PendingPaymentForward { trust_model, .. } + | OutboundJITChannelState::PendingPayment { trust_model, .. } => trust_model.get_funding_tx(), + _ => None, + } + } + + fn client_trusts_lsp(&self) -> bool { + self.state.client_trusts_lsp() + } } struct PeerState { @@ -715,6 +936,7 @@ where buy_request.payment_size_msat, buy_request.opening_fee_params, user_channel_id, + client_trusts_lsp, ); peer_state_lock @@ -961,12 +1183,14 @@ where Err(e) => { return Err(APIError::APIMisuseError { err: format!( - "Forwarded payment was not applicable for JIT channel: {}", - e.err - ), + "Forwarded payment was not applicable for JIT channel: {}", + e.err + ), }) }, } + + self.broadcast_transaction_if_applies(&jit_channel); } } else { return Err(APIError::APIMisuseError { @@ -1455,6 +1679,125 @@ where peer_state_lock.is_prunable() == false }); } + + /// Checks if the JIT channel with the given `user_channel_id` needs manual broadcast. + /// Will be true if client_trusts_lsp is set to true + pub fn channel_needs_manual_broadcast( + &self, user_channel_id: u128, counterparty_node_id: &PublicKey, + ) -> Result { + let outer_state_lock = self.per_peer_state.read().unwrap(); + let inner_state_lock = + outer_state_lock.get(counterparty_node_id).ok_or_else(|| APIError::APIMisuseError { + err: format!("No counterparty state for: {}", counterparty_node_id), + })?; + let peer_state = inner_state_lock.lock().unwrap(); + + let intercept_scid = peer_state + .intercept_scid_by_user_channel_id + .get(&user_channel_id) + .copied() + .ok_or_else(|| APIError::APIMisuseError { + err: format!("Could not find a channel with user_channel_id {}", user_channel_id), + })?; + + let jit_channel = peer_state + .outbound_channels_by_intercept_scid + .get(&intercept_scid) + .ok_or_else(|| APIError::APIMisuseError { + err: format!( + "Failed to map intercept_scid {} for user_channel_id {} to a channel.", + intercept_scid, user_channel_id, + ), + })?; + + Ok(jit_channel.client_trusts_lsp()) + } + + /// Called to store the funding transaction for a JIT channel. + /// This should be called when the funding transaction is created but before it's broadcast. + pub fn store_funding_transaction( + &self, user_channel_id: u128, counterparty_node_id: &PublicKey, funding_tx: Transaction, + ) -> Result<(), APIError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + let inner_state_lock = + outer_state_lock.get(counterparty_node_id).ok_or_else(|| APIError::APIMisuseError { + err: format!("No counterparty state for: {}", counterparty_node_id), + })?; + let mut peer_state = inner_state_lock.lock().unwrap(); + + let intercept_scid = peer_state + .intercept_scid_by_user_channel_id + .get(&user_channel_id) + .copied() + .ok_or_else(|| APIError::APIMisuseError { + err: format!("Could not find a channel with user_channel_id {}", user_channel_id), + })?; + + let jit_channel = peer_state + .outbound_channels_by_intercept_scid + .get_mut(&intercept_scid) + .ok_or_else(|| APIError::APIMisuseError { + err: format!( + "Failed to map intercept_scid {} for user_channel_id {} to a channel.", + intercept_scid, user_channel_id, + ), + })?; + + jit_channel + .set_funding_tx(funding_tx) + .map_err(|e| APIError::APIMisuseError { err: e.err.to_string() })?; + + self.broadcast_transaction_if_applies(jit_channel); + Ok(()) + } + + /// Called by ldk-node when the funding transaction is safe to broadcast. + /// This marks the funding_tx_broadcast_safe flag as true for the given user_channel_id. + pub fn funding_tx_broadcast_safe( + &self, user_channel_id: u128, counterparty_node_id: &PublicKey, + ) -> Result<(), APIError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + let inner_state_lock = + outer_state_lock.get(counterparty_node_id).ok_or_else(|| APIError::APIMisuseError { + err: format!("No counterparty state for: {}", counterparty_node_id), + })?; + let mut peer_state = inner_state_lock.lock().unwrap(); + + let intercept_scid = peer_state + .intercept_scid_by_user_channel_id + .get(&user_channel_id) + .copied() + .ok_or_else(|| APIError::APIMisuseError { + err: format!("Could not find a channel with user_channel_id {}", user_channel_id), + })?; + + let jit_channel = peer_state + .outbound_channels_by_intercept_scid + .get_mut(&intercept_scid) + .ok_or_else(|| APIError::APIMisuseError { + err: format!( + "Failed to map intercept_scid {} for user_channel_id {} to a channel.", + intercept_scid, user_channel_id, + ), + })?; + + jit_channel + .set_funding_tx_broadcast_safe(true) + .map_err(|e| APIError::APIMisuseError { err: e.err.to_string() })?; + + self.broadcast_transaction_if_applies(jit_channel); + Ok(()) + } + + fn broadcast_transaction_if_applies(&self, jit_channel: &OutboundJITChannel) { + if jit_channel.should_broadcast_funding_transaction() { + let funding_tx = jit_channel.get_funding_tx(); + + if let Some(funding_tx) = funding_tx { + self.channel_manager.get_cm().unsafe_broadcast_transaction(&funding_tx); + } + } + } } impl LSPSProtocolMessageHandler for LSPS2ServiceHandler @@ -1649,6 +1992,7 @@ mod tests { expected_outbound_amount_msat: 200_000_000, payment_hash: PaymentHash([100; 32]), }, + false, ) .unwrap(); assert!(matches!(state, OutboundJITChannelState::PendingInitialPayment { .. })); @@ -1665,6 +2009,7 @@ mod tests { expected_outbound_amount_msat: 1_000_000, payment_hash: PaymentHash([101; 32]), }, + false, ) .unwrap(); assert!(matches!(state, OutboundJITChannelState::PendingInitialPayment { .. })); @@ -1682,6 +2027,7 @@ mod tests { expected_outbound_amount_msat: 300_000_000, payment_hash: PaymentHash([100; 32]), }, + false, ) .unwrap(); assert!(matches!(state, OutboundJITChannelState::PendingChannelOpen { .. })); @@ -1720,6 +2066,7 @@ mod tests { expected_outbound_amount_msat: 2_000_000, payment_hash: PaymentHash([102; 32]), }, + false, ) .unwrap(); assert!(matches!(state, OutboundJITChannelState::PendingPaymentForward { .. })); @@ -1743,6 +2090,7 @@ mod tests { expected_outbound_amount_msat: 500_000_000, payment_hash: PaymentHash([101; 32]), }, + false, ) .unwrap(); assert!(matches!(state, OutboundJITChannelState::PendingPaymentForward { .. })); @@ -1799,6 +2147,7 @@ mod tests { expected_outbound_amount_msat: 200_000_000, payment_hash: PaymentHash([103; 32]), }, + false, ) .unwrap(); assert!(matches!(state, OutboundJITChannelState::PaymentForwarded { .. })); @@ -1833,6 +2182,7 @@ mod tests { expected_outbound_amount_msat: 500_000_000, payment_hash: PaymentHash([100; 32]), }, + false, ) .unwrap(); assert!(matches!(state, OutboundJITChannelState::PendingChannelOpen { .. })); @@ -1849,6 +2199,7 @@ mod tests { expected_outbound_amount_msat: 600_000_000, payment_hash: PaymentHash([101; 32]), }, + false, ) .unwrap(); assert!(matches!(state, OutboundJITChannelState::PendingChannelOpen { .. })); @@ -1880,6 +2231,7 @@ mod tests { expected_outbound_amount_msat: 500_000_000, payment_hash: PaymentHash([102; 32]), }, + false, ) .unwrap(); assert!(matches!(state, OutboundJITChannelState::PendingPaymentForward { .. })); @@ -1934,6 +2286,7 @@ mod tests { expected_outbound_amount_msat: 200_000_000, payment_hash: PaymentHash([103; 32]), }, + false, ) .unwrap(); assert!(matches!(state, OutboundJITChannelState::PaymentForwarded { .. })); diff --git a/lightning-transaction-sync/Cargo.toml b/lightning-transaction-sync/Cargo.toml index 3b92bbef76e..6f6267a4204 100644 --- a/lightning-transaction-sync/Cargo.toml +++ b/lightning-transaction-sync/Cargo.toml @@ -35,7 +35,7 @@ lightning = { version = "0.2.0", path = "../lightning", default-features = false lightning-macros = { version = "0.2", path = "../lightning-macros", default-features = false } bitcoin = { version = "0.32.2", default-features = false } futures = { version = "0.3", optional = true } -esplora-client = { version = "0.12", default-features = false, optional = true } +esplora-client = { version = "0.11", default-features = false, optional = true } electrum-client = { version = "0.23.1", optional = true, default-features = false, features = ["proxy"] } [dev-dependencies] diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 6a3c066ce14..c0ae5f004b6 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -1029,6 +1029,11 @@ enum FundingType { /// scenario could be when constructing the funding transaction as part of a Payjoin /// transaction. Unchecked(OutPoint), + /// This variant is useful when we want LDK to validate the funding transaction and + /// broadcast it manually. + /// + /// Used in LSPS2 on a client_trusts_lsp model + CheckedManualBroadcast(Transaction), } impl FundingType { @@ -1036,6 +1041,7 @@ impl FundingType { match self { FundingType::Checked(tx) => tx.compute_txid(), FundingType::Unchecked(outp) => outp.txid, + FundingType::CheckedManualBroadcast(tx) => tx.compute_txid(), } } @@ -1048,6 +1054,7 @@ impl FundingType { input: Vec::new(), output: Vec::new(), }, + FundingType::CheckedManualBroadcast(tx) => tx.clone(), } } @@ -1055,6 +1062,7 @@ impl FundingType { match self { FundingType::Checked(_) => false, FundingType::Unchecked(_) => true, + FundingType::CheckedManualBroadcast(_) => true, } } } @@ -5541,6 +5549,18 @@ where } + /// Same as batch_funding_transaction_generated but it does not automatically broadcast the funding transaction + pub fn funding_transaction_generated_manual_broadcast( + &self, temporary_channel_id: ChannelId, counterparty_node_id: PublicKey, + funding_transaction: Transaction, + ) -> Result<(), APIError> { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + self.batch_funding_transaction_generated_intern( + &[(&temporary_channel_id, &counterparty_node_id)], + FundingType::CheckedManualBroadcast(funding_transaction), + ) + } + /// Call this upon creation of a batch funding transaction for the given channels. /// /// Return values are identical to [`Self::funding_transaction_generated`], respective to @@ -5620,7 +5640,7 @@ where let mut output_index = None; let expected_spk = chan.funding.get_funding_redeemscript().to_p2wsh(); let outpoint = match &funding { - FundingType::Checked(tx) => { + FundingType::Checked(tx) | FundingType::CheckedManualBroadcast(tx) => { for (idx, outp) in tx.output.iter().enumerate() { if outp.script_pubkey == expected_spk && outp.value.to_sat() == chan.funding.get_value_satoshis() { if output_index.is_some() { @@ -7445,7 +7465,8 @@ where ComplFunc: FnOnce( Option, bool, - ) -> (Option, Option), + ) + -> (Option, Option), >( &self, prev_hop: HTLCPreviousHopData, payment_preimage: PaymentPreimage, payment_info: Option, completion_action: ComplFunc, @@ -10119,6 +10140,20 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ } } + /// Manually broadcast a transaction using the internal transaction broadcaster. + /// + /// This method should only be used in specific scenarios where manual control + /// over transaction broadcast timing is required (e.g., LSPS2 workflows). + /// + /// # Warning + /// Improper use of this method could lead to channel state inconsistencies. + /// Ensure the transaction being broadcast is valid and expected by LDK. + pub fn unsafe_broadcast_transaction(&self, tx: &Transaction) { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + log_info!(self.logger, "Broadcasting transaction {}", log_tx!(tx)); + self.tx_broadcaster.broadcast_transactions(&[tx]); + } + /// Check whether any channels have finished removing all pending updates after a shutdown /// exchange and can now send a closing_signed. /// Returns whether any closing_signed messages were generated. diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 1a76e682668..d27fe1db2f7 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -1075,7 +1075,7 @@ impl PaymentParameters { } /// A struct for configuring parameters for routing the payment. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct RouteParametersConfig { /// The maximum total fees, in millisatoshi, that may accrue during route finding. ///