lightning_liquidity/lsps1/
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 LSPS1 client object, [`LSPS1ClientHandler`].
11
12use super::event::LSPS1ClientEvent;
13use super::msgs::{
14	CreateOrderRequest, CreateOrderResponse, GetInfoRequest, GetInfoResponse, GetOrderRequest,
15	LSPS1Message, LSPS1Request, LSPS1Response, OrderId, OrderParameters,
16};
17use crate::message_queue::MessageQueue;
18
19use crate::events::{Event, EventQueue};
20use crate::lsps0::ser::{ProtocolMessageHandler, RequestId, ResponseError};
21use crate::prelude::{new_hash_map, HashMap, HashSet};
22use crate::sync::{Arc, Mutex, RwLock};
23
24use lightning::ln::msgs::{ErrorAction, LightningError};
25use lightning::sign::EntropySource;
26use lightning::util::logger::Level;
27
28use bitcoin::secp256k1::PublicKey;
29use bitcoin::Address;
30
31use core::ops::Deref;
32
33/// Client-side configuration options for LSPS1 channel requests.
34#[derive(Clone, Debug)]
35pub struct LSPS1ClientConfig {
36	/// The maximally allowed channel fees.
37	pub max_channel_fees_msat: Option<u64>,
38}
39
40#[derive(Default)]
41struct PeerState {
42	pending_get_info_requests: HashSet<RequestId>,
43	pending_create_order_requests: HashSet<RequestId>,
44	pending_get_order_requests: HashSet<RequestId>,
45}
46
47/// The main object allowing to send and receive LSPS1 messages.
48pub struct LSPS1ClientHandler<ES: Deref>
49where
50	ES::Target: EntropySource,
51{
52	entropy_source: ES,
53	pending_messages: Arc<MessageQueue>,
54	pending_events: Arc<EventQueue>,
55	per_peer_state: RwLock<HashMap<PublicKey, Mutex<PeerState>>>,
56	_config: LSPS1ClientConfig,
57}
58
59impl<ES: Deref> LSPS1ClientHandler<ES>
60where
61	ES::Target: EntropySource,
62{
63	/// Constructs an `LSPS1ClientHandler`.
64	pub(crate) fn new(
65		entropy_source: ES, pending_messages: Arc<MessageQueue>, pending_events: Arc<EventQueue>,
66		config: LSPS1ClientConfig,
67	) -> Self {
68		Self {
69			entropy_source,
70			pending_messages,
71			pending_events,
72			per_peer_state: RwLock::new(new_hash_map()),
73			_config: config,
74		}
75	}
76
77	/// Request the supported options from the LSP.
78	///
79	/// The user will receive the LSP's response via an [`SupportedOptionsReady`] event.
80	///
81	/// `counterparty_node_id` is the `node_id` of the LSP you would like to use.
82	///
83	/// Returns the used [`RequestId`], which will be returned via [`SupportedOptionsReady`].
84	///
85	/// [`SupportedOptionsReady`]: crate::lsps1::event::LSPS1ClientEvent::SupportedOptionsReady
86	pub fn request_supported_options(&self, counterparty_node_id: PublicKey) -> RequestId {
87		let request_id = crate::utils::generate_request_id(&self.entropy_source);
88		{
89			let mut outer_state_lock = self.per_peer_state.write().unwrap();
90			let inner_state_lock = outer_state_lock
91				.entry(counterparty_node_id)
92				.or_insert(Mutex::new(PeerState::default()));
93			let mut peer_state_lock = inner_state_lock.lock().unwrap();
94			peer_state_lock.pending_get_info_requests.insert(request_id.clone());
95		}
96
97		let request = LSPS1Request::GetInfo(GetInfoRequest {});
98		let msg = LSPS1Message::Request(request_id.clone(), request).into();
99		self.pending_messages.enqueue(&counterparty_node_id, msg);
100		request_id
101	}
102
103	fn handle_get_info_response(
104		&self, request_id: RequestId, counterparty_node_id: &PublicKey, result: GetInfoResponse,
105	) -> Result<(), LightningError> {
106		let outer_state_lock = self.per_peer_state.write().unwrap();
107
108		match outer_state_lock.get(counterparty_node_id) {
109			Some(inner_state_lock) => {
110				let mut peer_state_lock = inner_state_lock.lock().unwrap();
111
112				if !peer_state_lock.pending_get_info_requests.remove(&request_id) {
113					return Err(LightningError {
114						err: format!(
115							"Received get_info response for an unknown request: {:?}",
116							request_id
117						),
118						action: ErrorAction::IgnoreAndLog(Level::Debug),
119					});
120				}
121
122				self.pending_events.enqueue(Event::LSPS1Client(
123					LSPS1ClientEvent::SupportedOptionsReady {
124						counterparty_node_id: *counterparty_node_id,
125						supported_options: result.options,
126						request_id,
127					},
128				));
129				Ok(())
130			},
131			None => Err(LightningError {
132				err: format!(
133					"Received get_info response from unknown peer: {:?}",
134					counterparty_node_id
135				),
136				action: ErrorAction::IgnoreAndLog(Level::Debug),
137			}),
138		}
139	}
140
141	fn handle_get_info_error(
142		&self, request_id: RequestId, counterparty_node_id: &PublicKey, error: ResponseError,
143	) -> Result<(), LightningError> {
144		let outer_state_lock = self.per_peer_state.read().unwrap();
145		match outer_state_lock.get(counterparty_node_id) {
146			Some(inner_state_lock) => {
147				let mut peer_state_lock = inner_state_lock.lock().unwrap();
148
149				if !peer_state_lock.pending_get_info_requests.remove(&request_id) {
150					return Err(LightningError {
151						err: format!(
152							"Received get_info error for an unknown request: {:?}",
153							request_id
154						),
155						action: ErrorAction::IgnoreAndLog(Level::Debug),
156					});
157				}
158
159				self.pending_events.enqueue(Event::LSPS1Client(
160					LSPS1ClientEvent::SupportedOptionsRequestFailed {
161						request_id: request_id.clone(),
162						counterparty_node_id: *counterparty_node_id,
163						error: error.clone(),
164					},
165				));
166
167				Err(LightningError {
168					err: format!(
169						"Received get_info error response for request {:?}: {:?}",
170						request_id, error
171					),
172					action: ErrorAction::IgnoreAndLog(Level::Error),
173				})
174			},
175			None => {
176				return Err(LightningError {
177					err: format!(
178						"Received get_info error response from an unknown counterparty ({:?})",
179						counterparty_node_id
180					),
181					action: ErrorAction::IgnoreAndLog(Level::Debug),
182				});
183			},
184		}
185	}
186
187	/// Places an order with the connected LSP given its `counterparty_node_id`.
188	///
189	/// The client agrees to paying channel fees according to the provided parameters.
190	pub fn create_order(
191		&self, counterparty_node_id: &PublicKey, order: OrderParameters,
192		refund_onchain_address: Option<Address>,
193	) -> RequestId {
194		let (request_id, request_msg) = {
195			let mut outer_state_lock = self.per_peer_state.write().unwrap();
196			let inner_state_lock = outer_state_lock
197				.entry(*counterparty_node_id)
198				.or_insert(Mutex::new(PeerState::default()));
199			let mut peer_state_lock = inner_state_lock.lock().unwrap();
200
201			let request_id = crate::utils::generate_request_id(&self.entropy_source);
202			let request =
203				LSPS1Request::CreateOrder(CreateOrderRequest { order, refund_onchain_address });
204			let msg = LSPS1Message::Request(request_id.clone(), request).into();
205			peer_state_lock.pending_create_order_requests.insert(request_id.clone());
206
207			(request_id, Some(msg))
208		};
209
210		if let Some(msg) = request_msg {
211			self.pending_messages.enqueue(&counterparty_node_id, msg);
212		}
213
214		request_id
215	}
216
217	fn handle_create_order_response(
218		&self, request_id: RequestId, counterparty_node_id: &PublicKey,
219		response: CreateOrderResponse,
220	) -> Result<(), LightningError> {
221		let outer_state_lock = self.per_peer_state.read().unwrap();
222		match outer_state_lock.get(counterparty_node_id) {
223			Some(inner_state_lock) => {
224				let mut peer_state_lock = inner_state_lock.lock().unwrap();
225
226				if !peer_state_lock.pending_create_order_requests.remove(&request_id) {
227					return Err(LightningError {
228						err: format!(
229							"Received create_order response for an unknown request: {:?}",
230							request_id
231						),
232						action: ErrorAction::IgnoreAndLog(Level::Debug),
233					});
234				}
235
236				self.pending_events.enqueue(Event::LSPS1Client(LSPS1ClientEvent::OrderCreated {
237					request_id,
238					counterparty_node_id: *counterparty_node_id,
239					order_id: response.order_id,
240					order: response.order,
241					payment: response.payment,
242					channel: response.channel,
243				}));
244			},
245			None => {
246				return Err(LightningError {
247					err: format!(
248						"Received create_order response from unknown peer: {}",
249						counterparty_node_id
250					),
251					action: ErrorAction::IgnoreAndLog(Level::Debug),
252				})
253			},
254		}
255
256		Ok(())
257	}
258
259	fn handle_create_order_error(
260		&self, request_id: RequestId, counterparty_node_id: &PublicKey, error: ResponseError,
261	) -> Result<(), LightningError> {
262		let outer_state_lock = self.per_peer_state.read().unwrap();
263		match outer_state_lock.get(counterparty_node_id) {
264			Some(inner_state_lock) => {
265				let mut peer_state_lock = inner_state_lock.lock().unwrap();
266
267				if !peer_state_lock.pending_create_order_requests.remove(&request_id) {
268					return Err(LightningError {
269						err: format!(
270							"Received create order error for an unknown request: {:?}",
271							request_id
272						),
273						action: ErrorAction::IgnoreAndLog(Level::Debug),
274					});
275				}
276
277				self.pending_events.enqueue(Event::LSPS1Client(
278					LSPS1ClientEvent::OrderRequestFailed {
279						request_id: request_id.clone(),
280						counterparty_node_id: *counterparty_node_id,
281						error: error.clone(),
282					},
283				));
284
285				Err(LightningError {
286					err: format!(
287						"Received create_order error response for request {:?}: {:?}",
288						request_id, error
289					),
290					action: ErrorAction::IgnoreAndLog(Level::Error),
291				})
292			},
293			None => {
294				return Err(LightningError {
295					err: format!(
296						"Received error response for a create order request from an unknown counterparty ({:?})",
297						counterparty_node_id
298					),
299					action: ErrorAction::IgnoreAndLog(Level::Debug),
300				});
301			},
302		}
303	}
304
305	/// Queries the status of a pending payment, i.e., whether a payment has been received by the LSP.
306	///
307	/// Upon success an [`LSPS1ClientEvent::OrderStatus`] event will be emitted.
308	///
309	/// [`LSPS1ClientEvent::OrderStatus`]: crate::lsps1::event::LSPS1ClientEvent::OrderStatus
310	pub fn check_order_status(
311		&self, counterparty_node_id: &PublicKey, order_id: OrderId,
312	) -> RequestId {
313		let (request_id, request_msg) = {
314			let mut outer_state_lock = self.per_peer_state.write().unwrap();
315			let inner_state_lock = outer_state_lock
316				.entry(*counterparty_node_id)
317				.or_insert(Mutex::new(PeerState::default()));
318			let mut peer_state_lock = inner_state_lock.lock().unwrap();
319
320			let request_id = crate::utils::generate_request_id(&self.entropy_source);
321			peer_state_lock.pending_get_order_requests.insert(request_id.clone());
322
323			let request = LSPS1Request::GetOrder(GetOrderRequest { order_id: order_id.clone() });
324			let msg = LSPS1Message::Request(request_id.clone(), request).into();
325
326			(request_id, Some(msg))
327		};
328
329		if let Some(msg) = request_msg {
330			self.pending_messages.enqueue(&counterparty_node_id, msg);
331		}
332
333		request_id
334	}
335
336	fn handle_get_order_response(
337		&self, request_id: RequestId, counterparty_node_id: &PublicKey,
338		response: CreateOrderResponse,
339	) -> Result<(), LightningError> {
340		let outer_state_lock = self.per_peer_state.read().unwrap();
341		match outer_state_lock.get(counterparty_node_id) {
342			Some(inner_state_lock) => {
343				let mut peer_state_lock = inner_state_lock.lock().unwrap();
344
345				if !peer_state_lock.pending_get_order_requests.remove(&request_id) {
346					return Err(LightningError {
347						err: format!(
348							"Received get_order response for an unknown request: {:?}",
349							request_id
350						),
351						action: ErrorAction::IgnoreAndLog(Level::Debug),
352					});
353				}
354
355				self.pending_events.enqueue(Event::LSPS1Client(LSPS1ClientEvent::OrderStatus {
356					request_id,
357					counterparty_node_id: *counterparty_node_id,
358					order_id: response.order_id,
359					order: response.order,
360					payment: response.payment,
361					channel: response.channel,
362				}));
363			},
364			None => {
365				return Err(LightningError {
366					err: format!(
367						"Received get_order response from unknown peer: {}",
368						counterparty_node_id
369					),
370					action: ErrorAction::IgnoreAndLog(Level::Debug),
371				})
372			},
373		}
374
375		Ok(())
376	}
377
378	fn handle_get_order_error(
379		&self, request_id: RequestId, counterparty_node_id: &PublicKey, error: ResponseError,
380	) -> Result<(), LightningError> {
381		let outer_state_lock = self.per_peer_state.read().unwrap();
382		match outer_state_lock.get(counterparty_node_id) {
383			Some(inner_state_lock) => {
384				let mut peer_state_lock = inner_state_lock.lock().unwrap();
385
386				if !peer_state_lock.pending_get_order_requests.remove(&request_id) {
387					return Err(LightningError {
388						err: format!(
389							"Received get order error for an unknown request: {:?}",
390							request_id
391						),
392						action: ErrorAction::IgnoreAndLog(Level::Debug),
393					});
394				}
395
396				self.pending_events.enqueue(Event::LSPS1Client(
397					LSPS1ClientEvent::OrderRequestFailed {
398						request_id: request_id.clone(),
399						counterparty_node_id: *counterparty_node_id,
400						error: error.clone(),
401					},
402				));
403
404				Err(LightningError {
405					err: format!(
406						"Received get_order error response for request {:?}: {:?}",
407						request_id, error
408					),
409					action: ErrorAction::IgnoreAndLog(Level::Error),
410				})
411			},
412			None => {
413				return Err(LightningError {
414					err: format!(
415						"Received error response for a get order request from an unknown counterparty ({:?})",
416						counterparty_node_id
417					),
418					action: ErrorAction::IgnoreAndLog(Level::Debug),
419				});
420			},
421		}
422	}
423}
424
425impl<ES: Deref> ProtocolMessageHandler for LSPS1ClientHandler<ES>
426where
427	ES::Target: EntropySource,
428{
429	type ProtocolMessage = LSPS1Message;
430	const PROTOCOL_NUMBER: Option<u16> = Some(1);
431
432	fn handle_message(
433		&self, message: Self::ProtocolMessage, counterparty_node_id: &PublicKey,
434	) -> Result<(), LightningError> {
435		match message {
436			LSPS1Message::Response(request_id, response) => match response {
437				LSPS1Response::GetInfo(params) => {
438					self.handle_get_info_response(request_id, counterparty_node_id, params)
439				},
440				LSPS1Response::GetInfoError(error) => {
441					self.handle_get_info_error(request_id, counterparty_node_id, error)
442				},
443				LSPS1Response::CreateOrder(params) => {
444					self.handle_create_order_response(request_id, counterparty_node_id, params)
445				},
446				LSPS1Response::CreateOrderError(error) => {
447					self.handle_create_order_error(request_id, counterparty_node_id, error)
448				},
449				LSPS1Response::GetOrder(params) => {
450					self.handle_get_order_response(request_id, counterparty_node_id, params)
451				},
452				LSPS1Response::GetOrderError(error) => {
453					self.handle_get_order_error(request_id, counterparty_node_id, error)
454				},
455			},
456			_ => {
457				debug_assert!(
458					false,
459					"Client handler received LSPS1 request message. This should never happen."
460				);
461				Err(LightningError {
462					err: format!(
463						"Client handler received LSPS1 request message from node {:?}. This should never happen.",
464						counterparty_node_id
465					),
466					action: ErrorAction::IgnoreAndLog(Level::Error),
467				})
468			},
469		}
470	}
471}