lightning_liquidity/lsps2/
client.rs

1// This file is Copyright its original authors, visible in version control
2// history.
3//
4// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7// You may not use this file except in accordance with one or both of these
8// licenses.
9
10//! Contains the main bLIP-52 / LSPS2 client object, [`LSPS2ClientHandler`].
11
12use alloc::string::{String, ToString};
13use lightning::util::persist::KVStore;
14
15use core::default::Default;
16use core::ops::Deref;
17
18use crate::events::EventQueue;
19use crate::lsps0::ser::{LSPSProtocolMessageHandler, LSPSRequestId, LSPSResponseError};
20use crate::lsps2::event::LSPS2ClientEvent;
21use crate::message_queue::MessageQueue;
22use crate::prelude::{new_hash_map, new_hash_set, HashMap, HashSet};
23use crate::sync::{Arc, Mutex, RwLock};
24
25use lightning::ln::msgs::{ErrorAction, LightningError};
26use lightning::sign::EntropySource;
27use lightning::util::errors::APIError;
28use lightning::util::logger::Level;
29
30use bitcoin::secp256k1::PublicKey;
31
32use crate::lsps2::msgs::{
33	LSPS2BuyRequest, LSPS2BuyResponse, LSPS2GetInfoRequest, LSPS2GetInfoResponse, LSPS2Message,
34	LSPS2OpeningFeeParams, LSPS2Request, LSPS2Response,
35};
36
37/// Client-side configuration options for JIT channels.
38#[derive(Clone, Debug, Copy, Default)]
39pub struct LSPS2ClientConfig {}
40
41struct InboundJITChannel {
42	payment_size_msat: Option<u64>,
43}
44
45impl InboundJITChannel {
46	fn new(payment_size_msat: Option<u64>) -> Self {
47		Self { payment_size_msat }
48	}
49}
50
51struct PeerState {
52	pending_get_info_requests: HashSet<LSPSRequestId>,
53	pending_buy_requests: HashMap<LSPSRequestId, InboundJITChannel>,
54}
55
56impl PeerState {
57	fn new() -> Self {
58		let pending_get_info_requests = new_hash_set();
59		let pending_buy_requests = new_hash_map();
60		Self { pending_get_info_requests, pending_buy_requests }
61	}
62}
63
64/// The main object allowing to send and receive bLIP-52 / LSPS2 messages.
65///
66/// Note that currently only the 'client-trusts-LSP' trust model is supported, i.e., we don't
67/// provide any additional API guidance to allow withholding the preimage until the channel is
68/// opened. Please refer to the [`bLIP-52 / LSPS2 specification`] for more information.
69///
70/// [`bLIP-52 / LSPS2 specification`]: https://github.com/lightning/blips/blob/master/blip-0052.md#trust-models
71pub struct LSPS2ClientHandler<ES: Deref, K: Deref + Clone>
72where
73	ES::Target: EntropySource,
74	K::Target: KVStore,
75{
76	entropy_source: ES,
77	pending_messages: Arc<MessageQueue>,
78	pending_events: Arc<EventQueue<K>>,
79	per_peer_state: RwLock<HashMap<PublicKey, Mutex<PeerState>>>,
80	config: LSPS2ClientConfig,
81}
82
83impl<ES: Deref, K: Deref + Clone> LSPS2ClientHandler<ES, K>
84where
85	ES::Target: EntropySource,
86	K::Target: KVStore,
87{
88	/// Constructs an `LSPS2ClientHandler`.
89	pub(crate) fn new(
90		entropy_source: ES, pending_messages: Arc<MessageQueue>,
91		pending_events: Arc<EventQueue<K>>, config: LSPS2ClientConfig,
92	) -> Self {
93		Self {
94			entropy_source,
95			pending_messages,
96			pending_events,
97			per_peer_state: RwLock::new(new_hash_map()),
98			config,
99		}
100	}
101
102	/// Returns a reference to the used config.
103	pub fn config(&self) -> &LSPS2ClientConfig {
104		&self.config
105	}
106
107	/// Request the channel opening parameters from the LSP.
108	///
109	/// This initiates the JIT-channel flow that, at the end of it, will have the LSP
110	/// open a channel with sufficient inbound liquidity to be able to receive the payment.
111	///
112	/// The user will receive the LSP's response via an [`OpeningParametersReady`] event.
113	///
114	/// `counterparty_node_id` is the `node_id` of the LSP you would like to use.
115	///
116	/// `token` is an optional `String` that will be provided to the LSP.
117	/// It can be used by the LSP as an API key, coupon code, or some other way to identify a user.
118	///
119	/// Returns the used [`LSPSRequestId`], which will be returned via [`OpeningParametersReady`].
120	///
121	/// [`OpeningParametersReady`]: crate::lsps2::event::LSPS2ClientEvent::OpeningParametersReady
122	pub fn request_opening_params(
123		&self, counterparty_node_id: PublicKey, token: Option<String>,
124	) -> LSPSRequestId {
125		let mut message_queue_notifier = self.pending_messages.notifier();
126
127		let request_id = crate::utils::generate_request_id(&self.entropy_source);
128
129		{
130			let mut outer_state_lock = self.per_peer_state.write().unwrap();
131			let inner_state_lock = outer_state_lock
132				.entry(counterparty_node_id)
133				.or_insert(Mutex::new(PeerState::new()));
134			let mut peer_state_lock = inner_state_lock.lock().unwrap();
135			peer_state_lock.pending_get_info_requests.insert(request_id.clone());
136		}
137
138		let request = LSPS2Request::GetInfo(LSPS2GetInfoRequest { token });
139		let msg = LSPS2Message::Request(request_id.clone(), request).into();
140		message_queue_notifier.enqueue(&counterparty_node_id, msg);
141
142		request_id
143	}
144
145	/// Confirms a set of chosen channel opening parameters to use for the JIT channel and
146	/// requests the necessary invoice generation parameters from the LSP.
147	///
148	/// Should be called in response to receiving a [`OpeningParametersReady`] event.
149	///
150	/// The user will receive the LSP's response via an [`InvoiceParametersReady`] event.
151	///
152	/// If `payment_size_msat` is [`Option::Some`] then the invoice will be for a fixed amount
153	/// and MPP can be used to pay it.
154	///
155	/// If `payment_size_msat` is [`Option::None`] then the invoice can be for an arbitrary amount
156	/// but MPP can no longer be used to pay it.
157	///
158	/// The client agrees to paying an opening fee equal to
159	/// `max(min_fee_msat, proportional * (payment_size_msat / 1_000_000))`.
160	///
161	/// Returns the used [`LSPSRequestId`] that was used for the buy request.
162	///
163	/// [`OpeningParametersReady`]: crate::lsps2::event::LSPS2ClientEvent::OpeningParametersReady
164	/// [`InvoiceParametersReady`]: crate::lsps2::event::LSPS2ClientEvent::InvoiceParametersReady
165	pub fn select_opening_params(
166		&self, counterparty_node_id: PublicKey, payment_size_msat: Option<u64>,
167		opening_fee_params: LSPS2OpeningFeeParams,
168	) -> Result<LSPSRequestId, APIError> {
169		let mut message_queue_notifier = self.pending_messages.notifier();
170
171		let request_id = crate::utils::generate_request_id(&self.entropy_source);
172
173		{
174			let mut outer_state_lock = self.per_peer_state.write().unwrap();
175			let inner_state_lock = outer_state_lock
176				.entry(counterparty_node_id)
177				.or_insert(Mutex::new(PeerState::new()));
178			let mut peer_state_lock = inner_state_lock.lock().unwrap();
179
180			let jit_channel = InboundJITChannel::new(payment_size_msat);
181			if peer_state_lock
182				.pending_buy_requests
183				.insert(request_id.clone(), jit_channel)
184				.is_some()
185			{
186				return Err(APIError::APIMisuseError {
187					err: "Failed due to duplicate request_id. This should never happen!"
188						.to_string(),
189				});
190			}
191		}
192
193		let request = LSPS2Request::Buy(LSPS2BuyRequest { opening_fee_params, payment_size_msat });
194		let msg = LSPS2Message::Request(request_id.clone(), request).into();
195		message_queue_notifier.enqueue(&counterparty_node_id, msg);
196
197		Ok(request_id)
198	}
199
200	fn handle_get_info_response(
201		&self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey,
202		result: LSPS2GetInfoResponse,
203	) -> Result<(), LightningError> {
204		let event_queue_notifier = self.pending_events.notifier();
205
206		let outer_state_lock = self.per_peer_state.read().unwrap();
207		match outer_state_lock.get(counterparty_node_id) {
208			Some(inner_state_lock) => {
209				let mut peer_state = inner_state_lock.lock().unwrap();
210
211				if !peer_state.pending_get_info_requests.remove(&request_id) {
212					return Err(LightningError {
213						err: format!(
214							"Received get_info response for an unknown request: {:?}",
215							request_id
216						),
217						action: ErrorAction::IgnoreAndLog(Level::Debug),
218					});
219				}
220
221				event_queue_notifier.enqueue(LSPS2ClientEvent::OpeningParametersReady {
222					request_id,
223					counterparty_node_id: *counterparty_node_id,
224					opening_fee_params_menu: result.opening_fee_params_menu,
225				});
226			},
227			None => {
228				return Err(LightningError {
229					err: format!(
230						"Received get_info response from unknown peer: {}",
231						counterparty_node_id
232					),
233					action: ErrorAction::IgnoreAndLog(Level::Debug),
234				})
235			},
236		}
237
238		Ok(())
239	}
240
241	fn handle_get_info_error(
242		&self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey,
243		error: LSPSResponseError,
244	) -> Result<(), LightningError> {
245		let event_queue_notifier = self.pending_events.notifier();
246		let outer_state_lock = self.per_peer_state.read().unwrap();
247		match outer_state_lock.get(counterparty_node_id) {
248			Some(inner_state_lock) => {
249				let mut peer_state = inner_state_lock.lock().unwrap();
250
251				if !peer_state.pending_get_info_requests.remove(&request_id) {
252					return Err(LightningError {
253						err: format!(
254							"Received get_info error for an unknown request: {:?}",
255							request_id
256						),
257						action: ErrorAction::IgnoreAndLog(Level::Debug),
258					});
259				}
260
261				let lightning_error = LightningError {
262					err: format!(
263						"Received get_info error response for request {:?}: {:?}",
264						request_id, error
265					),
266					action: ErrorAction::IgnoreAndLog(Level::Error),
267				};
268
269				event_queue_notifier.enqueue(LSPS2ClientEvent::GetInfoFailed {
270					request_id,
271					counterparty_node_id: *counterparty_node_id,
272					error,
273				});
274
275				Err(lightning_error)
276			},
277			None => {
278				return Err(LightningError { err: format!("Received error response for a get_info request from an unknown counterparty {}",counterparty_node_id), action: ErrorAction::IgnoreAndLog(Level::Debug)});
279			},
280		}
281	}
282
283	fn handle_buy_response(
284		&self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey,
285		result: LSPS2BuyResponse,
286	) -> Result<(), LightningError> {
287		let event_queue_notifier = self.pending_events.notifier();
288
289		let outer_state_lock = self.per_peer_state.read().unwrap();
290		match outer_state_lock.get(counterparty_node_id) {
291			Some(inner_state_lock) => {
292				let mut peer_state = inner_state_lock.lock().unwrap();
293
294				let jit_channel =
295					peer_state.pending_buy_requests.remove(&request_id).ok_or(LightningError {
296						err: format!(
297							"Received buy response for an unknown request: {:?}",
298							request_id
299						),
300						action: ErrorAction::IgnoreAndLog(Level::Debug),
301					})?;
302
303				if let Ok(intercept_scid) = result.jit_channel_scid.to_scid() {
304					event_queue_notifier.enqueue(LSPS2ClientEvent::InvoiceParametersReady {
305						request_id,
306						counterparty_node_id: *counterparty_node_id,
307						intercept_scid,
308						cltv_expiry_delta: result.lsp_cltv_expiry_delta,
309						payment_size_msat: jit_channel.payment_size_msat,
310					});
311				} else {
312					return Err(LightningError {
313						err: format!(
314							"Received buy response with an invalid intercept scid {:?}",
315							result.jit_channel_scid
316						),
317						action: ErrorAction::IgnoreAndLog(Level::Info),
318					});
319				}
320			},
321			None => {
322				return Err(LightningError {
323					err: format!(
324						"Received buy response from unknown peer: {}",
325						counterparty_node_id
326					),
327					action: ErrorAction::IgnoreAndLog(Level::Debug),
328				});
329			},
330		}
331		Ok(())
332	}
333
334	fn handle_buy_error(
335		&self, request_id: LSPSRequestId, counterparty_node_id: &PublicKey,
336		error: LSPSResponseError,
337	) -> Result<(), LightningError> {
338		let event_queue_notifier = self.pending_events.notifier();
339		let outer_state_lock = self.per_peer_state.read().unwrap();
340		match outer_state_lock.get(counterparty_node_id) {
341			Some(inner_state_lock) => {
342				let mut peer_state = inner_state_lock.lock().unwrap();
343
344				peer_state.pending_buy_requests.remove(&request_id).ok_or(LightningError {
345					err: format!("Received buy error for an unknown request: {:?}", request_id),
346					action: ErrorAction::IgnoreAndLog(Level::Debug),
347				})?;
348
349				let lightning_error = LightningError {
350					err: format!(
351						"Received buy error response for request {:?}: {:?}",
352						request_id, error
353					),
354					action: ErrorAction::IgnoreAndLog(Level::Error),
355				};
356
357				event_queue_notifier.enqueue(LSPS2ClientEvent::BuyRequestFailed {
358					request_id,
359					counterparty_node_id: *counterparty_node_id,
360					error,
361				});
362
363				Err(lightning_error)
364			},
365			None => {
366				return Err(LightningError {
367					err: format!(
368						"Received error response for a buy request from an unknown counterparty {}",
369						counterparty_node_id
370					),
371					action: ErrorAction::IgnoreAndLog(Level::Debug),
372				});
373			},
374		}
375	}
376}
377
378impl<ES: Deref, K: Deref + Clone> LSPSProtocolMessageHandler for LSPS2ClientHandler<ES, K>
379where
380	ES::Target: EntropySource,
381	K::Target: KVStore,
382{
383	type ProtocolMessage = LSPS2Message;
384	const PROTOCOL_NUMBER: Option<u16> = Some(2);
385
386	fn handle_message(
387		&self, message: Self::ProtocolMessage, counterparty_node_id: &PublicKey,
388	) -> Result<(), LightningError> {
389		match message {
390			LSPS2Message::Response(request_id, response) => match response {
391				LSPS2Response::GetInfo(result) => {
392					self.handle_get_info_response(request_id, counterparty_node_id, result)
393				},
394				LSPS2Response::GetInfoError(error) => {
395					self.handle_get_info_error(request_id, counterparty_node_id, error)
396				},
397				LSPS2Response::Buy(result) => {
398					self.handle_buy_response(request_id, counterparty_node_id, result)
399				},
400				LSPS2Response::BuyError(error) => {
401					self.handle_buy_error(request_id, counterparty_node_id, error)
402				},
403			},
404			_ => {
405				debug_assert!(
406					false,
407					"Client handler received LSPS2 request message. This should never happen."
408				);
409				Err(LightningError { err: format!("Client handler received LSPS2 request message from node {}. This should never happen.", counterparty_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)})
410			},
411		}
412	}
413}
414
415#[cfg(test)]
416mod tests {}