Skip to content

Commit bf348fa

Browse files
committed
Add corresponding _for_hash methods to receive via jit channel
Add corresponding `receive_via_jit_channel_for_hash()` and `receive_variable_amount_via_jit_channel_for_hash()` methods that accept a custom payment hash from the user. These methods allow implementing swap-in functionality with a JIT channel (i.e., swapping an on-chain payment to Lightning by opening a new channel).
1 parent a147ad0 commit bf348fa

File tree

5 files changed

+235
-21
lines changed

5 files changed

+235
-21
lines changed

bindings/ldk_node.udl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,11 @@ interface Bolt11Payment {
189189
[Throws=NodeError]
190190
Bolt11Invoice receive_via_jit_channel(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_lsp_fee_limit_msat);
191191
[Throws=NodeError]
192+
Bolt11Invoice receive_via_jit_channel_for_hash(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_lsp_fee_limit_msat, PaymentHash payment_hash);
193+
[Throws=NodeError]
192194
Bolt11Invoice receive_variable_amount_via_jit_channel([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat);
195+
[Throws=NodeError]
196+
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);
193197
};
194198

195199
interface Bolt12Payment {

src/event.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,8 @@ where
683683
// the payment has been registered via `_for_hash` variants and needs to be manually claimed via
684684
// user interaction.
685685
match info.kind {
686-
PaymentKind::Bolt11 { preimage, .. } => {
686+
PaymentKind::Bolt11 { preimage, .. }
687+
| PaymentKind::Bolt11Jit { preimage, .. } => {
687688
if purpose.preimage().is_none() {
688689
debug_assert!(
689690
preimage.is_none(),

src/liquidity.rs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -987,7 +987,7 @@ where
987987

988988
pub(crate) async fn lsps2_receive_to_jit_channel(
989989
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
990-
max_total_lsp_fee_limit_msat: Option<u64>,
990+
max_total_lsp_fee_limit_msat: Option<u64>, payment_hash: Option<PaymentHash>,
991991
) -> Result<(Bolt11Invoice, u64), Error> {
992992
let fee_response = self.lsps2_request_opening_fee_params().await?;
993993

@@ -1039,6 +1039,7 @@ where
10391039
Some(amount_msat),
10401040
description,
10411041
expiry_secs,
1042+
payment_hash,
10421043
)?;
10431044

10441045
log_info!(self.logger, "JIT-channel invoice created: {}", invoice);
@@ -1047,7 +1048,7 @@ where
10471048

10481049
pub(crate) async fn lsps2_receive_variable_amount_to_jit_channel(
10491050
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
1050-
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
1051+
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>, payment_hash: Option<PaymentHash>,
10511052
) -> Result<(Bolt11Invoice, u64), Error> {
10521053
let fee_response = self.lsps2_request_opening_fee_params().await?;
10531054

@@ -1081,8 +1082,13 @@ where
10811082
);
10821083

10831084
let buy_response = self.lsps2_send_buy_request(None, min_opening_params).await?;
1084-
let invoice =
1085-
self.lsps2_create_jit_invoice(buy_response, None, description, expiry_secs)?;
1085+
let invoice = self.lsps2_create_jit_invoice(
1086+
buy_response,
1087+
None,
1088+
description,
1089+
expiry_secs,
1090+
payment_hash,
1091+
)?;
10861092

10871093
log_info!(self.logger, "JIT-channel invoice created: {}", invoice);
10881094
Ok((invoice, min_prop_fee_ppm_msat))
@@ -1165,18 +1171,36 @@ where
11651171
fn lsps2_create_jit_invoice(
11661172
&self, buy_response: LSPS2BuyResponse, amount_msat: Option<u64>,
11671173
description: &Bolt11InvoiceDescription, expiry_secs: u32,
1174+
payment_hash: Option<PaymentHash>,
11681175
) -> Result<Bolt11Invoice, Error> {
11691176
let lsps2_client = self.lsps2_client.as_ref().ok_or(Error::LiquiditySourceUnavailable)?;
11701177

11711178
// LSPS2 requires min_final_cltv_expiry_delta to be at least 2 more than usual.
11721179
let min_final_cltv_expiry_delta = MIN_FINAL_CLTV_EXPIRY_DELTA + 2;
1173-
let (payment_hash, payment_secret) = self
1174-
.channel_manager
1175-
.create_inbound_payment(None, expiry_secs, Some(min_final_cltv_expiry_delta))
1176-
.map_err(|e| {
1177-
log_error!(self.logger, "Failed to register inbound payment: {:?}", e);
1178-
Error::InvoiceCreationFailed
1179-
})?;
1180+
let (payment_hash, payment_secret) = match payment_hash {
1181+
Some(payment_hash) => {
1182+
let payment_secret = self
1183+
.channel_manager
1184+
.create_inbound_payment_for_hash(
1185+
payment_hash,
1186+
None,
1187+
expiry_secs,
1188+
Some(min_final_cltv_expiry_delta),
1189+
)
1190+
.map_err(|e| {
1191+
log_error!(self.logger, "Failed to register inbound payment: {:?}", e);
1192+
Error::InvoiceCreationFailed
1193+
})?;
1194+
(payment_hash, payment_secret)
1195+
},
1196+
None => self
1197+
.channel_manager
1198+
.create_inbound_payment(None, expiry_secs, Some(min_final_cltv_expiry_delta))
1199+
.map_err(|e| {
1200+
log_error!(self.logger, "Failed to register inbound payment: {:?}", e);
1201+
Error::InvoiceCreationFailed
1202+
})?,
1203+
};
11801204

11811205
let route_hint = RouteHint(vec![RouteHintHop {
11821206
src_node_id: lsps2_client.lsp_node_id,

src/payment/bolt11.rs

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,15 @@ impl Bolt11Payment {
360360
}
361361

362362
if let Some(details) = self.payment_store.get(&payment_id) {
363+
let skimmed_fee_msat = match details.kind {
364+
PaymentKind::Bolt11Jit {
365+
counterparty_skimmed_fee_msat: Some(skimmed_fee_msat),
366+
..
367+
} => skimmed_fee_msat,
368+
_ => 0,
369+
};
363370
if let Some(expected_amount_msat) = details.amount_msat {
364-
if claimable_amount_msat < expected_amount_msat {
371+
if claimable_amount_msat < expected_amount_msat - skimmed_fee_msat {
365372
log_error!(
366373
self.logger,
367374
"Failed to manually claim payment {} as the claimable amount is less than expected",
@@ -578,6 +585,46 @@ impl Bolt11Payment {
578585
expiry_secs,
579586
max_total_lsp_fee_limit_msat,
580587
None,
588+
None,
589+
)?;
590+
Ok(maybe_wrap(invoice))
591+
}
592+
593+
/// Returns a payable invoice that can be used to request a payment of the amount given and
594+
/// receive it via a newly created just-in-time (JIT) channel.
595+
///
596+
/// When the returned invoice is paid, the configured [LSPS2]-compliant LSP will open a channel
597+
/// to us, supplying just-in-time inbound liquidity.
598+
///
599+
/// If set, `max_total_lsp_fee_limit_msat` will limit how much fee we allow the LSP to take for opening the
600+
/// channel to us. We'll use its cheapest offer otherwise.
601+
///
602+
/// We will register the given payment hash and emit a [`PaymentClaimable`] event once
603+
/// the inbound payment arrives. The check that [`counterparty_skimmed_fee_msat`] is within the limits
604+
/// is performed *before* emitting the event.
605+
///
606+
/// **Note:** users *MUST* handle this event and claim the payment manually via
607+
/// [`claim_for_hash`] as soon as they have obtained access to the preimage of the given
608+
/// payment hash. If they're unable to obtain the preimage, they *MUST* immediately fail the payment via
609+
/// [`fail_for_hash`].
610+
///
611+
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
612+
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
613+
/// [`claim_for_hash`]: Self::claim_for_hash
614+
/// [`fail_for_hash`]: Self::fail_for_hash
615+
/// [`counterparty_skimmed_fee_msat`]: crate::payment::PaymentKind::Bolt11Jit::counterparty_skimmed_fee_msat
616+
pub fn receive_via_jit_channel_for_hash(
617+
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
618+
max_total_lsp_fee_limit_msat: Option<u64>, payment_hash: PaymentHash,
619+
) -> Result<Bolt11Invoice, Error> {
620+
let description = maybe_try_convert_enum(description)?;
621+
let invoice = self.receive_via_jit_channel_inner(
622+
Some(amount_msat),
623+
&description,
624+
expiry_secs,
625+
max_total_lsp_fee_limit_msat,
626+
None,
627+
Some(payment_hash),
581628
)?;
582629
Ok(maybe_wrap(invoice))
583630
}
@@ -604,14 +651,55 @@ impl Bolt11Payment {
604651
expiry_secs,
605652
None,
606653
max_proportional_lsp_fee_limit_ppm_msat,
654+
None,
655+
)?;
656+
Ok(maybe_wrap(invoice))
657+
}
658+
659+
/// Returns a payable invoice that can be used to request a variable amount payment (also known
660+
/// as "zero-amount" invoice) and receive it via a newly created just-in-time (JIT) channel.
661+
///
662+
/// When the returned invoice is paid, the configured [LSPS2]-compliant LSP will open a channel
663+
/// to us, supplying just-in-time inbound liquidity.
664+
///
665+
/// If set, `max_proportional_lsp_fee_limit_ppm_msat` will limit how much proportional fee, in
666+
/// parts-per-million millisatoshis, we allow the LSP to take for opening the channel to us.
667+
/// We'll use its cheapest offer otherwise.
668+
///
669+
/// We will register the given payment hash and emit a [`PaymentClaimable`] event once
670+
/// the inbound payment arrives. The check that [`counterparty_skimmed_fee_msat`] is within the limits
671+
/// is performed *before* emitting the event.
672+
///
673+
/// **Note:** users *MUST* handle this event and claim the payment manually via
674+
/// [`claim_for_hash`] as soon as they have obtained access to the preimage of the given
675+
/// payment hash. If they're unable to obtain the preimage, they *MUST* immediately fail the payment via
676+
/// [`fail_for_hash`].
677+
///
678+
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
679+
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
680+
/// [`claim_for_hash`]: Self::claim_for_hash
681+
/// [`fail_for_hash`]: Self::fail_for_hash
682+
/// [`counterparty_skimmed_fee_msat`]: crate::payment::PaymentKind::Bolt11Jit::counterparty_skimmed_fee_msat
683+
pub fn receive_variable_amount_via_jit_channel_for_hash(
684+
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
685+
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>, payment_hash: PaymentHash,
686+
) -> Result<Bolt11Invoice, Error> {
687+
let description = maybe_try_convert_enum(description)?;
688+
let invoice = self.receive_via_jit_channel_inner(
689+
None,
690+
&description,
691+
expiry_secs,
692+
None,
693+
max_proportional_lsp_fee_limit_ppm_msat,
694+
Some(payment_hash),
607695
)?;
608696
Ok(maybe_wrap(invoice))
609697
}
610698

611699
fn receive_via_jit_channel_inner(
612700
&self, amount_msat: Option<u64>, description: &LdkBolt11InvoiceDescription,
613701
expiry_secs: u32, max_total_lsp_fee_limit_msat: Option<u64>,
614-
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
702+
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>, payment_hash: Option<PaymentHash>,
615703
) -> Result<LdkBolt11Invoice, Error> {
616704
let liquidity_source =
617705
self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?;
@@ -649,6 +737,7 @@ impl Bolt11Payment {
649737
description,
650738
expiry_secs,
651739
max_total_lsp_fee_limit_msat,
740+
payment_hash,
652741
)
653742
.await
654743
.map(|(invoice, total_fee)| (invoice, Some(total_fee), None))
@@ -658,6 +747,7 @@ impl Bolt11Payment {
658747
description,
659748
expiry_secs,
660749
max_proportional_lsp_fee_limit_ppm_msat,
750+
payment_hash,
661751
)
662752
.await
663753
.map(|(invoice, prop_fee)| (invoice, None, Some(prop_fee)))

tests/integration_tests_rust.rs

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ mod common;
99

1010
use common::{
1111
do_channel_full_cycle, expect_channel_pending_event, expect_channel_ready_event, expect_event,
12-
expect_payment_received_event, expect_payment_successful_event, generate_blocks_and_wait,
12+
expect_payment_claimable_event, expect_payment_received_event, expect_payment_successful_event,
13+
generate_blocks_and_wait,
1314
logging::{init_log_logger, validate_log_entry, TestLogWriter},
1415
open_channel, premine_and_distribute_funds, random_config, random_listening_addresses,
1516
setup_bitcoind_and_electrsd, setup_builder, setup_node, setup_two_nodes, wait_for_tx,
@@ -29,7 +30,7 @@ use lightning::routing::gossip::{NodeAlias, NodeId};
2930
use lightning::util::persist::KVStore;
3031

3132
use lightning_invoice::{Bolt11InvoiceDescription, Description};
32-
use lightning_types::payment::PaymentPreimage;
33+
use lightning_types::payment::{PaymentHash, PaymentPreimage};
3334

3435
use bitcoin::address::NetworkUnchecked;
3536
use bitcoin::hashes::sha256::Hash as Sha256Hash;
@@ -1334,6 +1335,7 @@ fn lsps2_client_service_integration() {
13341335
let payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).unwrap();
13351336
expect_channel_pending_event!(service_node, client_node.node_id());
13361337
expect_channel_ready_event!(service_node, client_node.node_id());
1338+
expect_event!(service_node, PaymentForwarded);
13371339
expect_channel_pending_event!(client_node, service_node.node_id());
13381340
expect_channel_ready_event!(client_node, service_node.node_id());
13391341

@@ -1359,19 +1361,112 @@ fn lsps2_client_service_integration() {
13591361

13601362
println!("Generating regular invoice!");
13611363
let invoice_description =
1362-
Bolt11InvoiceDescription::Direct(Description::new(String::from("asdf")).unwrap());
1364+
Bolt11InvoiceDescription::Direct(Description::new(String::from("asdf")).unwrap()).into();
13631365
let amount_msat = 5_000_000;
1364-
let invoice = client_node
1365-
.bolt11_payment()
1366-
.receive(amount_msat, &invoice_description.into(), 1024)
1367-
.unwrap();
1366+
let invoice =
1367+
client_node.bolt11_payment().receive(amount_msat, &invoice_description, 1024).unwrap();
13681368

13691369
// Have the payer_node pay the invoice, to check regular forwards service_node -> client_node
13701370
// are working as expected.
13711371
println!("Paying regular invoice!");
13721372
let payment_id = payer_node.bolt11_payment().send(&invoice, None).unwrap();
13731373
expect_payment_successful_event!(payer_node, Some(payment_id), None);
1374+
expect_event!(service_node, PaymentForwarded);
13741375
expect_payment_received_event!(client_node, amount_msat);
1376+
1377+
////////////////////////////////////////////////////////////////////////////
1378+
// receive_via_jit_channel_for_hash and claim_for_hash
1379+
////////////////////////////////////////////////////////////////////////////
1380+
println!("Generating JIT invoice!");
1381+
// Increase the amount to make sure it does not fit into the existing channels.
1382+
let jit_amount_msat = 200_000_000;
1383+
let manual_preimage = PaymentPreimage([42u8; 32]);
1384+
let manual_payment_hash: PaymentHash = manual_preimage.into();
1385+
let jit_invoice = client_node
1386+
.bolt11_payment()
1387+
.receive_via_jit_channel_for_hash(
1388+
jit_amount_msat,
1389+
&invoice_description,
1390+
1024,
1391+
None,
1392+
manual_payment_hash,
1393+
)
1394+
.unwrap();
1395+
1396+
// Have the payer_node pay the invoice, therby triggering channel open service_node -> client_node.
1397+
println!("Paying JIT invoice!");
1398+
let payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).unwrap();
1399+
expect_channel_pending_event!(service_node, client_node.node_id());
1400+
expect_channel_ready_event!(service_node, client_node.node_id());
1401+
expect_channel_pending_event!(client_node, service_node.node_id());
1402+
expect_channel_ready_event!(client_node, service_node.node_id());
1403+
1404+
let service_fee_msat = (jit_amount_msat * channel_opening_fee_ppm as u64) / 1_000_000;
1405+
let expected_received_amount_msat = jit_amount_msat - service_fee_msat;
1406+
let claimable_amount_msat = expect_payment_claimable_event!(
1407+
client_node,
1408+
payment_id,
1409+
manual_payment_hash,
1410+
expected_received_amount_msat
1411+
);
1412+
println!("Claiming payment!");
1413+
client_node
1414+
.bolt11_payment()
1415+
.claim_for_hash(manual_payment_hash, claimable_amount_msat, manual_preimage)
1416+
.unwrap();
1417+
1418+
expect_event!(service_node, PaymentForwarded);
1419+
expect_payment_successful_event!(payer_node, Some(payment_id), None);
1420+
let client_payment_id =
1421+
expect_payment_received_event!(client_node, expected_received_amount_msat).unwrap();
1422+
let client_payment = client_node.payment(&client_payment_id).unwrap();
1423+
match client_payment.kind {
1424+
PaymentKind::Bolt11Jit { counterparty_skimmed_fee_msat, .. } => {
1425+
assert_eq!(counterparty_skimmed_fee_msat, Some(service_fee_msat));
1426+
},
1427+
_ => panic!("Unexpected payment kind"),
1428+
}
1429+
1430+
////////////////////////////////////////////////////////////////////////////
1431+
// receive_via_jit_channel_for_hash and fail_for_hash
1432+
////////////////////////////////////////////////////////////////////////////
1433+
println!("Generating JIT invoice!");
1434+
// Increase the amount to make sure it does not fit into the existing channels.
1435+
let jit_amount_msat = 400_000_000;
1436+
let manual_preimage = PaymentPreimage([43u8; 32]);
1437+
let manual_payment_hash: PaymentHash = manual_preimage.into();
1438+
let jit_invoice = client_node
1439+
.bolt11_payment()
1440+
.receive_via_jit_channel_for_hash(
1441+
jit_amount_msat,
1442+
&invoice_description,
1443+
1024,
1444+
None,
1445+
manual_payment_hash,
1446+
)
1447+
.unwrap();
1448+
1449+
// Have the payer_node pay the invoice, therby triggering channel open service_node -> client_node.
1450+
println!("Paying JIT invoice!");
1451+
let payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).unwrap();
1452+
expect_channel_pending_event!(service_node, client_node.node_id());
1453+
expect_channel_ready_event!(service_node, client_node.node_id());
1454+
expect_channel_pending_event!(client_node, service_node.node_id());
1455+
expect_channel_ready_event!(client_node, service_node.node_id());
1456+
1457+
let service_fee_msat = (jit_amount_msat * channel_opening_fee_ppm as u64) / 1_000_000;
1458+
let expected_received_amount_msat = jit_amount_msat - service_fee_msat;
1459+
expect_payment_claimable_event!(
1460+
client_node,
1461+
payment_id,
1462+
manual_payment_hash,
1463+
expected_received_amount_msat
1464+
);
1465+
println!("Failing payment!");
1466+
client_node.bolt11_payment().fail_for_hash(manual_payment_hash).unwrap();
1467+
1468+
expect_event!(payer_node, PaymentFailed);
1469+
assert_eq!(client_node.payment(&payment_id).unwrap().status, PaymentStatus::Failed);
13751470
}
13761471

13771472
#[test]

0 commit comments

Comments
 (0)