Skip to content

Commit f2afdad

Browse files
committed
WIP: Add receive_via_jit_channel_for_hash() method
This method allows to implement swap-in functionality with a JIT channel (i.e., swapping an on-chain payment to Lightning by opening a new channel).
1 parent 0a2bccd commit f2afdad

File tree

5 files changed

+191
-20
lines changed

5 files changed

+191
-20
lines changed

bindings/ldk_node.udl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ interface Bolt11Payment {
190190
Bolt11Invoice receive_via_jit_channel(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_lsp_fee_limit_msat);
191191
[Throws=NodeError]
192192
Bolt11Invoice receive_variable_amount_via_jit_channel([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat);
193+
[Throws=NodeError]
194+
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);
193195
};
194196

195197
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: 50 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,44 @@ 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.
604+
///
605+
/// **Note:** users *MUST* handle this event and claim the payment manually via
606+
/// [`claim_for_hash`] as soon as they have obtained access to the preimage of the given
607+
/// payment hash. If they're unable to obtain the preimage, they *MUST* immediately fail the payment via
608+
/// [`fail_for_hash`].
609+
///
610+
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
611+
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
612+
/// [`claim_for_hash`]: Self::claim_for_hash
613+
/// [`fail_for_hash`]: Self::fail_for_hash
614+
pub fn receive_via_jit_channel_for_hash(
615+
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
616+
max_total_lsp_fee_limit_msat: Option<u64>, payment_hash: PaymentHash,
617+
) -> Result<Bolt11Invoice, Error> {
618+
let description = maybe_try_convert_enum(description)?;
619+
let invoice = self.receive_via_jit_channel_inner(
620+
Some(amount_msat),
621+
&description,
622+
expiry_secs,
623+
max_total_lsp_fee_limit_msat,
624+
None,
625+
Some(payment_hash),
581626
)?;
582627
Ok(maybe_wrap(invoice))
583628
}
@@ -604,14 +649,15 @@ impl Bolt11Payment {
604649
expiry_secs,
605650
None,
606651
max_proportional_lsp_fee_limit_ppm_msat,
652+
None,
607653
)?;
608654
Ok(maybe_wrap(invoice))
609655
}
610656

611657
fn receive_via_jit_channel_inner(
612658
&self, amount_msat: Option<u64>, description: &LdkBolt11InvoiceDescription,
613659
expiry_secs: u32, max_total_lsp_fee_limit_msat: Option<u64>,
614-
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
660+
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>, payment_hash: Option<PaymentHash>,
615661
) -> Result<LdkBolt11Invoice, Error> {
616662
let liquidity_source =
617663
self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?;
@@ -649,6 +695,7 @@ impl Bolt11Payment {
649695
description,
650696
expiry_secs,
651697
max_total_lsp_fee_limit_msat,
698+
payment_hash,
652699
)
653700
.await
654701
.map(|(invoice, total_fee)| (invoice, Some(total_fee), None))
@@ -658,6 +705,7 @@ impl Bolt11Payment {
658705
description,
659706
expiry_secs,
660707
max_proportional_lsp_fee_limit_ppm_msat,
708+
payment_hash,
661709
)
662710
.await
663711
.map(|(invoice, prop_fee)| (invoice, None, Some(prop_fee)))

tests/integration_tests_rust.rs

Lines changed: 102 additions & 6 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,6 +30,7 @@ use lightning::routing::gossip::{NodeAlias, NodeId};
2930
use lightning::util::persist::KVStore;
3031

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

3335
use bitcoin::address::NetworkUnchecked;
3436
use bitcoin::hashes::Hash;
@@ -1332,6 +1334,7 @@ fn lsps2_client_service_integration() {
13321334
let payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).unwrap();
13331335
expect_channel_pending_event!(service_node, client_node.node_id());
13341336
expect_channel_ready_event!(service_node, client_node.node_id());
1337+
expect_event!(service_node, PaymentForwarded);
13351338
expect_channel_pending_event!(client_node, service_node.node_id());
13361339
expect_channel_ready_event!(client_node, service_node.node_id());
13371340

@@ -1357,19 +1360,112 @@ fn lsps2_client_service_integration() {
13571360

13581361
println!("Generating regular invoice!");
13591362
let invoice_description =
1360-
Bolt11InvoiceDescription::Direct(Description::new(String::from("asdf")).unwrap());
1363+
Bolt11InvoiceDescription::Direct(Description::new(String::from("asdf")).unwrap()).into();
13611364
let amount_msat = 5_000_000;
1362-
let invoice = client_node
1363-
.bolt11_payment()
1364-
.receive(amount_msat, &invoice_description.into(), 1024)
1365-
.unwrap();
1365+
let invoice =
1366+
client_node.bolt11_payment().receive(amount_msat, &invoice_description, 1024).unwrap();
13661367

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

13751471
#[test]

0 commit comments

Comments
 (0)