Skip to content

Commit 6412945

Browse files
fixup: list of changes:
- improved documentation to explain client_trusts_lsp where appropiate - added a full end to end test that tests the client_trusts_lsp - added a new event BroadcastFundingTransaction so the user can broadcast when necessary - keep refactoring lsps2/service to simplify the code
1 parent bd0892d commit 6412945

File tree

7 files changed

+612
-87
lines changed

7 files changed

+612
-87
lines changed

lightning-liquidity/src/lsps2/event.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,23 @@ pub enum LSPS2ServiceEvent {
160160
/// The intercept short channel id to use in the route hint.
161161
intercept_scid: u64,
162162
},
163+
/// You should broadcast the funding transaction to finalize opening the channel.
164+
///
165+
/// This event is emitted once both [`Event::FundingTxBroadcastSafe`] and the
166+
/// corresponding payment has been successfully claimed.
167+
///
168+
/// Call [`ChannelManager::broadcast_transaction`] with the funding
169+
/// transaction to publish it on-chain.
170+
///
171+
/// [`Event::FundingTxBroadcastSafe`]: lightning::events::Event::FundingTxBroadcastSafe
172+
/// [`Event::PaymentClaimed`]: lightning::events::Event::PaymentClaimed
173+
/// [`ChannelManager::broadcast_transaction`]: lightning::ln::channelmanager::ChannelManager::broadcast_transaction
174+
BroadcastFundingTransaction {
175+
/// The node id of the counterparty.
176+
counterparty_node_id: PublicKey,
177+
/// The user channel id that was used to open the channel.
178+
user_channel_id: u128,
179+
/// The funding transaction to broadcast.
180+
funding_tx: bitcoin::Transaction,
181+
},
163182
}

lightning-liquidity/src/lsps2/msgs.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,14 @@ pub struct LSPS2BuyResponse {
168168
pub jit_channel_scid: LSPS2InterceptScid,
169169
/// The locktime expiry delta the lsp requires.
170170
pub lsp_cltv_expiry_delta: u32,
171-
/// A flag that indicates who is trusting who.
171+
/// Trust model flag (default: false).
172+
///
173+
/// false => "LSP trusts client": LSP immediately (or as soon as safe) broadcasts the
174+
/// funding transaction; client may wait for broadcast / confirmations
175+
/// before revealing the preimage.
176+
/// true => "Client trusts LSP": LSP may defer broadcasting until after the client
177+
/// reveals the preimage; client MUST send the preimage once HTLC(s) are
178+
/// irrevocably committed.
172179
#[serde(default)]
173180
pub client_trusts_lsp: bool,
174181
}

lightning-liquidity/src/lsps2/service.rs

Lines changed: 64 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,6 @@ use crate::lsps0::ser::{
2323
LSPS0_CLIENT_REJECTED_ERROR_CODE,
2424
};
2525
use crate::lsps2::event::LSPS2ServiceEvent;
26-
use crate::lsps2::msgs::{
27-
LSPS2BuyRequest, LSPS2BuyResponse, LSPS2GetInfoRequest, LSPS2GetInfoResponse, LSPS2Message,
28-
LSPS2OpeningFeeParams, LSPS2RawOpeningFeeParams, LSPS2Request, LSPS2Response,
29-
LSPS2_BUY_REQUEST_INVALID_OPENING_FEE_PARAMS_ERROR_CODE,
30-
LSPS2_BUY_REQUEST_PAYMENT_SIZE_TOO_LARGE_ERROR_CODE,
31-
LSPS2_BUY_REQUEST_PAYMENT_SIZE_TOO_SMALL_ERROR_CODE,
32-
LSPS2_GET_INFO_REQUEST_UNRECOGNIZED_OR_STALE_TOKEN_ERROR_CODE,
33-
};
3426
use crate::lsps2::payment_queue::{InterceptedHTLC, PaymentQueue};
3527
use crate::lsps2::utils::{
3628
compute_opening_fee, is_expired_opening_fee_params, is_valid_opening_fee_params,
@@ -52,6 +44,15 @@ use lightning_types::payment::PaymentHash;
5244
use bitcoin::secp256k1::PublicKey;
5345
use bitcoin::Transaction;
5446

47+
use crate::lsps2::msgs::{
48+
LSPS2BuyRequest, LSPS2BuyResponse, LSPS2GetInfoRequest, LSPS2GetInfoResponse, LSPS2Message,
49+
LSPS2OpeningFeeParams, LSPS2RawOpeningFeeParams, LSPS2Request, LSPS2Response,
50+
LSPS2_BUY_REQUEST_INVALID_OPENING_FEE_PARAMS_ERROR_CODE,
51+
LSPS2_BUY_REQUEST_PAYMENT_SIZE_TOO_LARGE_ERROR_CODE,
52+
LSPS2_BUY_REQUEST_PAYMENT_SIZE_TOO_SMALL_ERROR_CODE,
53+
LSPS2_GET_INFO_REQUEST_UNRECOGNIZED_OR_STALE_TOKEN_ERROR_CODE,
54+
};
55+
5556
const MAX_PENDING_REQUESTS_PER_PEER: usize = 10;
5657
const MAX_TOTAL_PENDING_REQUESTS: usize = 1000;
5758
const MAX_TOTAL_PEERS: usize = 100000;
@@ -112,20 +113,21 @@ enum TrustModel {
112113
ClientTrustsLsp {
113114
funding_tx_broadcast_safe: bool,
114115
payment_claimed: bool,
115-
funding_tx: Option<Transaction>,
116+
funding_tx: Option<Arc<Transaction>>,
116117
},
117118
LspTrustsClient,
118119
}
119120

120121
impl TrustModel {
121-
fn should_broadcast(&self) -> bool {
122+
fn should_manually_broadcast(&self) -> bool {
122123
match self {
123124
TrustModel::ClientTrustsLsp {
124125
funding_tx_broadcast_safe,
125126
payment_claimed,
126127
funding_tx,
127128
} => *funding_tx_broadcast_safe && *payment_claimed && funding_tx.is_some(),
128-
TrustModel::LspTrustsClient => true,
129+
// in lsp-trusts-client, the broadcast is automatic, so we never need to manually broadcast.
130+
TrustModel::LspTrustsClient => false,
129131
}
130132
}
131133

@@ -141,7 +143,7 @@ impl TrustModel {
141143
}
142144
}
143145

144-
fn set_funding_tx(&mut self, funding_tx: Transaction) {
146+
fn set_funding_tx(&mut self, funding_tx: Arc<Transaction>) {
145147
match self {
146148
TrustModel::ClientTrustsLsp { funding_tx: tx, .. } => {
147149
*tx = Some(funding_tx);
@@ -174,10 +176,17 @@ impl TrustModel {
174176
}
175177
}
176178

177-
fn get_funding_tx(&self) -> Option<Transaction> {
179+
fn get_funding_tx(&self) -> Option<Arc<Transaction>> {
178180
match self {
179-
TrustModel::ClientTrustsLsp { funding_tx, .. } => funding_tx.clone(),
180-
TrustModel::LspTrustsClient => None,
181+
TrustModel::ClientTrustsLsp { funding_tx: Some(tx), .. } => Some(Arc::clone(&tx)),
182+
_ => None,
183+
}
184+
}
185+
186+
fn is_client_trusts_lsp(&self) -> bool {
187+
match self {
188+
TrustModel::ClientTrustsLsp { .. } => true,
189+
TrustModel::LspTrustsClient => false,
181190
}
182191
}
183192
}
@@ -355,7 +364,6 @@ impl OutboundJITChannelState {
355364
channel_id,
356365
FeePayment { htlcs: entry.htlcs, opening_fee_msat: *opening_fee_msat },
357366
);
358-
359367
*self = OutboundJITChannelState::PendingPaymentForward {
360368
payment_queue: core::mem::take(payment_queue),
361369
opening_fee_msat: *opening_fee_msat,
@@ -453,8 +461,7 @@ struct OutboundJITChannel {
453461
user_channel_id: u128,
454462
opening_fee_params: LSPS2OpeningFeeParams,
455463
payment_size_msat: Option<u64>,
456-
client_trusts_lsp: bool,
457-
trust_model: Option<TrustModel>,
464+
trust_model: TrustModel,
458465
}
459466

460467
impl OutboundJITChannel {
@@ -467,23 +474,15 @@ impl OutboundJITChannel {
467474
state: OutboundJITChannelState::new(),
468475
opening_fee_params,
469476
payment_size_msat,
470-
client_trusts_lsp,
471-
trust_model: None,
477+
trust_model: TrustModel::new(client_trusts_lsp),
472478
}
473479
}
474480

475481
fn htlc_intercepted(
476482
&mut self, htlc: InterceptedHTLC,
477483
) -> Result<Option<HTLCInterceptedAction>, LightningError> {
478-
let was_initial =
479-
matches!(self.state, OutboundJITChannelState::PendingInitialPayment { .. });
480484
let action =
481485
self.state.htlc_intercepted(&self.opening_fee_params, &self.payment_size_msat, htlc)?;
482-
if was_initial && self.trust_model.is_none() {
483-
if !matches!(self.state, OutboundJITChannelState::PendingInitialPayment { .. }) {
484-
self.trust_model = Some(TrustModel::new(self.client_trusts_lsp));
485-
}
486-
}
487486
Ok(action)
488487
}
489488

@@ -502,9 +501,7 @@ impl OutboundJITChannel {
502501
fn payment_forwarded(&mut self) -> Result<Option<ForwardHTLCsAction>, LightningError> {
503502
let action = self.state.payment_forwarded()?;
504503
if action.is_some() {
505-
if let Some(tm) = &mut self.trust_model {
506-
tm.set_payment_claimed(true);
507-
}
504+
self.trust_model.set_payment_claimed(true);
508505
}
509506
Ok(action)
510507
}
@@ -520,43 +517,24 @@ impl OutboundJITChannel {
520517
self.is_pending_initial_payment() && is_expired
521518
}
522519

523-
fn set_funding_tx(&mut self, funding_tx: Transaction) -> Result<(), LightningError> {
524-
if let Some(tm) = &mut self.trust_model {
525-
tm.set_funding_tx(funding_tx);
526-
Ok(())
527-
} else {
528-
Err(LightningError::from(ChannelStateError(
529-
"Store funding transaction when JIT Channel was in invalid state".to_string(),
530-
)))
531-
}
520+
fn set_funding_tx(&mut self, funding_tx: Arc<Transaction>) {
521+
self.trust_model.set_funding_tx(funding_tx);
532522
}
533523

534-
fn set_funding_tx_broadcast_safe(
535-
&mut self, funding_tx_broadcast_safe: bool,
536-
) -> Result<(), LightningError> {
537-
if let Some(tm) = &mut self.trust_model {
538-
tm.set_funding_tx_broadcast_safe(funding_tx_broadcast_safe);
539-
Ok(())
540-
} else {
541-
Err(LightningError::from(ChannelStateError(
542-
"Store funding transaction broadcast safe when JIT Channel was in invalid state"
543-
.to_string(),
544-
)))
545-
}
524+
fn set_funding_tx_broadcast_safe(&mut self, funding_tx_broadcast_safe: bool) {
525+
self.trust_model.set_funding_tx_broadcast_safe(funding_tx_broadcast_safe);
546526
}
547527

548528
fn should_broadcast_funding_transaction(&self) -> bool {
549-
self.trust_model.as_ref().map_or(false, |tm| tm.should_broadcast())
529+
self.trust_model.should_manually_broadcast()
550530
}
551531

552-
fn get_funding_tx(&self) -> Option<Transaction> {
553-
self.trust_model.as_ref().and_then(|tm| tm.get_funding_tx())
532+
fn get_funding_tx(&self) -> Option<Arc<Transaction>> {
533+
self.trust_model.get_funding_tx()
554534
}
555535

556536
fn client_trusts_lsp(&self) -> bool {
557-
self.trust_model
558-
.as_ref()
559-
.map_or(false, |tm| matches!(tm, TrustModel::ClientTrustsLsp { .. }))
537+
self.trust_model.is_client_trusts_lsp()
560538
}
561539
}
562540

@@ -797,6 +775,12 @@ where
797775
///
798776
/// Should be called in response to receiving a [`LSPS2ServiceEvent::BuyRequest`] event.
799777
///
778+
/// `client_trusts_lsp`:
779+
/// * false (default) => "LSP trusts client": LSP broadcasts the funding
780+
/// transaction as soon as it is safe and forwards the payment normally.
781+
/// * true => "Client trusts LSP": LSP may defer broadcasting the funding
782+
/// transaction until after the client claims the forwarded HTLC(s).
783+
///
800784
/// [`ChannelManager::get_intercept_scid`]: lightning::ln::channelmanager::ChannelManager::get_intercept_scid
801785
/// [`LSPS2ServiceEvent::BuyRequest`]: crate::lsps2::event::LSPS2ServiceEvent::BuyRequest
802786
pub fn invoice_parameters_generated(
@@ -1052,14 +1036,17 @@ where
10521036
Err(e) => {
10531037
return Err(APIError::APIMisuseError {
10541038
err: format!(
1055-
"Forwarded payment was not applicable for JIT channel: {}",
1056-
e.err
1057-
),
1039+
"Forwarded payment was not applicable for JIT channel: {}",
1040+
e.err
1041+
),
10581042
})
10591043
},
10601044
}
10611045

1062-
self.broadcast_transaction_if_applies(&jit_channel);
1046+
self.emit_broadcast_funding_transaction_event_if_applies(
1047+
jit_channel,
1048+
counterparty_node_id,
1049+
);
10631050
}
10641051
} else {
10651052
return Err(APIError::APIMisuseError {
@@ -1583,7 +1570,8 @@ where
15831570
/// Called to store the funding transaction for a JIT channel.
15841571
/// This should be called when the funding transaction is created but before it's broadcast.
15851572
pub fn store_funding_transaction(
1586-
&self, user_channel_id: u128, counterparty_node_id: &PublicKey, funding_tx: Transaction,
1573+
&self, user_channel_id: u128, counterparty_node_id: &PublicKey,
1574+
funding_tx: Arc<Transaction>,
15871575
) -> Result<(), APIError> {
15881576
let outer_state_lock = self.per_peer_state.read().unwrap();
15891577
let inner_state_lock =
@@ -1610,11 +1598,9 @@ where
16101598
),
16111599
})?;
16121600

1613-
jit_channel
1614-
.set_funding_tx(funding_tx)
1615-
.map_err(|e| APIError::APIMisuseError { err: e.err.to_string() })?;
1601+
jit_channel.set_funding_tx(funding_tx);
16161602

1617-
self.broadcast_transaction_if_applies(jit_channel);
1603+
self.emit_broadcast_funding_transaction_event_if_applies(jit_channel, counterparty_node_id);
16181604
Ok(())
16191605
}
16201606

@@ -1648,20 +1634,26 @@ where
16481634
),
16491635
})?;
16501636

1651-
jit_channel
1652-
.set_funding_tx_broadcast_safe(true)
1653-
.map_err(|e| APIError::APIMisuseError { err: e.err.to_string() })?;
1637+
jit_channel.set_funding_tx_broadcast_safe(true);
16541638

1655-
self.broadcast_transaction_if_applies(jit_channel);
1639+
self.emit_broadcast_funding_transaction_event_if_applies(jit_channel, counterparty_node_id);
16561640
Ok(())
16571641
}
16581642

1659-
fn broadcast_transaction_if_applies(&self, jit_channel: &OutboundJITChannel) {
1643+
fn emit_broadcast_funding_transaction_event_if_applies(
1644+
&self, jit_channel: &OutboundJITChannel, counterparty_node_id: &PublicKey,
1645+
) {
16601646
if jit_channel.should_broadcast_funding_transaction() {
16611647
let funding_tx = jit_channel.get_funding_tx();
16621648

16631649
if let Some(funding_tx) = funding_tx {
1664-
self.channel_manager.get_cm().broadcast_transaction(&funding_tx);
1650+
let event_queue_notifier = self.pending_events.notifier();
1651+
let event = LSPS2ServiceEvent::BroadcastFundingTransaction {
1652+
counterparty_node_id: *counterparty_node_id,
1653+
user_channel_id: jit_channel.user_channel_id,
1654+
funding_tx: funding_tx.as_ref().clone(),
1655+
};
1656+
event_queue_notifier.enqueue(event);
16651657
}
16661658
}
16671659
}

lightning-liquidity/tests/common/mod.rs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,19 @@ pub(crate) struct LSPSNodes<'a, 'b, 'c> {
1919
pub client_node: LiquidityNode<'a, 'b, 'c>,
2020
}
2121

22-
pub(crate) fn create_service_and_client_nodes<'a, 'b, 'c>(
23-
nodes: Vec<Node<'a, 'b, 'c>>, service_config: LiquidityServiceConfig,
22+
// this is ONLY used on LSPS2 so it says it's not used but it is
23+
#[allow(dead_code)]
24+
pub(crate) struct LSPSNodesWithPayer<'a, 'b, 'c> {
25+
pub service_node: LiquidityNode<'a, 'b, 'c>,
26+
pub client_node: LiquidityNode<'a, 'b, 'c>,
27+
pub payer_node: Node<'a, 'b, 'c>,
28+
}
29+
30+
fn build_service_and_client<'a, 'b, 'c>(
31+
mut nodes: Vec<Node<'a, 'b, 'c>>, service_config: LiquidityServiceConfig,
2432
client_config: LiquidityClientConfig, time_provider: Arc<dyn TimeProvider + Send + Sync>,
25-
) -> LSPSNodes<'a, 'b, 'c> {
33+
) -> (LiquidityNode<'a, 'b, 'c>, LiquidityNode<'a, 'b, 'c>, Option<Node<'a, 'b, 'c>>) {
34+
assert!(nodes.len() >= 2, "Need at least two nodes (service, client)");
2635
let chain_params = ChainParameters {
2736
network: Network::Testnet,
2837
best_block: BestBlock::from_network(Network::Testnet),
@@ -49,13 +58,35 @@ pub(crate) fn create_service_and_client_nodes<'a, 'b, 'c>(
4958
time_provider,
5059
);
5160

52-
let mut iter = nodes.into_iter();
61+
let mut iter = nodes.drain(..);
5362
let service_node = LiquidityNode::new(iter.next().unwrap(), service_lm);
5463
let client_node = LiquidityNode::new(iter.next().unwrap(), client_lm);
64+
let leftover = iter.next(); // payer if present
65+
(service_node, client_node, leftover)
66+
}
5567

68+
pub(crate) fn create_service_and_client_nodes<'a, 'b, 'c>(
69+
nodes: Vec<Node<'a, 'b, 'c>>, service_config: LiquidityServiceConfig,
70+
client_config: LiquidityClientConfig, time_provider: Arc<dyn TimeProvider + Send + Sync>,
71+
) -> LSPSNodes<'a, 'b, 'c> {
72+
let (service_node, client_node, _extra) =
73+
build_service_and_client(nodes, service_config, client_config, time_provider);
5674
LSPSNodes { service_node, client_node }
5775
}
5876

77+
// this is ONLY used on LSPS2 so it says it's not used but it is
78+
#[allow(dead_code)]
79+
pub(crate) fn create_service_client_and_payer_nodes<'a, 'b, 'c>(
80+
nodes: Vec<Node<'a, 'b, 'c>>, service_config: LiquidityServiceConfig,
81+
client_config: LiquidityClientConfig, time_provider: Arc<dyn TimeProvider + Send + Sync>,
82+
) -> LSPSNodesWithPayer<'a, 'b, 'c> {
83+
assert!(nodes.len() >= 3, "Need three nodes (service, client, payer)");
84+
let (service_node, client_node, payer_opt) =
85+
build_service_and_client(nodes, service_config, client_config, time_provider);
86+
let payer_node = payer_opt.expect("payer node missing");
87+
LSPSNodesWithPayer { service_node, client_node, payer_node }
88+
}
89+
5990
pub(crate) struct LiquidityNode<'a, 'b, 'c> {
6091
pub inner: Node<'a, 'b, 'c>,
6192
pub liquidity_manager: LiquidityManager<

0 commit comments

Comments
 (0)