Skip to content

Commit fb259f4

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 9c5ca9e commit fb259f4

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
@@ -159,7 +159,14 @@ pub struct LSPS2BuyResponse {
159159
pub jit_channel_scid: LSPS2InterceptScid,
160160
/// The locktime expiry delta the lsp requires.
161161
pub lsp_cltv_expiry_delta: u32,
162-
/// A flag that indicates who is trusting who.
162+
/// Trust model flag (default: false).
163+
///
164+
/// false => "LSP trusts client": LSP immediately (or as soon as safe) broadcasts the
165+
/// funding transaction; client may wait for broadcast / confirmations
166+
/// before revealing the preimage.
167+
/// true => "Client trusts LSP": LSP may defer broadcasting until after the client
168+
/// reveals the preimage; client MUST send the preimage once HTLC(s) are
169+
/// irrevocably committed.
163170
#[serde(default)]
164171
pub client_trusts_lsp: bool,
165172
}

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
}
@@ -359,7 +368,6 @@ impl OutboundJITChannelState {
359368
channel_id,
360369
FeePayment { opening_fee_msat: *opening_fee_msat, htlcs },
361370
);
362-
363371
*self = OutboundJITChannelState::PendingPaymentForward {
364372
payment_queue: core::mem::take(payment_queue),
365373
opening_fee_msat: *opening_fee_msat,
@@ -459,8 +467,7 @@ struct OutboundJITChannel {
459467
user_channel_id: u128,
460468
opening_fee_params: LSPS2OpeningFeeParams,
461469
payment_size_msat: Option<u64>,
462-
client_trusts_lsp: bool,
463-
trust_model: Option<TrustModel>,
470+
trust_model: TrustModel,
464471
}
465472

466473
impl OutboundJITChannel {
@@ -473,23 +480,15 @@ impl OutboundJITChannel {
473480
state: OutboundJITChannelState::new(),
474481
opening_fee_params,
475482
payment_size_msat,
476-
client_trusts_lsp,
477-
trust_model: None,
483+
trust_model: TrustModel::new(client_trusts_lsp),
478484
}
479485
}
480486

481487
fn htlc_intercepted(
482488
&mut self, htlc: InterceptedHTLC,
483489
) -> Result<Option<HTLCInterceptedAction>, LightningError> {
484-
let was_initial =
485-
matches!(self.state, OutboundJITChannelState::PendingInitialPayment { .. });
486490
let action =
487491
self.state.htlc_intercepted(&self.opening_fee_params, &self.payment_size_msat, htlc)?;
488-
if was_initial && self.trust_model.is_none() {
489-
if !matches!(self.state, OutboundJITChannelState::PendingInitialPayment { .. }) {
490-
self.trust_model = Some(TrustModel::new(self.client_trusts_lsp));
491-
}
492-
}
493492
Ok(action)
494493
}
495494

@@ -508,9 +507,7 @@ impl OutboundJITChannel {
508507
fn payment_forwarded(&mut self) -> Result<Option<ForwardHTLCsAction>, LightningError> {
509508
let action = self.state.payment_forwarded()?;
510509
if action.is_some() {
511-
if let Some(tm) = &mut self.trust_model {
512-
tm.set_payment_claimed(true);
513-
}
510+
self.trust_model.set_payment_claimed(true);
514511
}
515512
Ok(action)
516513
}
@@ -526,43 +523,24 @@ impl OutboundJITChannel {
526523
self.is_pending_initial_payment() && is_expired
527524
}
528525

529-
fn set_funding_tx(&mut self, funding_tx: Transaction) -> Result<(), LightningError> {
530-
if let Some(tm) = &mut self.trust_model {
531-
tm.set_funding_tx(funding_tx);
532-
Ok(())
533-
} else {
534-
Err(LightningError::from(ChannelStateError(
535-
"Store funding transaction when JIT Channel was in invalid state".to_string(),
536-
)))
537-
}
526+
fn set_funding_tx(&mut self, funding_tx: Arc<Transaction>) {
527+
self.trust_model.set_funding_tx(funding_tx);
538528
}
539529

540-
fn set_funding_tx_broadcast_safe(
541-
&mut self, funding_tx_broadcast_safe: bool,
542-
) -> Result<(), LightningError> {
543-
if let Some(tm) = &mut self.trust_model {
544-
tm.set_funding_tx_broadcast_safe(funding_tx_broadcast_safe);
545-
Ok(())
546-
} else {
547-
Err(LightningError::from(ChannelStateError(
548-
"Store funding transaction broadcast safe when JIT Channel was in invalid state"
549-
.to_string(),
550-
)))
551-
}
530+
fn set_funding_tx_broadcast_safe(&mut self, funding_tx_broadcast_safe: bool) {
531+
self.trust_model.set_funding_tx_broadcast_safe(funding_tx_broadcast_safe);
552532
}
553533

554534
fn should_broadcast_funding_transaction(&self) -> bool {
555-
self.trust_model.as_ref().map_or(false, |tm| tm.should_broadcast())
535+
self.trust_model.should_manually_broadcast()
556536
}
557537

558-
fn get_funding_tx(&self) -> Option<Transaction> {
559-
self.trust_model.as_ref().and_then(|tm| tm.get_funding_tx())
538+
fn get_funding_tx(&self) -> Option<Arc<Transaction>> {
539+
self.trust_model.get_funding_tx()
560540
}
561541

562542
fn client_trusts_lsp(&self) -> bool {
563-
self.trust_model
564-
.as_ref()
565-
.map_or(false, |tm| matches!(tm, TrustModel::ClientTrustsLsp { .. }))
543+
self.trust_model.is_client_trusts_lsp()
566544
}
567545
}
568546

@@ -803,6 +781,12 @@ where
803781
///
804782
/// Should be called in response to receiving a [`LSPS2ServiceEvent::BuyRequest`] event.
805783
///
784+
/// `client_trusts_lsp`:
785+
/// * false (default) => "LSP trusts client": LSP broadcasts the funding
786+
/// transaction as soon as it is safe and forwards the payment normally.
787+
/// * true => "Client trusts LSP": LSP may defer broadcasting the funding
788+
/// transaction until after the client claims the forwarded HTLC(s).
789+
///
806790
/// [`ChannelManager::get_intercept_scid`]: lightning::ln::channelmanager::ChannelManager::get_intercept_scid
807791
/// [`LSPS2ServiceEvent::BuyRequest`]: crate::lsps2::event::LSPS2ServiceEvent::BuyRequest
808792
pub fn invoice_parameters_generated(
@@ -1058,14 +1042,17 @@ where
10581042
Err(e) => {
10591043
return Err(APIError::APIMisuseError {
10601044
err: format!(
1061-
"Forwarded payment was not applicable for JIT channel: {}",
1062-
e.err
1063-
),
1045+
"Forwarded payment was not applicable for JIT channel: {}",
1046+
e.err
1047+
),
10641048
})
10651049
},
10661050
}
10671051

1068-
self.broadcast_transaction_if_applies(&jit_channel);
1052+
self.emit_broadcast_funding_transaction_event_if_applies(
1053+
jit_channel,
1054+
counterparty_node_id,
1055+
);
10691056
}
10701057
} else {
10711058
return Err(APIError::APIMisuseError {
@@ -1589,7 +1576,8 @@ where
15891576
/// Called to store the funding transaction for a JIT channel.
15901577
/// This should be called when the funding transaction is created but before it's broadcast.
15911578
pub fn store_funding_transaction(
1592-
&self, user_channel_id: u128, counterparty_node_id: &PublicKey, funding_tx: Transaction,
1579+
&self, user_channel_id: u128, counterparty_node_id: &PublicKey,
1580+
funding_tx: Arc<Transaction>,
15931581
) -> Result<(), APIError> {
15941582
let outer_state_lock = self.per_peer_state.read().unwrap();
15951583
let inner_state_lock =
@@ -1616,11 +1604,9 @@ where
16161604
),
16171605
})?;
16181606

1619-
jit_channel
1620-
.set_funding_tx(funding_tx)
1621-
.map_err(|e| APIError::APIMisuseError { err: e.err.to_string() })?;
1607+
jit_channel.set_funding_tx(funding_tx);
16221608

1623-
self.broadcast_transaction_if_applies(jit_channel);
1609+
self.emit_broadcast_funding_transaction_event_if_applies(jit_channel, counterparty_node_id);
16241610
Ok(())
16251611
}
16261612

@@ -1654,20 +1640,26 @@ where
16541640
),
16551641
})?;
16561642

1657-
jit_channel
1658-
.set_funding_tx_broadcast_safe(true)
1659-
.map_err(|e| APIError::APIMisuseError { err: e.err.to_string() })?;
1643+
jit_channel.set_funding_tx_broadcast_safe(true);
16601644

1661-
self.broadcast_transaction_if_applies(jit_channel);
1645+
self.emit_broadcast_funding_transaction_event_if_applies(jit_channel, counterparty_node_id);
16621646
Ok(())
16631647
}
16641648

1665-
fn broadcast_transaction_if_applies(&self, jit_channel: &OutboundJITChannel) {
1649+
fn emit_broadcast_funding_transaction_event_if_applies(
1650+
&self, jit_channel: &OutboundJITChannel, counterparty_node_id: &PublicKey,
1651+
) {
16661652
if jit_channel.should_broadcast_funding_transaction() {
16671653
let funding_tx = jit_channel.get_funding_tx();
16681654

16691655
if let Some(funding_tx) = funding_tx {
1670-
self.channel_manager.get_cm().broadcast_transaction(&funding_tx);
1656+
let event_queue_notifier = self.pending_events.notifier();
1657+
let event = LSPS2ServiceEvent::BroadcastFundingTransaction {
1658+
counterparty_node_id: *counterparty_node_id,
1659+
user_channel_id: jit_channel.user_channel_id,
1660+
funding_tx: funding_tx.as_ref().clone(),
1661+
};
1662+
event_queue_notifier.enqueue(event);
16711663
}
16721664
}
16731665
}

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)