diff --git a/src/channel_request/channel_manager.rs b/src/channel_request/channel_manager.rs new file mode 100644 index 0000000..4ec4535 --- /dev/null +++ b/src/channel_request/channel_manager.rs @@ -0,0 +1,1075 @@ +// This file is Copyright its original authors, visible in version contror +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +use chrono::Utc; +use std::collections::HashMap; +use std::ops::Deref; +use std::sync::{Arc, Mutex, RwLock}; + +use bitcoin::secp256k1::PublicKey; +use lightning::chain; +use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; +use lightning::ln::channelmanager::ChannelManager; +use lightning::ln::msgs::{ + ChannelMessageHandler, ErrorAction, LightningError, OnionMessageHandler, RoutingMessageHandler, +}; +use lightning::ln::peer_handler::{CustomMessageHandler, PeerManager, SocketDescriptor}; +use lightning::routing::router::Router; +use lightning::sign::{EntropySource, NodeSigner, SignerProvider}; +use lightning::util::errors::APIError; +use lightning::util::logger::{Level, Logger}; + +use crate::events::EventQueue; +use crate::transport::message_handler::{CRChannelConfig, ProtocolMessageHandler}; +use crate::transport::msgs::{LSPSMessage, RequestId}; +use crate::utils; +use crate::{events::Event, transport::msgs::ResponseError}; + +use super::msgs::{ + ChannelInfo, CreateOrderRequest, CreateOrderResponse, GetInfoRequest, GetInfoResponse, + GetOrderRequest, GetOrderResponse, LSPS1Message, LSPS1Request, LSPS1Response, OptionsSupported, + Order, OrderId, OrderState, Payment, LSPS1_CREATE_ORDER_REQUEST_INVALID_VERSION_ERROR_CODE, + LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE, +}; +use super::utils::is_valid; + +const SUPPORTED_SPEC_VERSIONS: [u16; 1] = [1]; + +struct ChannelStateError(String); + +impl From for LightningError { + fn from(value: ChannelStateError) -> Self { + LightningError { err: value.0, action: ErrorAction::IgnoreAndLog(Level::Info) } + } +} + +#[derive(PartialEq, Debug)] +enum InboundRequestState { + InfoRequested, + OptionsSupport { version: u16, options_supported: OptionsSupported }, + OrderRequested { version: u16, order: Order }, + PendingPayment { order_id: OrderId }, + AwaitingConfirmation { id: u128, order_id: OrderId }, +} + +impl InboundRequestState { + fn info_received( + &self, versions: Vec, options: OptionsSupported, + ) -> Result { + let max_shared_version = versions + .iter() + .filter(|version| SUPPORTED_SPEC_VERSIONS.contains(version)) + .max() + .cloned() + .ok_or(ChannelStateError(format!( + "LSP does not support any of our specification versions. ours = {:?}. theirs = {:?}", + SUPPORTED_SPEC_VERSIONS, versions + )))?; + + match self { + InboundRequestState::InfoRequested => Ok(InboundRequestState::OptionsSupport { + version: max_shared_version, + options_supported: options, + }), + state => Err(ChannelStateError(format!( + "Received unexpected get_versions response. Channel was in state: {:?}", + state + ))), + } + } + + pub fn order_requested(&self, order: Order) -> Result { + match self { + InboundRequestState::OptionsSupport { version, options_supported } => { + if is_valid(&order, options_supported) { + Ok(InboundRequestState::OrderRequested { version: *version, order }) + } else { + return Err(ChannelStateError(format!( + "The order created does not match options supported by LSP. Options Supported by LSP are {:?}. The order created was {:?}", + options_supported, order + ))); + } + } + state => Err(ChannelStateError(format!( + "Received create order request for wrong channel. Channel was in state: {:?}", + state + ))), + } + } + + pub fn order_received( + &self, response_order: &Order, order_id: OrderId, + ) -> Result { + match self { + InboundRequestState::OrderRequested { version, order } => { + if response_order == order { + Ok(InboundRequestState::PendingPayment { order_id }) + } else { + Err(ChannelStateError(format!( + "Received order is different from created order. The order created was : {:?}. Order Received from LSP is : {:?}", + order, response_order + ))) + } + } + state => Err(ChannelStateError(format!( + "Received unexpected create order response. Channel was in state: {:?}", + state + ))), + } + } + + pub fn pay_for_channel(&self, channel_id: u128) -> Result { + match self { + InboundRequestState::PendingPayment { order_id } => { + Ok(InboundRequestState::AwaitingConfirmation { + id: channel_id, + order_id: order_id.clone(), + }) + } + state => Err(ChannelStateError(format!( + "Received unexpected response. Channel was in state: {:?}", + state + ))), + } + } +} + +struct InboundCRChannel { + id: u128, + state: InboundRequestState, +} + +impl InboundCRChannel { + pub fn new(id: u128) -> Self { + Self { id, state: InboundRequestState::InfoRequested } + } + + pub fn info_received( + &mut self, versions: Vec, options: OptionsSupported, + ) -> Result { + self.state = self.state.info_received(versions, options)?; + + match self.state { + InboundRequestState::OptionsSupport { version, .. } => Ok(version), + _ => Err(LightningError { + action: ErrorAction::IgnoreAndLog(Level::Error), + err: "impossible state transition".to_string(), + }), + } + } + + pub fn order_requested(&mut self, order: Order) -> Result { + self.state = self.state.order_requested(order)?; + + match self.state { + InboundRequestState::OrderRequested { version, .. } => Ok(version), + _ => { + return Err(LightningError { + action: ErrorAction::IgnoreAndLog(Level::Error), + err: "impossible state transition".to_string(), + }); + } + } + } + + pub fn order_received( + &mut self, order: &Order, order_id: OrderId, + ) -> Result<(), LightningError> { + self.state = self.state.order_received(order, order_id)?; + Ok(()) + } + + pub fn pay_for_channel(&mut self, channel_id: u128) -> Result<(), LightningError> { + self.state = self.state.pay_for_channel(channel_id)?; + Ok(()) + } +} + +#[derive(PartialEq, Debug)] +enum OutboundRequestState { + OrderCreated { order_id: OrderId }, + WaitingPayment { order_id: OrderId }, + Ready, +} + +impl OutboundRequestState { + pub fn create_payment_invoice(&self) -> Result { + match self { + OutboundRequestState::OrderCreated { order_id } => { + Ok(OutboundRequestState::WaitingPayment { order_id: order_id.clone() }) + } + state => Err(ChannelStateError(format!( + "Received unexpected get_versions response. JIT Channel was in state: {:?}", + state + ))), + } + } +} + +struct OutboundCRChannelConfig { + order: Order, + created_at: chrono::DateTime, + expires_at: chrono::DateTime, + payment: Payment, +} + +struct OutboundCRChannel { + state: OutboundRequestState, + config: OutboundCRChannelConfig, +} + +impl OutboundCRChannel { + pub fn new( + order: Order, created_at: chrono::DateTime, expires_at: chrono::DateTime, + order_id: OrderId, payment: Payment, + ) -> Self { + Self { + state: OutboundRequestState::OrderCreated { order_id }, + config: OutboundCRChannelConfig { order, created_at, expires_at, payment }, + } + } + pub fn create_payment_invoice(&mut self) -> Result<(), LightningError> { + self.state = self.state.create_payment_invoice()?; + Ok(()) + } + + pub fn check_order_validity(&self, options_supported: &OptionsSupported) -> bool { + let order = &self.config.order; + + is_valid(order, options_supported) + } +} + +#[derive(Default)] +struct PeerState { + inbound_channels_by_id: HashMap, + outbound_channels_by_order_id: HashMap, + request_to_cid: HashMap, + pending_requests: HashMap, +} + +impl PeerState { + pub fn insert_inbound_channel(&mut self, id: u128, channel: InboundCRChannel) { + self.inbound_channels_by_id.insert(id, channel); + } + + pub fn insert_outbound_channel(&mut self, order_id: OrderId, channel: OutboundCRChannel) { + self.outbound_channels_by_order_id.insert(order_id, channel); + } + + pub fn insert_request(&mut self, request_id: RequestId, channel_id: u128) { + self.request_to_cid.insert(request_id, channel_id); + } + + pub fn remove_inbound_channel(&mut self, id: u128) { + self.inbound_channels_by_id.remove(&id); + } + + pub fn remove_outbound_channel(&mut self, order_id: OrderId) { + self.outbound_channels_by_order_id.remove(&order_id); + } +} + +pub struct CRManager< + ES: Deref, + M: Deref, + T: Deref, + F: Deref, + R: Deref, + SP: Deref, + Descriptor: SocketDescriptor, + L: Deref, + RM: Deref, + CM: Deref, + OM: Deref, + CMH: Deref, + NS: Deref, +> where + ES::Target: EntropySource, + M::Target: chain::Watch<::Signer>, + T::Target: BroadcasterInterface, + F::Target: FeeEstimator, + R::Target: Router, + SP::Target: SignerProvider, + L::Target: Logger, + RM::Target: RoutingMessageHandler, + CM::Target: ChannelMessageHandler, + OM::Target: OnionMessageHandler, + CMH::Target: CustomMessageHandler, + NS::Target: NodeSigner, +{ + entropy_source: ES, + peer_manager: Mutex>>>, + channel_manager: Arc>, + pending_messages: Arc>>, + pending_events: Arc, + per_peer_state: RwLock>>, + options_config: Option, + website: Option, + max_fees: Option, +} + +impl< + ES: Deref, + M: Deref, + T: Deref, + F: Deref, + R: Deref, + SP: Deref, + Descriptor: SocketDescriptor, + L: Deref, + RM: Deref, + CM: Deref, + OM: Deref, + CMH: Deref, + NS: Deref, + > CRManager +where + ES::Target: EntropySource, + M::Target: chain::Watch<::Signer>, + T::Target: BroadcasterInterface, + F::Target: FeeEstimator, + R::Target: Router, + SP::Target: SignerProvider, + L::Target: Logger, + RM::Target: RoutingMessageHandler, + CM::Target: ChannelMessageHandler, + OM::Target: OnionMessageHandler, + CMH::Target: CustomMessageHandler, + NS::Target: NodeSigner, +{ + pub(crate) fn new( + entropy_source: ES, config: &CRChannelConfig, + pending_messages: Arc>>, + pending_events: Arc, + channel_manager: Arc>, + ) -> Self { + Self { + entropy_source, + pending_messages, + pending_events, + per_peer_state: RwLock::new(HashMap::new()), + peer_manager: Mutex::new(None), + channel_manager, + options_config: config.options_supported.clone(), + website: config.website.clone(), + max_fees: config.max_fees, + } + } + + pub fn set_peer_manager( + &self, peer_manager: Arc>, + ) { + *self.peer_manager.lock().unwrap() = Some(peer_manager); + } + + pub fn request_for_info(&self, counterparty_node_id: PublicKey, channel_id: u128) { + let channel = InboundCRChannel::new(channel_id); + + let mut outer_state_lock = self.per_peer_state.write().unwrap(); + let inner_state_lock = outer_state_lock + .entry(counterparty_node_id) + .or_insert(Mutex::new(PeerState::default())); + let peer_state = inner_state_lock.get_mut().unwrap(); + peer_state.insert_inbound_channel(channel_id, channel); + + let request_id = self.generate_request_id(); + peer_state.insert_request(request_id.clone(), channel_id); + + { + let mut pending_messages = self.pending_messages.lock().unwrap(); + pending_messages.push(( + counterparty_node_id, + LSPS1Message::Request(request_id, LSPS1Request::GetInfo(GetInfoRequest {})).into(), + )); + } + + if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { + peer_manager.process_events(); + } + } + + fn handle_get_info_request( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, + ) -> Result<(), LightningError> { + let response = GetInfoResponse { + supported_versions: SUPPORTED_SPEC_VERSIONS.to_vec(), + website: self.website.clone().unwrap().to_string(), + options: self + .options_config + .clone() + .ok_or(LightningError { + err: format!("Configuration for LSP server not set."), + action: ErrorAction::IgnoreAndLog(Level::Info), + }) + .unwrap(), + }; + + self.enqueue_response(*counterparty_node_id, request_id, LSPS1Response::GetInfo(response)); + Ok(()) + } + + fn handle_get_info_response( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, result: GetInfoResponse, + ) -> Result<(), LightningError> { + let outer_state_lock = self.per_peer_state.write().unwrap(); + + match outer_state_lock.get(counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + + let channel_id = + peer_state.request_to_cid.remove(&request_id).ok_or(LightningError { + err: format!( + "Received get_info response for an unknown request: {:?}", + request_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + let inbound_channel = peer_state + .inbound_channels_by_id + .get_mut(&channel_id) + .ok_or(LightningError { + err: format!( + "Received get_info response for an unknown channel: {:?}", + channel_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + let version = match inbound_channel + .info_received(result.supported_versions, result.options.clone()) + { + Ok(version) => version, + Err(e) => { + peer_state.remove_inbound_channel(channel_id); + return Err(e); + } + }; + + self.enqueue_event(Event::LSPS1(super::event::Event::GetInfoResponse { + id: channel_id, + request_id, + counterparty_node_id: *counterparty_node_id, + version, + website: result.website, + options_supported: result.options, + })) + } + None => { + return Err(LightningError { + err: format!( + "Received get_info response from unknown peer: {:?}", + counterparty_node_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + }) + } + } + Ok(()) + } + + pub fn place_order( + &self, channel_id: u128, counterparty_node_id: &PublicKey, order: Order, + ) -> Result<(), APIError> { + let outer_state_lock = self.per_peer_state.write().unwrap(); + + match outer_state_lock.get(counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + + let inbound_channel = peer_state + .inbound_channels_by_id + .get_mut(&channel_id) + .ok_or(APIError::APIMisuseError { + err: format!("Channel with id {} not found", channel_id), + })?; + + let version = match inbound_channel.order_requested(order.clone()) { + Ok(version) => version, + Err(e) => { + peer_state.remove_inbound_channel(channel_id); + return Err(APIError::APIMisuseError { err: e.err }); + } + }; + + let request_id = self.generate_request_id(); + peer_state.insert_request(request_id.clone(), channel_id); + + { + let mut pending_messages = self.pending_messages.lock().unwrap(); + pending_messages.push(( + *counterparty_node_id, + LSPS1Message::Request( + request_id, + LSPS1Request::CreateOrder(CreateOrderRequest { order, version }), + ) + .into(), + )); + } + if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { + peer_manager.process_events(); + } + } + None => { + return Err(APIError::APIMisuseError { + err: format!("No existing state with counterparty {}", counterparty_node_id), + }) + } + } + Ok(()) + } + + fn handle_create_order_request( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, params: CreateOrderRequest, + ) -> Result<(), LightningError> { + if !SUPPORTED_SPEC_VERSIONS.contains(¶ms.version) { + self.enqueue_response( + *counterparty_node_id, + request_id, + LSPS1Response::CreateOrderError(ResponseError { + code: LSPS1_CREATE_ORDER_REQUEST_INVALID_VERSION_ERROR_CODE, + message: format!("version {} is not supported", params.version), + data: Some(format!("Supported versions are {:?}", SUPPORTED_SPEC_VERSIONS)), + }), + ); + return Err(LightningError { + err: format!("client requested unsupported version {}", params.version), + action: ErrorAction::IgnoreAndLog(Level::Info), + }); + } + + if !is_valid(¶ms.order, &self.options_config.as_ref().unwrap()) { + self.enqueue_response( + *counterparty_node_id, + request_id, + LSPS1Response::CreateOrderError(ResponseError { + code: LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE, + message: format!("Order does not match options supported by LSP server"), + data: Some(format!( + "Supported options are {:?}", + &self.options_config.as_ref().unwrap() + )), + }), + ); + return Err(LightningError { + err: format!("client requested unsupported version {}", params.version), + action: ErrorAction::IgnoreAndLog(Level::Info), + }); + } + + let mut outer_state_lock = self.per_peer_state.write().unwrap(); + + let inner_state_lock = outer_state_lock + .entry(*counterparty_node_id) + .or_insert(Mutex::new(PeerState::default())); + let peer_state = inner_state_lock.get_mut().unwrap(); + + peer_state + .pending_requests + .insert(request_id.clone(), LSPS1Request::CreateOrder(params.clone())); + + self.enqueue_event(Event::LSPS1(super::event::Event::CreateInvoice { + request_id, + counterparty_node_id: *counterparty_node_id, + order: params.order, + })); + + Ok(()) + } + + pub fn send_invoice_for_order( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, payment: Payment, + created_at: chrono::DateTime, expires_at: chrono::DateTime, + ) -> Result<(), APIError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + + match outer_state_lock.get(counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + + match peer_state.pending_requests.remove(&request_id) { + Some(LSPS1Request::CreateOrder(params)) => { + let order_id = self.generate_order_id(); + let channel = OutboundCRChannel::new( + params.order.clone(), + created_at.clone(), + expires_at.clone(), + order_id.clone(), + payment.clone(), + ); + + peer_state.insert_outbound_channel(order_id.clone(), channel); + + self.enqueue_response( + *counterparty_node_id, + request_id, + LSPS1Response::CreateOrder(CreateOrderResponse { + order: params.order, + order_id, + order_state: OrderState::Created, + created_at, + expires_at, + payment, + channel: None, + }), + ); + } + + _ => { + return Err(APIError::APIMisuseError { + err: format!("No pending buy request for request_id: {:?}", request_id), + }) + } + } + } + None => { + return Err(APIError::APIMisuseError { + err: format!( + "No state for the counterparty exists: {:?}", + counterparty_node_id + ), + }) + } + } + + Ok(()) + } + + fn handle_create_order_response( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, + response: CreateOrderResponse, + ) -> Result<(), LightningError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + match outer_state_lock.get(&counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + + let channel_id = + peer_state.request_to_cid.remove(&request_id).ok_or(LightningError { + err: format!( + "Received create_order response for an unknown request: {:?}", + request_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + let inbound_channel = peer_state + .inbound_channels_by_id + .get_mut(&channel_id) + .ok_or(LightningError { + err: format!( + "Received create_order response for an unknown channel: {:?}", + channel_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + if let Err(e) = + inbound_channel.order_received(&response.order, response.order_id.clone()) + { + peer_state.remove_inbound_channel(channel_id); + return Err(e); + } + + let total_fees = response.payment.fee_total_sat + response.order.client_balance_sat; + let max_fees = self.max_fees.unwrap_or(u64::MAX); + + if total_fees == response.payment.order_total_sat && total_fees < max_fees { + self.enqueue_event(Event::LSPS1(super::event::Event::DisplayOrder { + id: channel_id, + counterparty_node_id: *counterparty_node_id, + order: response.order, + payment: response.payment, + channel: response.channel, + })); + } else { + peer_state.remove_inbound_channel(channel_id); + return Err(LightningError { + err: format!("Fees are too high : {:?}", total_fees), + action: ErrorAction::IgnoreAndLog(Level::Info), + }); + } + } + None => { + return Err(LightningError { + err: format!( + "Received create_order response from unknown peer: {}", + counterparty_node_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + }) + } + } + + Ok(()) + } + + fn handle_create_order_error( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, error: ResponseError, + ) -> Result<(), LightningError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + match outer_state_lock.get(&counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + + let channel_id = + peer_state.request_to_cid.remove(&request_id).ok_or(LightningError { + err: format!( + "Received create order error for an unknown request: {:?}", + request_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + let inbound_channel = peer_state + .inbound_channels_by_id + .get_mut(&channel_id) + .ok_or(LightningError { + err: format!( + "Received create order error for an unknown channel: {:?}", + channel_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + Ok(()) + } + None => { + return Err(LightningError { err: format!("Received error response for a create order request from an unknown counterparty ({:?})",counterparty_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); + } + } + } + + pub fn check_order_status( + &self, counterparty_node_id: &PublicKey, order_id: OrderId, channel_id: u128, + ) -> Result<(), APIError> { + let outer_state_lock = self.per_peer_state.write().unwrap(); + match outer_state_lock.get(&counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + + if let Some(inbound_channel) = + peer_state.inbound_channels_by_id.get_mut(&channel_id) + { + if let Err(e) = inbound_channel.pay_for_channel(channel_id) { + peer_state.remove_inbound_channel(channel_id); + return Err(APIError::APIMisuseError { err: e.err }); + } + + let request_id = self.generate_request_id(); + peer_state.insert_request(request_id.clone(), channel_id); + + { + let mut pending_messages = self.pending_messages.lock().unwrap(); + pending_messages.push(( + *counterparty_node_id, + LSPS1Message::Request( + request_id, + LSPS1Request::GetOrder(GetOrderRequest { + order_id: order_id.clone(), + }), + ) + .into(), + )); + } + if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { + peer_manager.process_events(); + } + } else { + return Err(APIError::APIMisuseError { + err: format!("Channel with id {} not found", channel_id), + }); + } + } + None => { + return Err(APIError::APIMisuseError { + err: format!("No existing state with counterparty {}", counterparty_node_id), + }) + } + } + + Ok(()) + } + + fn handle_get_order_request( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, params: GetOrderRequest, + ) -> Result<(), LightningError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + match outer_state_lock.get(&counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + + let outbound_channel = peer_state + .outbound_channels_by_order_id + .get_mut(¶ms.order_id) + .ok_or(LightningError { + err: format!( + "Received get order request for unknown order id {:?}", + params.order_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + if let Err(e) = outbound_channel.create_payment_invoice() { + peer_state.outbound_channels_by_order_id.remove(¶ms.order_id); + self.enqueue_event(Event::LSPS1(super::event::Event::Refund { + request_id, + counterparty_node_id: *counterparty_node_id, + order_id: params.order_id, + })); + return Err(e); + } + + peer_state + .pending_requests + .insert(request_id.clone(), LSPS1Request::GetOrder(params.clone())); + + self.enqueue_event(Event::LSPS1(super::event::Event::CheckPaymentConfirmation { + request_id, + counterparty_node_id: *counterparty_node_id, + order_id: params.order_id, + })); + } + None => { + return Err(LightningError { + err: format!("Received error response for a create order request from an unknown counterparty ({:?})",counterparty_node_id), + action: ErrorAction::IgnoreAndLog(Level::Info), + }); + } + } + + Ok(()) + } + + pub fn update_order_status( + &self, request_id: RequestId, counterparty_node_id: PublicKey, order_id: OrderId, + order_state: OrderState, channel: Option, + ) -> Result<(), APIError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + + match outer_state_lock.get(&counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + + if let Some(outbound_channel) = + peer_state.outbound_channels_by_order_id.get_mut(&order_id) + { + let config = &outbound_channel.config; + + self.enqueue_response( + counterparty_node_id, + request_id, + LSPS1Response::GetOrder(GetOrderResponse { + response: CreateOrderResponse { + order_id, + order: config.order.clone(), + order_state, + created_at: config.created_at, + expires_at: config.expires_at, + payment: config.payment.clone(), + channel, + }, + }), + ) + } else { + return Err(APIError::APIMisuseError { + err: format!("Channel with order_id {} not found", order_id.0), + }); + } + } + None => { + return Err(APIError::APIMisuseError { + err: format!("No existing state with counterparty {}", counterparty_node_id), + }) + } + } + Ok(()) + } + + fn handle_get_order_response( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, params: GetOrderResponse, + ) -> Result<(), LightningError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + match outer_state_lock.get(&counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + + let channel_id = + peer_state.request_to_cid.remove(&request_id).ok_or(LightningError { + err: format!( + "Received get_versions response for an unknown request: {:?}", + request_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + let inbound_channel = peer_state + .inbound_channels_by_id + .get_mut(&channel_id) + .ok_or(LightningError { + err: format!( + "Received get_versions response for an unknown channel: {:?}", + channel_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + } + None => { + return Err(LightningError { + err: format!( + "Received get_order response from unknown peer: {}", + counterparty_node_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + }) + } + } + + Ok(()) + } + + fn handle_get_order_error( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, params: ResponseError, + ) -> Result<(), LightningError> { + let outer_state_lock = self.per_peer_state.read().unwrap(); + match outer_state_lock.get(&counterparty_node_id) { + Some(inner_state_lock) => { + let mut peer_state = inner_state_lock.lock().unwrap(); + + let channel_id = + peer_state.request_to_cid.remove(&request_id).ok_or(LightningError { + err: format!( + "Received get_order error for an unknown request: {:?}", + request_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + + let _inbound_channel = peer_state + .inbound_channels_by_id + .get_mut(&channel_id) + .ok_or(LightningError { + err: format!( + "Received get_order error for an unknown channel: {:?}", + channel_id + ), + action: ErrorAction::IgnoreAndLog(Level::Info), + })?; + Ok(()) + } + None => { + return Err(LightningError { err: format!("Received get_order response for a create order request from an unknown counterparty ({:?})",counterparty_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); + } + } + } + + fn enqueue_response( + &self, counterparty_node_id: PublicKey, request_id: RequestId, response: LSPS1Response, + ) { + { + let mut pending_messages = self.pending_messages.lock().unwrap(); + pending_messages + .push((counterparty_node_id, LSPS1Message::Response(request_id, response).into())); + } + + if let Some(peer_manager) = self.peer_manager.lock().unwrap().as_ref() { + peer_manager.process_events(); + } + } + + fn enqueue_event(&self, event: Event) { + self.pending_events.enqueue(event); + } + + fn generate_channel_id(&self) -> u128 { + let bytes = self.entropy_source.get_secure_random_bytes(); + let mut id_bytes: [u8; 16] = [0; 16]; + id_bytes.copy_from_slice(&bytes[0..16]); + u128::from_be_bytes(id_bytes) + } + + fn generate_request_id(&self) -> RequestId { + let bytes = self.entropy_source.get_secure_random_bytes(); + RequestId(utils::hex_str(&bytes[0..16])) + } + + fn generate_order_id(&self) -> OrderId { + let bytes = self.entropy_source.get_secure_random_bytes(); + OrderId(utils::hex_str(&bytes[0..16])) + } +} + +impl< + ES: Deref, + M: Deref, + T: Deref, + F: Deref, + R: Deref, + SP: Deref, + Descriptor: SocketDescriptor, + L: Deref, + RM: Deref, + CM: Deref, + OM: Deref, + CMH: Deref, + NS: Deref, + > ProtocolMessageHandler for CRManager +where + ES::Target: EntropySource, + M::Target: chain::Watch<::Signer>, + T::Target: BroadcasterInterface, + F::Target: FeeEstimator, + R::Target: Router, + SP::Target: SignerProvider, + L::Target: Logger, + RM::Target: RoutingMessageHandler, + CM::Target: ChannelMessageHandler, + OM::Target: OnionMessageHandler, + CMH::Target: CustomMessageHandler, + NS::Target: NodeSigner, +{ + type ProtocolMessage = LSPS1Message; + const PROTOCOL_NUMBER: Option = Some(2); + + fn handle_message( + &self, message: Self::ProtocolMessage, counterparty_node_id: &PublicKey, + ) -> Result<(), LightningError> { + match message { + LSPS1Message::Request(request_id, request) => match request { + super::msgs::LSPS1Request::GetInfo(_) => { + self.handle_get_info_request(request_id, counterparty_node_id) + } + super::msgs::LSPS1Request::CreateOrder(params) => { + self.handle_create_order_request(request_id, counterparty_node_id, params) + } + super::msgs::LSPS1Request::GetOrder(params) => { + self.handle_get_order_request(request_id, counterparty_node_id, params) + } + }, + LSPS1Message::Response(request_id, response) => match response { + super::msgs::LSPS1Response::GetInfo(params) => { + self.handle_get_info_response(request_id, counterparty_node_id, params) + } + super::msgs::LSPS1Response::CreateOrder(params) => { + self.handle_create_order_response(request_id, counterparty_node_id, params) + } + super::msgs::LSPS1Response::CreateOrderError(params) => { + self.handle_create_order_error(request_id, counterparty_node_id, params) + } + super::msgs::LSPS1Response::GetOrder(params) => { + self.handle_get_order_response(request_id, counterparty_node_id, params) + } + super::msgs::LSPS1Response::GetOrderError(error) => { + self.handle_get_order_error(request_id, counterparty_node_id, error) + } + }, + } + } +} diff --git a/src/channel_request/event.rs b/src/channel_request/event.rs new file mode 100644 index 0000000..9942e82 --- /dev/null +++ b/src/channel_request/event.rs @@ -0,0 +1,59 @@ +#![allow(missing_docs)] + +use bitcoin::secp256k1::PublicKey; + +use super::msgs::{ChannelInfo, OptionsSupported, Order, OrderId, Payment}; +use crate::transport::msgs::RequestId; + +/// An "Event" which you should probably take some action in response to. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Event { + GetInfoResponse { + id: u128, + request_id: RequestId, + counterparty_node_id: PublicKey, + version: u16, + website: String, + options_supported: OptionsSupported, + }, + + CreateInvoice { + request_id: RequestId, + counterparty_node_id: PublicKey, + order: Order, + }, + + DisplayOrder { + id: u128, + counterparty_node_id: PublicKey, + order: Order, + payment: Payment, + channel: Option, + }, + + PayforChannel { + request_id: RequestId, + counterparty_node_id: PublicKey, + order: Order, + payment: Payment, + channel: Option, + }, + + CheckPaymentConfirmation { + request_id: RequestId, + counterparty_node_id: PublicKey, + order_id: OrderId, + }, + + OpenChannel { + request_id: RequestId, + counterparty_node_id: PublicKey, + order_id: OrderId, + }, + + Refund { + request_id: RequestId, + counterparty_node_id: PublicKey, + order_id: OrderId, + }, +} diff --git a/src/channel_request/mod.rs b/src/channel_request/mod.rs index b3b102c..07f75f1 100644 --- a/src/channel_request/mod.rs +++ b/src/channel_request/mod.rs @@ -8,3 +8,8 @@ // licenses. //! Types and primitives that implement the LSPS1: Channel Request specification. +pub(crate) mod channel_manager; +pub(crate) mod event; +/// Message, request, and other primitive types used to implement LSPS2. +pub mod msgs; +pub(crate) mod utils; diff --git a/src/channel_request/msgs.rs b/src/channel_request/msgs.rs new file mode 100644 index 0000000..2d8dd0e --- /dev/null +++ b/src/channel_request/msgs.rs @@ -0,0 +1,187 @@ +use chrono::Utc; +use std::convert::TryFrom; + +use serde::{Deserialize, Serialize}; + +use crate::transport::msgs::{LSPSMessage, RequestId, ResponseError}; + +pub(crate) const LSPS1_GET_INFO_METHOD_NAME: &str = "lsps1.get_info"; +pub(crate) const LSPS1_CREATE_ORDER_METHOD_NAME: &str = "lsps1.create_order"; +pub(crate) const LSPS1_GET_ORDER_METHOD_NAME: &str = "lsps1.get_order"; + +pub(crate) const LSPS1_CREATE_ORDER_REQUEST_INVALID_PARAMS_ERROR_CODE: i32 = -32602; +pub(crate) const LSPS1_CREATE_ORDER_REQUEST_ORDER_MISMATCH_ERROR_CODE: i32 = 1000; +pub(crate) const LSPS1_CREATE_ORDER_REQUEST_CLIENT_REJECTED_ERROR_CODE: i32 = 1001; +pub(crate) const LSPS1_CREATE_ORDER_REQUEST_INVALID_VERSION_ERROR_CODE: i32 = 1; +pub(crate) const LSPS1_CREATE_ORDER_REQUEST_INVALID_TOKEN_ERROR_CODE: i32 = 2; + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Hash)] +pub struct OrderId(pub String); + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] +#[serde(default)] +pub struct GetInfoRequest {} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct OptionsSupported { + pub minimum_channel_confirmations: u8, + pub minimum_onchain_payment_confirmations: u8, + pub supports_zero_channel_reserve: bool, + pub min_onchain_payment_size_sat: Option, + pub max_channel_expiry_blocks: u32, + pub min_initial_client_balance_sat: u64, + pub max_initial_client_balance_sat: u64, + pub min_initial_lsp_balance_sat: u64, + pub max_initial_lsp_balance_sat: u64, + pub min_channel_balance_sat: u64, + pub max_channel_balance_sat: u64, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct GetInfoResponse { + pub supported_versions: Vec, + pub website: String, + pub options: OptionsSupported, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct CreateOrderRequest { + pub version: u16, + pub order: Order, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct Order { + pub api_version: u16, + pub lsp_balance_sat: u64, + pub client_balance_sat: u64, + pub confirms_within_blocks: u32, + pub channel_expiry_blocks: u32, + pub token: String, + pub announce_channel: bool, + pub refund_onchain_address: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct CreateOrderResponse { + pub order_id: OrderId, + pub order: Order, + pub order_state: OrderState, + pub created_at: chrono::DateTime, + pub expires_at: chrono::DateTime, + pub payment: Payment, + pub channel: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub enum OrderState { + Requested, + Created, + Completed, + Failed, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct Payment { + pub state: PaymentState, + pub fee_total_sat: u64, + pub order_total_sat: u64, + pub onchain_address: String, + pub bolt11_invoice: String, + pub onchain_block_confirmations_required: u8, + pub minimum_fee_for_0conf: u8, + pub onchain_payment: OnchainPayment, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub enum PaymentState { + ExpectPayment, + Hold, + Paid, + Refunded, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct OnchainPayment { + pub outpoint: String, + pub sat: u64, + pub confirmed: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct ChannelInfo { + pub state: ChannelStatus, + pub funded_at: String, + pub funding_outpoint: String, + pub scid: Option, + pub expires_at: String, + pub closing_transaction: Option, + pub closed_at: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub enum ChannelStatus { + Opening, + Opened, + Closed, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct GetOrderRequest { + pub order_id: OrderId, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct GetOrderResponse { + pub response: CreateOrderResponse, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum LSPS1Request { + GetInfo(GetInfoRequest), + CreateOrder(CreateOrderRequest), + GetOrder(GetOrderRequest), +} + +impl LSPS1Request { + pub fn method(&self) -> &str { + match self { + LSPS1Request::GetInfo(_) => LSPS1_GET_INFO_METHOD_NAME, + LSPS1Request::CreateOrder(_) => LSPS1_CREATE_ORDER_METHOD_NAME, + LSPS1Request::GetOrder(_) => LSPS1_GET_ORDER_METHOD_NAME, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum LSPS1Response { + GetInfo(GetInfoResponse), + CreateOrder(CreateOrderResponse), + CreateOrderError(ResponseError), + GetOrder(GetOrderResponse), + GetOrderError(ResponseError), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum LSPS1Message { + Request(RequestId, LSPS1Request), + Response(RequestId, LSPS1Response), +} + +impl TryFrom for LSPS1Message { + type Error = (); + + fn try_from(message: LSPSMessage) -> Result { + if let LSPSMessage::LSPS1(message) = message { + return Ok(message); + } + + Err(()) + } +} + +impl From for LSPSMessage { + fn from(message: LSPS1Message) -> Self { + LSPSMessage::LSPS1(message) + } +} diff --git a/src/channel_request/utils.rs b/src/channel_request/utils.rs new file mode 100644 index 0000000..298b6d4 --- /dev/null +++ b/src/channel_request/utils.rs @@ -0,0 +1,23 @@ +use super::msgs::{OptionsSupported, Order}; + +pub fn check_range(min: u64, max: u64, value: u64) -> bool { + (value >= min) && (value <= max) +} + +pub fn is_valid(order: &Order, options: &OptionsSupported) -> bool { + let bool = check_range( + options.min_initial_client_balance_sat, + options.max_initial_client_balance_sat, + order.client_balance_sat, + ) && check_range( + options.min_initial_lsp_balance_sat, + options.max_initial_lsp_balance_sat, + order.lsp_balance_sat, + ) && check_range( + 1, + options.max_channel_expiry_blocks.into(), + order.channel_expiry_blocks.into(), + ); + + bool +} diff --git a/src/events.rs b/src/events.rs index f54d9c6..bb1e9b6 100644 --- a/src/events.rs +++ b/src/events.rs @@ -14,6 +14,8 @@ //! [`LiquidityManager::get_and_clear_pending_events`] to receive events. //! //! [`LiquidityManager::get_and_clear_pending_events`]: crate::LiquidityManager::get_and_clear_pending_events +/// [`crate::LiquidityManager::get_and_clear_pending_events()`] to receive events. +use crate::channel_request; use crate::jit_channel; use std::collections::VecDeque; use std::sync::{Condvar, Mutex}; @@ -60,4 +62,6 @@ impl EventQueue { pub enum Event { /// An LSPS2 (JIT Channel) protocol event. LSPS2(jit_channel::LSPS2Event), + /// An LSPS1 protocol event. + LSPS1(channel_request::event::Event), } diff --git a/src/lib.rs b/src/lib.rs index 6c928fd..00cae42 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ pub mod jit_channel; mod transport; mod utils; +pub use channel_request::event::Event as LSPS1Event; pub use transport::message_handler::{ JITChannelsConfig, LiquidityManager, LiquidityProviderConfig, }; diff --git a/src/transport/message_handler.rs b/src/transport/message_handler.rs index 300ee65..bd5fa57 100644 --- a/src/transport/message_handler.rs +++ b/src/transport/message_handler.rs @@ -1,3 +1,8 @@ +#![allow(missing_docs)] +use crate::channel_request::channel_manager::CRManager; +use crate::channel_request::msgs::{ + ChannelInfo, OptionsSupported, Order, OrderId, OrderState, Payment, +}; use crate::events::{Event, EventQueue}; use crate::jit_channel::channel_manager::JITChannelManager; use crate::jit_channel::msgs::{OpeningFeeParams, RawOpeningFeeParams}; @@ -5,6 +10,7 @@ use crate::transport::msgs::RequestId; use crate::transport::msgs::{LSPSMessage, RawLSPSMessage, LSPS_MESSAGE_TYPE_ID}; use crate::transport::protocol::LSPS0MessageHandler; +use chrono::Utc; use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use lightning::chain::{self, BestBlock, Confirm, Filter, Listen}; use lightning::ln::channelmanager::{ChainParameters, ChannelManager, InterceptId}; @@ -50,6 +56,8 @@ pub(crate) trait ProtocolMessageHandler { /// Allows end-user to configure options when using the [`LiquidityManager`] /// to provide liquidity services to clients. pub struct LiquidityProviderConfig { + /// LSPS1 Configuration + pub lsps1_config: Option, /// Optional configuration for JIT channels /// should you want to support them. pub jit_channels: Option, @@ -67,6 +75,13 @@ pub struct JITChannelsConfig { pub max_payment_size_msat: u64, } +pub struct CRChannelConfig { + pub token: Option, + pub max_fees: Option, + pub options_supported: Option, + pub website: Option, +} + /// The main interface into LSP functionality. /// /// Should be used as a [`CustomMessageHandler`] for your @@ -120,6 +135,8 @@ pub struct LiquidityManager< pending_events: Arc, request_id_to_method_map: Mutex>, lsps0_message_handler: LSPS0MessageHandler, + lsps1_message_handler: + Option>, lsps2_message_handler: Option>, provider_config: Option, @@ -172,8 +189,11 @@ where { let pending_messages = Arc::new(Mutex::new(vec![])); let pending_events = Arc::new(EventQueue::default()); - let lsps0_message_handler = - LSPS0MessageHandler::new(entropy_source.clone(), vec![], Arc::clone(&pending_messages)); + let lsps0_message_handler = LSPS0MessageHandler::new( + entropy_source.clone().clone(), + vec![], + Arc::clone(&pending_messages), + ); let lsps2_message_handler = provider_config.as_ref().and_then(|config| { config.jit_channels.as_ref().map(|jit_channels_config| { @@ -187,11 +207,24 @@ where { }) }); + let lsps1_message_handler = provider_config.as_ref().and_then(|config| { + config.lsps1_config.as_ref().map(|lsps1_config| { + CRManager::new( + entropy_source.clone(), + lsps1_config, + Arc::clone(&pending_messages), + Arc::clone(&pending_events), + Arc::clone(&channel_manager), + ) + }) + }); + Self { pending_messages, pending_events, request_id_to_method_map: Mutex::new(HashMap::new()), lsps0_message_handler, + lsps1_message_handler, lsps2_message_handler, provider_config, channel_manager, @@ -227,11 +260,73 @@ where { pub fn set_peer_manager( &self, peer_manager: Arc>, ) { + if let Some(lsps1_message_handler) = &self.lsps1_message_handler { + lsps1_message_handler.set_peer_manager(peer_manager.clone()); + } if let Some(lsps2_message_handler) = &self.lsps2_message_handler { lsps2_message_handler.set_peer_manager(peer_manager); } } + pub fn request_for_info( + &self, counterparty_node_id: PublicKey, channel_id: u128, + ) -> Result<(), APIError> { + if let Some(lsps1_message_handler) = &self.lsps1_message_handler { + lsps1_message_handler.request_for_info(counterparty_node_id, channel_id); + } + Ok(()) + } + + pub fn place_order( + &self, channel_id: u128, counterparty_node_id: &PublicKey, order: Order, + ) -> Result<(), APIError> { + if let Some(lsps1_message_handler) = &self.lsps1_message_handler { + lsps1_message_handler.place_order(channel_id, counterparty_node_id, order)?; + } + Ok(()) + } + + pub fn send_invoice_for_order( + &self, request_id: RequestId, counterparty_node_id: &PublicKey, payment: Payment, + created_at: chrono::DateTime, expires_at: chrono::DateTime, + ) -> Result<(), APIError> { + if let Some(lsps1_message_handler) = &self.lsps1_message_handler { + lsps1_message_handler.send_invoice_for_order( + request_id, + counterparty_node_id, + payment, + created_at, + expires_at, + )?; + } + Ok(()) + } + + pub fn check_order_status( + self, channel_id: u128, counterparty_node_id: &PublicKey, order_id: OrderId, + ) -> Result<(), APIError> { + if let Some(lsps1_message_handler) = &self.lsps1_message_handler { + lsps1_message_handler.check_order_status(counterparty_node_id, order_id, channel_id)?; + } + Ok(()) + } + + pub fn update_order_status( + &self, request_id: RequestId, counterparty_node_id: PublicKey, order_id: OrderId, + order_state: OrderState, channel: Option, + ) -> Result<(), APIError> { + if let Some(lsps1_message_handler) = &self.lsps1_message_handler { + lsps1_message_handler.update_order_status( + request_id, + counterparty_node_id, + order_id, + order_state, + channel, + )?; + } + Ok(()) + } + /// Initiate the creation of an invoice that when paid will open a channel /// with enough inbound liquidity to be able to receive the payment. /// @@ -396,6 +491,14 @@ where { LSPSMessage::LSPS0(msg) => { self.lsps0_message_handler.handle_message(msg, sender_node_id)?; } + LSPSMessage::LSPS1(msg) => match &self.lsps1_message_handler { + Some(lsps1_message_handler) => { + lsps1_message_handler.handle_message(msg, sender_node_id)?; + } + None => { + return Err(LightningError { err: format!("Received LSPS1 message without LSPS1 message handler configured. From node = {:?}", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)}); + } + }, LSPSMessage::LSPS2(msg) => match &self.lsps2_message_handler { Some(lsps2_message_handler) => { lsps2_message_handler.handle_message(msg, sender_node_id)?; @@ -415,7 +518,7 @@ where { } impl< - ES: Deref + Clone, + ES: Deref + Clone + Clone, M: Deref, T: Deref, F: Deref, diff --git a/src/transport/msgs.rs b/src/transport/msgs.rs index 99b2578..bf89249 100644 --- a/src/transport/msgs.rs +++ b/src/transport/msgs.rs @@ -1,8 +1,11 @@ +use crate::channel_request::msgs::{ + LSPS1Message, LSPS1Request, LSPS1Response, LSPS1_CREATE_ORDER_METHOD_NAME, + LSPS1_GET_INFO_METHOD_NAME, LSPS1_GET_ORDER_METHOD_NAME, +}; use crate::jit_channel::msgs::{ LSPS2Message, LSPS2Request, LSPS2Response, LSPS2_BUY_METHOD_NAME, LSPS2_GET_INFO_METHOD_NAME, LSPS2_GET_VERSIONS_METHOD_NAME, }; - use lightning::impl_writeable_msg; use lightning::ln::wire; use serde::de; @@ -95,6 +98,7 @@ impl TryFrom for LSPS0Message { LSPSMessage::Invalid => Err(()), LSPSMessage::LSPS0(message) => Ok(message), LSPSMessage::LSPS2(_) => Err(()), + LSPSMessage::LSPS1(_) => Err(()), } } } @@ -110,6 +114,7 @@ pub enum LSPSMessage { Invalid, LSPS0(LSPS0Message), LSPS2(LSPS2Message), + LSPS1(LSPS1Message), } impl LSPSMessage { @@ -129,6 +134,9 @@ impl LSPSMessage { LSPSMessage::LSPS2(LSPS2Message::Request(request_id, request)) => { Some((request_id.0.clone(), request.method().to_string())) } + LSPSMessage::LSPS1(LSPS1Message::Request(request_id, request)) => { + Some((request_id.0.clone(), request.method().to_string())) + } _ => None, } } @@ -167,6 +175,43 @@ impl Serialize for LSPSMessage { } } } + LSPSMessage::LSPS1(LSPS1Message::Request(request_id, request)) => { + jsonrpc_object.serialize_field(JSONRPC_ID_FIELD_KEY, &request_id.0)?; + jsonrpc_object.serialize_field(JSONRPC_METHOD_FIELD_KEY, request.method())?; + + match request { + LSPS1Request::GetInfo(params) => { + jsonrpc_object.serialize_field(JSONRPC_PARAMS_FIELD_KEY, params)? + } + LSPS1Request::CreateOrder(params) => { + jsonrpc_object.serialize_field(JSONRPC_PARAMS_FIELD_KEY, params)? + } + LSPS1Request::GetOrder(params) => { + jsonrpc_object.serialize_field(JSONRPC_PARAMS_FIELD_KEY, params)? + } + } + } + LSPSMessage::LSPS1(LSPS1Message::Response(request_id, response)) => { + jsonrpc_object.serialize_field(JSONRPC_ID_FIELD_KEY, &request_id.0)?; + + match response { + LSPS1Response::GetInfo(result) => { + jsonrpc_object.serialize_field(JSONRPC_RESULT_FIELD_KEY, result)? + } + LSPS1Response::CreateOrder(result) => { + jsonrpc_object.serialize_field(JSONRPC_ERROR_FIELD_KEY, result)? + } + LSPS1Response::CreateOrderError(error) => { + jsonrpc_object.serialize_field(JSONRPC_RESULT_FIELD_KEY, error)? + } + LSPS1Response::GetOrder(result) => { + jsonrpc_object.serialize_field(JSONRPC_ERROR_FIELD_KEY, result)? + } + LSPS1Response::GetOrderError(error) => { + jsonrpc_object.serialize_field(JSONRPC_ERROR_FIELD_KEY, &error)? + } + } + } LSPSMessage::LSPS2(LSPS2Message::Request(request_id, request)) => { jsonrpc_object.serialize_field(JSONRPC_ID_FIELD_KEY, &request_id.0)?; jsonrpc_object.serialize_field(JSONRPC_METHOD_FIELD_KEY, request.method())?; @@ -274,6 +319,30 @@ impl<'de, 'a> Visitor<'de> for LSPSMessageVisitor<'a> { LSPS0Request::ListProtocols(ListProtocolsRequest {}), ))) } + LSPS1_GET_INFO_METHOD_NAME => { + let request = serde_json::from_value(params.unwrap_or(json!({}))) + .map_err(de::Error::custom)?; + Ok(LSPSMessage::LSPS1(LSPS1Message::Request( + RequestId(id), + LSPS1Request::GetInfo(request), + ))) + } + LSPS1_CREATE_ORDER_METHOD_NAME => { + let request = serde_json::from_value(params.unwrap_or(json!({}))) + .map_err(de::Error::custom)?; + Ok(LSPSMessage::LSPS1(LSPS1Message::Request( + RequestId(id), + LSPS1Request::CreateOrder(request), + ))) + } + LSPS1_GET_ORDER_METHOD_NAME => { + let request = serde_json::from_value(params.unwrap_or(json!({}))) + .map_err(de::Error::custom)?; + Ok(LSPSMessage::LSPS1(LSPS1Message::Request( + RequestId(id), + LSPS1Request::GetOrder(request), + ))) + } LSPS2_GET_VERSIONS_METHOD_NAME => { let request = serde_json::from_value(params.unwrap_or(json!({}))) .map_err(de::Error::custom)?; @@ -334,6 +403,40 @@ impl<'de, 'a> Visitor<'de> for LSPSMessageVisitor<'a> { Err(de::Error::custom("Received invalid lsps2.get_versions response.")) } } + LSPS1_CREATE_ORDER_METHOD_NAME => { + if let Some(error) = error { + Ok(LSPSMessage::LSPS1(LSPS1Message::Response( + RequestId(id), + LSPS1Response::CreateOrderError(error), + ))) + } else if let Some(result) = result { + let response = + serde_json::from_value(result).map_err(de::Error::custom)?; + Ok(LSPSMessage::LSPS1(LSPS1Message::Response( + RequestId(id), + LSPS1Response::CreateOrder(response), + ))) + } else { + Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required")) + } + } + LSPS1_GET_ORDER_METHOD_NAME => { + if let Some(error) = error { + Ok(LSPSMessage::LSPS1(LSPS1Message::Response( + RequestId(id), + LSPS1Response::GetOrderError(error), + ))) + } else if let Some(result) = result { + let response = + serde_json::from_value(result).map_err(de::Error::custom)?; + Ok(LSPSMessage::LSPS1(LSPS1Message::Response( + RequestId(id), + LSPS1Response::GetOrder(response), + ))) + } else { + Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required")) + } + } LSPS2_GET_INFO_METHOD_NAME => { if let Some(error) = error { Ok(LSPSMessage::LSPS2(LSPS2Message::Response(