Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ dictionary LSPS2ServiceConfig {
u32 max_client_to_self_delay;
u64 min_payment_size_msat;
u64 max_payment_size_msat;
boolean client_trusts_lsp;
};

enum LogLevel {
Expand Down Expand Up @@ -197,6 +198,13 @@ interface Bolt11Payment {
Bolt11Invoice receive_variable_amount_via_jit_channel([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat);
[Throws=NodeError]
Bolt11Invoice receive_variable_amount_via_jit_channel_for_hash([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat, PaymentHash payment_hash);
[Throws=NodeError]
JitChannelManualClaim receive_via_jit_channel_manual_claim(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_total_lsp_fee_limit_msat);
};

dictionary JitChannelManualClaim {
Bolt11Invoice invoice;
PaymentPreimage preimage;
};

interface Bolt12Payment {
Expand Down
1 change: 1 addition & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1613,6 +1613,7 @@ fn build_with_store_internal(
Arc::clone(&kv_store),
Arc::clone(&config),
Arc::clone(&logger),
Arc::clone(&tx_broadcaster),
);

lsc.lsps1_client.as_ref().map(|config| {
Expand Down
53 changes: 43 additions & 10 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ where
counterparty_node_id,
channel_value_satoshis,
output_script,
..
user_channel_id,
} => {
// Construct the raw transaction with the output that is paid the amount of the
// channel.
Expand All @@ -506,12 +506,43 @@ where
locktime,
) {
Ok(final_tx) => {
// Give the funding transaction back to LDK for opening the channel.
match self.channel_manager.funding_transaction_generated(
temporary_channel_id,
counterparty_node_id,
final_tx,
) {
let needs_manual_broadcast =
match self.liquidity_source.as_ref().map(|ls| {
ls.as_ref().lsps2_channel_needs_manual_broadcast(
counterparty_node_id,
user_channel_id,
)
}) {
Some(Ok(v)) => v,
Some(Err(e)) => {
log_error!(self.logger, "Failed to determine if channel needs manual broadcast: {:?}", e);
false
},
None => false,
};

let result = if needs_manual_broadcast {
self.liquidity_source.as_ref().map(|ls| {
ls.lsps2_store_funding_transaction(
user_channel_id,
counterparty_node_id,
final_tx.clone(),
);
});
self.channel_manager.funding_transaction_generated_manual_broadcast(
temporary_channel_id,
counterparty_node_id,
final_tx,
)
} else {
self.channel_manager.funding_transaction_generated(
temporary_channel_id,
counterparty_node_id,
final_tx,
)
};

match result {
Ok(()) => {},
Err(APIError::APIMisuseError { err }) => {
log_error!(self.logger, "Panicking due to APIMisuseError: {}", err);
Expand Down Expand Up @@ -550,8 +581,10 @@ where
},
}
},
LdkEvent::FundingTxBroadcastSafe { .. } => {
debug_assert!(false, "We currently only support safe funding, so this event should never be emitted.");
LdkEvent::FundingTxBroadcastSafe { user_channel_id, counterparty_node_id, .. } => {
self.liquidity_source.as_ref().map(|ls| {
ls.lsps2_funding_tx_broadcast_safe(user_channel_id, counterparty_node_id);
});
},
LdkEvent::PaymentClaimable {
payment_hash,
Expand Down Expand Up @@ -676,7 +709,7 @@ where
match info.kind {
PaymentKind::Bolt11 { preimage, .. }
| PaymentKind::Bolt11Jit { preimage, .. } => {
if purpose.preimage().is_none() {
if preimage.is_none() || purpose.preimage().is_none() {
debug_assert!(
preimage.is_none(),
"We would have registered the preimage if we knew"
Expand Down
9 changes: 9 additions & 0 deletions src/ffi/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,15 @@ impl UniffiCustomTypeConverter for LSPSDateTime {
}
}

/// A payable invoice and its corresponding preimage for manual claiming via a JIT channel.
#[derive(Debug, Clone)]
pub struct JitChannelManualClaim {
/// The payable invoice.
pub invoice: Arc<Bolt11Invoice>,
/// The payment preimage.
pub preimage: PaymentPreimage,
}

#[cfg(test)]
mod tests {
use std::num::NonZeroU64;
Expand Down
82 changes: 79 additions & 3 deletions src/liquidity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ use crate::{total_anchor_channels_reserve_sats, Config, Error};
const LIQUIDITY_REQUEST_TIMEOUT_SECS: u64 = 5;

const LSPS2_GETINFO_REQUEST_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24);
const LSPS2_CLIENT_TRUSTS_LSP_MODE: bool = true;
const LSPS2_CHANNEL_CLTV_EXPIRY_DELTA: u32 = 72;

struct LSPS1Client {
Expand Down Expand Up @@ -130,6 +129,8 @@ pub struct LSPS2ServiceConfig {
pub min_payment_size_msat: u64,
/// The maximum payment size that we will accept when opening a channel.
pub max_payment_size_msat: u64,
/// Use the client trusts lsp model
pub client_trusts_lsp: bool,
}

pub(crate) struct LiquiditySourceBuilder<L: Deref>
Expand All @@ -147,6 +148,7 @@ where
kv_store: Arc<DynStore>,
config: Arc<Config>,
logger: L,
broadcaster: Arc<Broadcaster>,
}

impl<L: Deref> LiquiditySourceBuilder<L>
Expand All @@ -156,7 +158,7 @@ where
pub(crate) fn new(
wallet: Arc<Wallet>, channel_manager: Arc<ChannelManager>, keys_manager: Arc<KeysManager>,
chain_source: Arc<ChainSource>, tx_broadcaster: Arc<Broadcaster>, kv_store: Arc<DynStore>,
config: Arc<Config>, logger: L,
config: Arc<Config>, logger: L, broadcaster: Arc<Broadcaster>,
) -> Self {
let lsps1_client = None;
let lsps2_client = None;
Expand All @@ -173,6 +175,7 @@ where
kv_store,
config,
logger,
broadcaster,
}
}

Expand Down Expand Up @@ -305,6 +308,79 @@ where
self.lsps2_client.as_ref().map(|s| (s.lsp_node_id, s.lsp_address.clone()))
}

pub(crate) fn lsps2_channel_needs_manual_broadcast(
&self, counterparty_node_id: PublicKey, user_channel_id: u128,
) -> Result<bool, APIError> {
// if we are not in a client_trusts_lsp model, we don't check and just return false
if !self.is_client_trusts_lsp() {
log_debug!(self.logger, "Skipping funding transaction broadcast as client trusts LSP.");
return Ok(false);
}

// if we are in a client_trusts_lsp model, then we check if the LSP has an LSPS2 operation in progress
self.lsps2_service.as_ref().map_or(Ok(false), |_| {
let lsps2_service_handler = self.liquidity_manager.lsps2_service_handler();
if let Some(handler) = lsps2_service_handler {
handler.channel_needs_manual_broadcast(user_channel_id, &counterparty_node_id)
} else {
log_error!(self.logger, "LSPS2 service handler is not available.");
Ok(false)
}
})
}

pub(crate) fn lsps2_store_funding_transaction(
&self, user_channel_id: u128, counterparty_node_id: PublicKey, funding_tx: Transaction,
) {
if !self.is_client_trusts_lsp() {
log_debug!(self.logger, "Skipping funding transaction broadcast as client trusts LSP.");
return;
}
self.lsps2_service.as_ref().map(|_| {
let lsps2_service_handler = self.liquidity_manager.lsps2_service_handler();
if let Some(handler) = lsps2_service_handler {
handler
.store_funding_transaction(user_channel_id, &counterparty_node_id, funding_tx)
.unwrap_or_else(|e| {
debug_assert!(false, "Failed to store funding transaction: {:?}", e);
log_error!(self.logger, "Failed to store funding transaction: {:?}", e);
});
} else {
log_error!(self.logger, "LSPS2 service handler is not available.");
}
});
}

pub(crate) fn lsps2_funding_tx_broadcast_safe(
&self, user_channel_id: u128, counterparty_node_id: PublicKey,
) {
if !self.is_client_trusts_lsp() {
log_debug!(self.logger, "Skipping funding transaction broadcast as client trusts LSP.");
return;
}
self.lsps2_service.as_ref().map(|_| {
let lsps2_service_handler = self.liquidity_manager.lsps2_service_handler();
if let Some(handler) = lsps2_service_handler {
handler
.set_funding_tx_broadcast_safe(user_channel_id, &counterparty_node_id)
.unwrap_or_else(|e| {
debug_assert!(false, "Failed to store funding transaction: {:?}", e);
log_error!(self.logger, "Failed to store funding transaction: {:?}", e);
});
} else {
log_error!(self.logger, "LSPS2 service handler is not available.");
}
});
}

fn is_client_trusts_lsp(&self) -> bool {
if let Some(lsps2_service) = self.lsps2_service.as_ref() {
lsps2_service.service_config.client_trusts_lsp
} else {
false
}
}

pub(crate) async fn handle_next_event(&self) {
match self.liquidity_manager.next_event_async().await {
LiquidityEvent::LSPS1Client(LSPS1ClientEvent::SupportedOptionsReady {
Expand Down Expand Up @@ -594,7 +670,7 @@ where
request_id,
intercept_scid,
LSPS2_CHANNEL_CLTV_EXPIRY_DELTA,
LSPS2_CLIENT_TRUSTS_LSP_MODE,
service_config.client_trusts_lsp,
user_channel_id,
)
.await
Expand Down
52 changes: 45 additions & 7 deletions src/payment/bolt11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ type Bolt11InvoiceDescription = LdkBolt11InvoiceDescription;
#[cfg(feature = "uniffi")]
type Bolt11InvoiceDescription = crate::ffi::Bolt11InvoiceDescription;

#[cfg(not(feature = "uniffi"))]
pub struct JitChannelManualClaim {
pub invoice: Bolt11Invoice,
pub preimage: PaymentPreimage,
}
#[cfg(feature = "uniffi")]
type JitChannelManualClaim = crate::ffi::JitChannelManualClaim;

/// A payment handler allowing to create and pay [BOLT 11] invoices.
///
/// Should be retrieved by calling [`Node::bolt11_payment`].
Expand Down Expand Up @@ -542,13 +550,14 @@ impl Bolt11Payment {
max_total_lsp_fee_limit_msat: Option<u64>,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_try_convert_enum(description)?;
let invoice = self.receive_via_jit_channel_inner(
let (invoice, _) = self.receive_via_jit_channel_inner(
Some(amount_msat),
&description,
expiry_secs,
max_total_lsp_fee_limit_msat,
None,
None,
true,
)?;
Ok(maybe_wrap(invoice))
}
Expand Down Expand Up @@ -581,17 +590,40 @@ impl Bolt11Payment {
max_total_lsp_fee_limit_msat: Option<u64>, payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_try_convert_enum(description)?;
let invoice = self.receive_via_jit_channel_inner(
let (invoice, _) = self.receive_via_jit_channel_inner(
Some(amount_msat),
&description,
expiry_secs,
max_total_lsp_fee_limit_msat,
None,
Some(payment_hash),
true,
)?;
Ok(maybe_wrap(invoice))
}

/// Returns a payable invoice for manual claiming via a JIT channel.
///
/// Similar to `receive_via_jit_channel` but requires manual claiming via `claim_for_hash`.
pub fn receive_via_jit_channel_manual_claim(
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
max_total_lsp_fee_limit_msat: Option<u64>,
) -> Result<JitChannelManualClaim, Error> {
let description = maybe_try_convert_enum(description)?;
let (invoice, preimage) = self.receive_via_jit_channel_inner(
Some(amount_msat),
&description,
expiry_secs,
max_total_lsp_fee_limit_msat,
None,
None,
false,
)?;
let preimage = preimage.ok_or(Error::InvoiceCreationFailed)?;
let invoice = maybe_wrap(invoice);
Ok(JitChannelManualClaim { invoice, preimage })
}

/// Returns a payable invoice that can be used to request a variable amount payment (also known
/// as "zero-amount" invoice) and receive it via a newly created just-in-time (JIT) channel.
///
Expand All @@ -608,13 +640,14 @@ impl Bolt11Payment {
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_try_convert_enum(description)?;
let invoice = self.receive_via_jit_channel_inner(
let (invoice, _) = self.receive_via_jit_channel_inner(
None,
&description,
expiry_secs,
None,
max_proportional_lsp_fee_limit_ppm_msat,
None,
true,
)?;
Ok(maybe_wrap(invoice))
}
Expand Down Expand Up @@ -648,13 +681,14 @@ impl Bolt11Payment {
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>, payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_try_convert_enum(description)?;
let invoice = self.receive_via_jit_channel_inner(
let (invoice, _) = self.receive_via_jit_channel_inner(
None,
&description,
expiry_secs,
None,
max_proportional_lsp_fee_limit_ppm_msat,
Some(payment_hash),
true,
)?;
Ok(maybe_wrap(invoice))
}
Expand All @@ -663,7 +697,8 @@ impl Bolt11Payment {
&self, amount_msat: Option<u64>, description: &LdkBolt11InvoiceDescription,
expiry_secs: u32, max_total_lsp_fee_limit_msat: Option<u64>,
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>, payment_hash: Option<PaymentHash>,
) -> Result<LdkBolt11Invoice, Error> {
auto_claim: bool,
) -> Result<(LdkBolt11Invoice, Option<PaymentPreimage>), Error> {
let liquidity_source =
self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?;

Expand Down Expand Up @@ -721,9 +756,12 @@ impl Bolt11Payment {
let id = PaymentId(payment_hash.0);
let preimage =
self.channel_manager.get_payment_preimage(payment_hash, payment_secret.clone()).ok();

let stored_preimage = if auto_claim { preimage } else { None };

let kind = PaymentKind::Bolt11Jit {
hash: payment_hash,
preimage,
preimage: stored_preimage,
secret: Some(payment_secret.clone()),
counterparty_skimmed_fee_msat: None,
lsp_fee_limits,
Expand All @@ -741,7 +779,7 @@ impl Bolt11Payment {
// Persist LSP peer to make sure we reconnect on restart.
self.peer_store.add_peer(peer_info)?;

Ok(invoice)
Ok((invoice, preimage))
}

/// Sends payment probes over all paths of a route that would be used to pay the given invoice.
Expand Down
Loading
Loading