Skip to content

Commit 4a00e8c

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 6e5e0d2 commit 4a00e8c

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

0 commit comments

Comments
 (0)