Skip to content

Commit 217b398

Browse files
authored
Merge pull request #608 from andrei-21/feature/for-hash
Add `receive_via_jit_channel_for_hash()` method
2 parents 110ab06 + 12bd254 commit 217b398

File tree

5 files changed

+238
-22
lines changed

5 files changed

+238
-22
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
@@ -685,7 +685,8 @@ where
685685
// the payment has been registered via `_for_hash` variants and needs to be manually claimed via
686686
// user interaction.
687687
match info.kind {
688-
PaymentKind::Bolt11 { preimage, .. } => {
688+
PaymentKind::Bolt11 { preimage, .. }
689+
| PaymentKind::Bolt11Jit { preimage, .. } => {
689690
if purpose.preimage().is_none() {
690691
debug_assert!(
691692
preimage.is_none(),

src/liquidity.rs

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

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

@@ -1040,6 +1040,7 @@ where
10401040
Some(amount_msat),
10411041
description,
10421042
expiry_secs,
1043+
payment_hash,
10431044
)?;
10441045

10451046
log_info!(self.logger, "JIT-channel invoice created: {}", invoice);
@@ -1048,7 +1049,7 @@ where
10481049

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

@@ -1082,8 +1083,13 @@ where
10821083
);
10831084

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

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

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

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

src/payment/bolt11.rs

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,8 +362,17 @@ impl Bolt11Payment {
362362
}
363363

364364
if let Some(details) = self.payment_store.get(&payment_id) {
365-
if let Some(expected_amount_msat) = details.amount_msat {
366-
if claimable_amount_msat < expected_amount_msat {
365+
// For payments requested via `receive*_via_jit_channel_for_hash()`
366+
// `skimmed_fee_msat` held by LSP must be taken into account.
367+
let skimmed_fee_msat = match details.kind {
368+
PaymentKind::Bolt11Jit {
369+
counterparty_skimmed_fee_msat: Some(skimmed_fee_msat),
370+
..
371+
} => skimmed_fee_msat,
372+
_ => 0,
373+
};
374+
if let Some(invoice_amount_msat) = details.amount_msat {
375+
if claimable_amount_msat < invoice_amount_msat - skimmed_fee_msat {
367376
log_error!(
368377
self.logger,
369378
"Failed to manually claim payment {} as the claimable amount is less than expected",
@@ -580,6 +589,46 @@ impl Bolt11Payment {
580589
expiry_secs,
581590
max_total_lsp_fee_limit_msat,
582591
None,
592+
None,
593+
)?;
594+
Ok(maybe_wrap(invoice))
595+
}
596+
597+
/// Returns a payable invoice that can be used to request a payment of the amount given and
598+
/// receive it via a newly created just-in-time (JIT) channel.
599+
///
600+
/// When the returned invoice is paid, the configured [LSPS2]-compliant LSP will open a channel
601+
/// to us, supplying just-in-time inbound liquidity.
602+
///
603+
/// If set, `max_total_lsp_fee_limit_msat` will limit how much fee we allow the LSP to take for opening the
604+
/// channel to us. We'll use its cheapest offer otherwise.
605+
///
606+
/// We will register the given payment hash and emit a [`PaymentClaimable`] event once
607+
/// the inbound payment arrives. The check that [`counterparty_skimmed_fee_msat`] is within the limits
608+
/// is performed *before* emitting the event.
609+
///
610+
/// **Note:** users *MUST* handle this event and claim the payment manually via
611+
/// [`claim_for_hash`] as soon as they have obtained access to the preimage of the given
612+
/// payment hash. If they're unable to obtain the preimage, they *MUST* immediately fail the payment via
613+
/// [`fail_for_hash`].
614+
///
615+
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
616+
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
617+
/// [`claim_for_hash`]: Self::claim_for_hash
618+
/// [`fail_for_hash`]: Self::fail_for_hash
619+
/// [`counterparty_skimmed_fee_msat`]: crate::payment::PaymentKind::Bolt11Jit::counterparty_skimmed_fee_msat
620+
pub fn receive_via_jit_channel_for_hash(
621+
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
622+
max_total_lsp_fee_limit_msat: Option<u64>, payment_hash: PaymentHash,
623+
) -> Result<Bolt11Invoice, Error> {
624+
let description = maybe_try_convert_enum(description)?;
625+
let invoice = self.receive_via_jit_channel_inner(
626+
Some(amount_msat),
627+
&description,
628+
expiry_secs,
629+
max_total_lsp_fee_limit_msat,
630+
None,
631+
Some(payment_hash),
583632
)?;
584633
Ok(maybe_wrap(invoice))
585634
}
@@ -606,14 +655,55 @@ impl Bolt11Payment {
606655
expiry_secs,
607656
None,
608657
max_proportional_lsp_fee_limit_ppm_msat,
658+
None,
659+
)?;
660+
Ok(maybe_wrap(invoice))
661+
}
662+
663+
/// Returns a payable invoice that can be used to request a variable amount payment (also known
664+
/// as "zero-amount" invoice) and receive it via a newly created just-in-time (JIT) channel.
665+
///
666+
/// When the returned invoice is paid, the configured [LSPS2]-compliant LSP will open a channel
667+
/// to us, supplying just-in-time inbound liquidity.
668+
///
669+
/// If set, `max_proportional_lsp_fee_limit_ppm_msat` will limit how much proportional fee, in
670+
/// parts-per-million millisatoshis, we allow the LSP to take for opening the channel to us.
671+
/// We'll use its cheapest offer otherwise.
672+
///
673+
/// We will register the given payment hash and emit a [`PaymentClaimable`] event once
674+
/// the inbound payment arrives. The check that [`counterparty_skimmed_fee_msat`] is within the limits
675+
/// is performed *before* emitting the event.
676+
///
677+
/// **Note:** users *MUST* handle this event and claim the payment manually via
678+
/// [`claim_for_hash`] as soon as they have obtained access to the preimage of the given
679+
/// payment hash. If they're unable to obtain the preimage, they *MUST* immediately fail the payment via
680+
/// [`fail_for_hash`].
681+
///
682+
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
683+
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
684+
/// [`claim_for_hash`]: Self::claim_for_hash
685+
/// [`fail_for_hash`]: Self::fail_for_hash
686+
/// [`counterparty_skimmed_fee_msat`]: crate::payment::PaymentKind::Bolt11Jit::counterparty_skimmed_fee_msat
687+
pub fn receive_variable_amount_via_jit_channel_for_hash(
688+
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
689+
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>, payment_hash: PaymentHash,
690+
) -> Result<Bolt11Invoice, Error> {
691+
let description = maybe_try_convert_enum(description)?;
692+
let invoice = self.receive_via_jit_channel_inner(
693+
None,
694+
&description,
695+
expiry_secs,
696+
None,
697+
max_proportional_lsp_fee_limit_ppm_msat,
698+
Some(payment_hash),
609699
)?;
610700
Ok(maybe_wrap(invoice))
611701
}
612702

613703
fn receive_via_jit_channel_inner(
614704
&self, amount_msat: Option<u64>, description: &LdkBolt11InvoiceDescription,
615705
expiry_secs: u32, max_total_lsp_fee_limit_msat: Option<u64>,
616-
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
706+
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>, payment_hash: Option<PaymentHash>,
617707
) -> Result<LdkBolt11Invoice, Error> {
618708
let liquidity_source =
619709
self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?;
@@ -645,6 +735,7 @@ impl Bolt11Payment {
645735
description,
646736
expiry_secs,
647737
max_total_lsp_fee_limit_msat,
738+
payment_hash,
648739
)
649740
.await
650741
.map(|(invoice, total_fee)| (invoice, Some(total_fee), None))
@@ -654,6 +745,7 @@ impl Bolt11Payment {
654745
description,
655746
expiry_secs,
656747
max_proportional_lsp_fee_limit_ppm_msat,
748+
payment_hash,
657749
)
658750
.await
659751
.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)