Skip to content

Commit 54020a1

Browse files
committed
Add LSPS2ServiceEvent handling
.. so far we just silently fail if something goes wrong, eventually we'll need to implement retrying channel opens to honor buy requests that didn't succeed on the first attempt.
1 parent ae92fd3 commit 54020a1

File tree

2 files changed

+268
-4
lines changed

2 files changed

+268
-4
lines changed

src/builder.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,7 @@ fn build_with_store_internal(
12061206
let (liquidity_source, custom_message_handler) =
12071207
if let Some(lsc) = liquidity_source_config.as_ref() {
12081208
let mut liquidity_source_builder = LiquiditySourceBuilder::new(
1209+
Arc::clone(&wallet),
12091210
Arc::clone(&channel_manager),
12101211
Arc::clone(&keys_manager),
12111212
Arc::clone(&chain_source),

src/liquidity.rs

+267-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::chain::ChainSource;
1111
use crate::connection::ConnectionManager;
1212
use crate::logger::{log_debug, log_error, log_info, LdkLogger, Logger};
1313
use crate::types::{ChannelManager, KeysManager, LiquidityManager, PeerManager, Wallet};
14-
use crate::{Config, Error};
14+
use crate::{total_anchor_channels_reserve_sats, Config, Error};
1515

1616
use lightning::events::HTLCDestination;
1717
use lightning::ln::channelmanager::{InterceptId, MIN_FINAL_CLTV_EXPIRY_DELTA};
@@ -27,8 +27,8 @@ use lightning_liquidity::lsps1::client::LSPS1ClientConfig as LdkLSPS1ClientConfi
2727
use lightning_liquidity::lsps1::event::LSPS1ClientEvent;
2828
use lightning_liquidity::lsps1::msgs::{ChannelInfo, LSPS1Options, OrderId, OrderParameters};
2929
use lightning_liquidity::lsps2::client::LSPS2ClientConfig as LdkLSPS2ClientConfig;
30-
use lightning_liquidity::lsps2::event::LSPS2ClientEvent;
31-
use lightning_liquidity::lsps2::msgs::OpeningFeeParams;
30+
use lightning_liquidity::lsps2::event::{LSPS2ClientEvent, LSPS2ServiceEvent};
31+
use lightning_liquidity::lsps2::msgs::{OpeningFeeParams, RawOpeningFeeParams};
3232
use lightning_liquidity::lsps2::service::LSPS2ServiceConfig as LdkLSPS2ServiceConfig;
3333
use lightning_liquidity::lsps2::utils::compute_opening_fee;
3434
use lightning_liquidity::{LiquidityClientConfig, LiquidityServiceConfig};
@@ -40,13 +40,21 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1};
4040

4141
use tokio::sync::oneshot;
4242

43+
use chrono::{DateTime, Utc};
44+
45+
use rand::Rng;
46+
4347
use std::collections::HashMap;
4448
use std::ops::Deref;
4549
use std::sync::{Arc, Mutex, RwLock};
4650
use std::time::Duration;
4751

4852
const LIQUIDITY_REQUEST_TIMEOUT_SECS: u64 = 5;
4953

54+
const LSPS2_GETINFO_REQUEST_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24);
55+
const LSPS2_CLIENT_TRUSTS_LSP_MODE: bool = true;
56+
const LSPS2_CHANNEL_CLTV_EXPIRY_DELTA: u32 = 72;
57+
5058
struct LSPS1Client {
5159
lsp_node_id: PublicKey,
5260
lsp_address: SocketAddress,
@@ -132,6 +140,7 @@ where
132140
lsps1_client: Option<LSPS1Client>,
133141
lsps2_client: Option<LSPS2Client>,
134142
lsps2_service: Option<LSPS2Service>,
143+
wallet: Arc<Wallet>,
135144
channel_manager: Arc<ChannelManager>,
136145
keys_manager: Arc<KeysManager>,
137146
chain_source: Arc<ChainSource>,
@@ -144,7 +153,7 @@ where
144153
L::Target: LdkLogger,
145154
{
146155
pub(crate) fn new(
147-
channel_manager: Arc<ChannelManager>, keys_manager: Arc<KeysManager>,
156+
wallet: Arc<Wallet>, channel_manager: Arc<ChannelManager>, keys_manager: Arc<KeysManager>,
148157
chain_source: Arc<ChainSource>, config: Arc<Config>, logger: L,
149158
) -> Self {
150159
let lsps1_client = None;
@@ -154,6 +163,7 @@ where
154163
lsps1_client,
155164
lsps2_client,
156165
lsps2_service,
166+
wallet,
157167
channel_manager,
158168
keys_manager,
159169
chain_source,
@@ -232,7 +242,9 @@ where
232242
lsps1_client: self.lsps1_client,
233243
lsps2_client: self.lsps2_client,
234244
lsps2_service: self.lsps2_service,
245+
wallet: self.wallet,
235246
channel_manager: self.channel_manager,
247+
peer_manager: RwLock::new(None),
236248
keys_manager: self.keys_manager,
237249
liquidity_manager,
238250
config: self.config,
@@ -248,7 +260,9 @@ where
248260
lsps1_client: Option<LSPS1Client>,
249261
lsps2_client: Option<LSPS2Client>,
250262
lsps2_service: Option<LSPS2Service>,
263+
wallet: Arc<Wallet>,
251264
channel_manager: Arc<ChannelManager>,
265+
peer_manager: RwLock<Option<Arc<PeerManager>>>,
252266
keys_manager: Arc<KeysManager>,
253267
liquidity_manager: Arc<LiquidityManager>,
254268
config: Arc<Config>,
@@ -260,6 +274,7 @@ where
260274
L::Target: LdkLogger,
261275
{
262276
pub(crate) fn set_peer_manager(&self, peer_manager: Arc<PeerManager>) {
277+
*self.peer_manager.write().unwrap() = Some(Arc::clone(&peer_manager));
263278
let process_msgs_callback = move || peer_manager.process_events();
264279
self.liquidity_manager.set_process_msgs_callback(process_msgs_callback);
265280
}
@@ -447,6 +462,254 @@ where
447462
log_error!(self.logger, "Received unexpected LSPS1Client::OrderStatus event!");
448463
}
449464
},
465+
Event::LSPS2Service(LSPS2ServiceEvent::GetInfo {
466+
request_id,
467+
counterparty_node_id,
468+
token,
469+
}) => {
470+
if let Some(lsps2_service_handler) =
471+
self.liquidity_manager.lsps2_service_handler().as_ref()
472+
{
473+
let service_config = if let Some(service_config) =
474+
self.lsps2_service.as_ref().map(|s| s.service_config.clone())
475+
{
476+
service_config
477+
} else {
478+
log_error!(self.logger, "Failed to handle LSPS2ServiceEvent as LSPS2 liquidity service was not configured.",);
479+
return;
480+
};
481+
482+
if let Some(required) = service_config.require_token {
483+
if token != Some(required) {
484+
log_error!(
485+
self.logger,
486+
"Rejecting LSPS2 request {:?} from counterparty {} as the client provided an invalid token.",
487+
request_id,
488+
counterparty_node_id
489+
);
490+
lsps2_service_handler.invalid_token_provided(&counterparty_node_id, request_id.clone()).unwrap_or_else(|e| {
491+
debug_assert!(false, "Failed to reject LSPS2 request. This should never happen.");
492+
log_error!(
493+
self.logger,
494+
"Failed to reject LSPS2 request {:?} from counterparty {} due to: {:?}. This should never happen.",
495+
request_id,
496+
counterparty_node_id,
497+
e
498+
);
499+
});
500+
return;
501+
}
502+
}
503+
504+
let mut valid_until: DateTime<Utc> = Utc::now();
505+
valid_until += LSPS2_GETINFO_REQUEST_EXPIRY;
506+
507+
let opening_fee_params = RawOpeningFeeParams {
508+
min_fee_msat: service_config.min_channel_opening_fee_msat,
509+
proportional: service_config.channel_opening_fee_ppm,
510+
valid_until,
511+
min_lifetime: service_config.min_channel_lifetime,
512+
max_client_to_self_delay: service_config.max_client_to_self_delay,
513+
min_payment_size_msat: service_config.min_payment_size_msat,
514+
max_payment_size_msat: service_config.max_payment_size_msat,
515+
};
516+
517+
let opening_fee_params_menu = vec![opening_fee_params];
518+
519+
if let Err(e) = lsps2_service_handler.opening_fee_params_generated(
520+
&counterparty_node_id,
521+
request_id,
522+
opening_fee_params_menu,
523+
) {
524+
log_error!(
525+
self.logger,
526+
"Failed to handle generated opening fee params: {:?}",
527+
e
528+
);
529+
}
530+
} else {
531+
log_error!(self.logger, "Failed to handle LSPS2ServiceEvent as LSPS2 liquidity service was not configured.",);
532+
return;
533+
}
534+
},
535+
Event::LSPS2Service(LSPS2ServiceEvent::BuyRequest {
536+
request_id,
537+
counterparty_node_id,
538+
opening_fee_params: _,
539+
payment_size_msat,
540+
}) => {
541+
if let Some(lsps2_service_handler) =
542+
self.liquidity_manager.lsps2_service_handler().as_ref()
543+
{
544+
let service_config = if let Some(service_config) =
545+
self.lsps2_service.as_ref().map(|s| s.service_config.clone())
546+
{
547+
service_config
548+
} else {
549+
log_error!(self.logger, "Failed to handle LSPS2ServiceEvent as LSPS2 liquidity service was not configured.",);
550+
return;
551+
};
552+
553+
let user_channel_id: u128 = rand::thread_rng().gen::<u128>();
554+
let intercept_scid = self.channel_manager.get_intercept_scid();
555+
556+
if let Some(payment_size_msat) = payment_size_msat {
557+
// We already check this in `lightning-liquidity`, but better safe than
558+
// sorry.
559+
//
560+
// TODO: We might want to eventually send back an error here, but we
561+
// currently can't and have to trust `lightning-liquidity` is doing the
562+
// right thing.
563+
//
564+
// TODO: Eventually we also might want to make sure that we have sufficient
565+
// liquidity for the channel opening here.
566+
if payment_size_msat > service_config.max_payment_size_msat
567+
|| payment_size_msat < service_config.min_payment_size_msat
568+
{
569+
log_error!(
570+
self.logger,
571+
"Rejecting to handle LSPS2 buy request {:?} from counterparty {} as the client requested an invalid payment size.",
572+
request_id,
573+
counterparty_node_id
574+
);
575+
return;
576+
}
577+
}
578+
579+
match lsps2_service_handler.invoice_parameters_generated(
580+
&counterparty_node_id,
581+
request_id,
582+
intercept_scid,
583+
LSPS2_CHANNEL_CLTV_EXPIRY_DELTA,
584+
LSPS2_CLIENT_TRUSTS_LSP_MODE,
585+
user_channel_id,
586+
) {
587+
Ok(()) => {},
588+
Err(e) => {
589+
log_error!(
590+
self.logger,
591+
"Failed to provide invoice parameters: {:?}",
592+
e
593+
);
594+
return;
595+
},
596+
}
597+
} else {
598+
log_error!(self.logger, "Failed to handle LSPS2ServiceEvent as LSPS2 liquidity service was not configured.",);
599+
return;
600+
}
601+
},
602+
Event::LSPS2Service(LSPS2ServiceEvent::OpenChannel {
603+
their_network_key,
604+
amt_to_forward_msat,
605+
opening_fee_msat: _,
606+
user_channel_id,
607+
intercept_scid: _,
608+
}) => {
609+
if self.liquidity_manager.lsps2_service_handler().is_none() {
610+
log_error!(self.logger, "Failed to handle LSPS2ServiceEvent as LSPS2 liquidity service was not configured.",);
611+
return;
612+
};
613+
614+
let service_config = if let Some(service_config) =
615+
self.lsps2_service.as_ref().map(|s| s.service_config.clone())
616+
{
617+
service_config
618+
} else {
619+
log_error!(self.logger, "Failed to handle LSPS2ServiceEvent as LSPS2 liquidity service was not configured.",);
620+
return;
621+
};
622+
623+
let init_features = if let Some(peer_manager) =
624+
self.peer_manager.read().unwrap().as_ref()
625+
{
626+
// Fail if we're not connected to the prospective channel partner.
627+
if let Some(peer) = peer_manager.peer_by_node_id(&their_network_key) {
628+
peer.init_features
629+
} else {
630+
// TODO: We just silently fail here. Eventually we will need to remember
631+
// the pending requests and regularly retry opening the channel until we
632+
// succeed.
633+
log_error!(
634+
self.logger,
635+
"Failed to open LSPS2 channel to {} due to peer not being not connected.",
636+
their_network_key,
637+
);
638+
return;
639+
}
640+
} else {
641+
debug_assert!(false, "Failed to handle LSPS2ServiceEvent as peer manager isn't available. This should never happen.",);
642+
log_error!(self.logger, "Failed to handle LSPS2ServiceEvent as peer manager isn't available. This should never happen.",);
643+
return;
644+
};
645+
646+
// Fail if we have insufficient onchain funds available.
647+
let over_provisioning_msat = (amt_to_forward_msat
648+
* service_config.channel_over_provisioning_ppm as u64)
649+
/ 1_000_000;
650+
let channel_amount_sats = (amt_to_forward_msat + over_provisioning_msat) / 1000;
651+
let cur_anchor_reserve_sats =
652+
total_anchor_channels_reserve_sats(&self.channel_manager, &self.config);
653+
let spendable_amount_sats =
654+
self.wallet.get_spendable_amount_sats(cur_anchor_reserve_sats).unwrap_or(0);
655+
let required_funds_sats = channel_amount_sats
656+
+ self.config.anchor_channels_config.as_ref().map_or(0, |c| {
657+
if init_features.requires_anchors_zero_fee_htlc_tx()
658+
&& !c.trusted_peers_no_reserve.contains(&their_network_key)
659+
{
660+
c.per_channel_reserve_sats
661+
} else {
662+
0
663+
}
664+
});
665+
if spendable_amount_sats < required_funds_sats {
666+
log_error!(self.logger,
667+
"Unable to create channel due to insufficient funds. Available: {}sats, Required: {}sats",
668+
spendable_amount_sats, channel_amount_sats
669+
);
670+
// TODO: We just silently fail here. Eventually we will need to remember
671+
// the pending requests and regularly retry opening the channel until we
672+
// succeed.
673+
return;
674+
}
675+
676+
let mut config = *self.channel_manager.get_current_default_configuration();
677+
678+
// Set the HTLC-value-in-flight to 100% of the channel value to ensure we can
679+
// forward the payment.
680+
config
681+
.channel_handshake_config
682+
.max_inbound_htlc_value_in_flight_percent_of_channel = 100;
683+
684+
// We set the forwarding fee to 0 for now as we're getting paid by the channel fee.
685+
//
686+
// TODO: revisit this decision eventually.
687+
config.channel_config.forwarding_fee_base_msat = 0;
688+
config.channel_config.forwarding_fee_proportional_millionths = 0;
689+
690+
match self.channel_manager.create_channel(
691+
their_network_key,
692+
channel_amount_sats,
693+
0,
694+
user_channel_id,
695+
None,
696+
Some(config),
697+
) {
698+
Ok(_) => {},
699+
Err(e) => {
700+
// TODO: We just silently fail here. Eventually we will need to remember
701+
// the pending requests and regularly retry opening the channel until we
702+
// succeed.
703+
log_error!(
704+
self.logger,
705+
"Failed to open LSPS2 channel to {}: {:?}",
706+
their_network_key,
707+
e
708+
);
709+
return;
710+
},
711+
}
712+
},
450713
Event::LSPS2Client(LSPS2ClientEvent::OpeningParametersReady {
451714
request_id,
452715
counterparty_node_id,

0 commit comments

Comments
 (0)