Skip to content

Commit 018a5b6

Browse files
committed
Allow to generate invoices for custom payment hashes
... requiring users to manucally claim them.
1 parent 07d020c commit 018a5b6

File tree

2 files changed

+212
-26
lines changed

2 files changed

+212
-26
lines changed

bindings/ldk_node.udl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,18 @@ interface Bolt11Payment {
100100
[Throws=NodeError]
101101
void send_probes_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat);
102102
[Throws=NodeError]
103+
void claim_for_hash(PaymentHash payment_hash, u64 claimable_amount_msat, PaymentPreimage preimage);
104+
[Throws=NodeError]
105+
void fail_for_hash(PaymentHash payment_hash);
106+
[Throws=NodeError]
103107
Bolt11Invoice receive(u64 amount_msat, [ByRef]string description, u32 expiry_secs);
104108
[Throws=NodeError]
109+
Bolt11Invoice receive_for_hash(u64 amount_msat, [ByRef]string description, u32 expiry_secs, PaymentHash payment_hash);
110+
[Throws=NodeError]
105111
Bolt11Invoice receive_variable_amount([ByRef]string description, u32 expiry_secs);
106112
[Throws=NodeError]
113+
Bolt11Invoice receive_variable_amount_for_hash([ByRef]string description, u32 expiry_secs, PaymentHash payment_hash);
114+
[Throws=NodeError]
107115
Bolt11Invoice receive_via_jit_channel(u64 amount_msat, [ByRef]string description, u32 expiry_secs, u64? max_lsp_fee_limit_msat);
108116
[Throws=NodeError]
109117
Bolt11Invoice receive_variable_amount_via_jit_channel([ByRef]string description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat);

src/payment/bolt11.rs

Lines changed: 204 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,23 @@ use crate::error::Error;
88
use crate::liquidity::LiquiditySource;
99
use crate::logger::{log_error, log_info, FilesystemLogger, Logger};
1010
use crate::payment::store::{
11-
LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, PaymentStore,
11+
LSPFeeLimits, PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind,
12+
PaymentStatus, PaymentStore,
1213
};
1314
use crate::peer_store::{PeerInfo, PeerStore};
1415
use crate::types::{ChannelManager, KeysManager};
1516

1617
use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry, RetryableSendFailure};
17-
use lightning::ln::PaymentHash;
18+
use lightning::ln::{PaymentHash, PaymentPreimage};
1819
use lightning::routing::router::{PaymentParameters, RouteParameters};
1920

2021
use lightning_invoice::{payment, Bolt11Invoice, Currency};
2122

23+
use bitcoin::hashes::sha256::Hash as Sha256;
2224
use bitcoin::hashes::Hash;
2325

2426
use std::sync::{Arc, RwLock};
27+
use std::time::SystemTime;
2528

2629
/// A payment handler allowing to create and pay [BOLT 11] invoices.
2730
///
@@ -254,55 +257,227 @@ impl Bolt11Payment {
254257
}
255258
}
256259

260+
/// Allows to attempt manually claiming payments with the given preimage that have previously
261+
/// been registered via [`receive_for_hash`] or [`receive_variable_amount_for_hash`].
262+
///
263+
/// This should be called in reponse to a [`PaymentClaimable`] event as soon as the preimage is
264+
/// available.
265+
///
266+
/// Will check that the payment is known, and that the given preimage and claimable amount
267+
/// match our expectations before attempting to claim the payment, and will return an error
268+
/// otherwise.
269+
///
270+
/// When claiming the payment has succeeded, a [`PaymentReceived`] event will be emitted.
271+
///
272+
/// [`receive_for_hash`]: Self::receive_for_hash
273+
/// [`receive_variable_amount_for_hash`]: Self::receive_variable_amount_for_hash
274+
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
275+
/// [`PaymentReceived`]: crate::Event::PaymentReceived
276+
pub fn claim_for_hash(
277+
&self, payment_hash: PaymentHash, claimable_amount_msat: u64, preimage: PaymentPreimage,
278+
) -> Result<(), Error> {
279+
let payment_id = PaymentId(payment_hash.0);
280+
281+
let expected_payment_hash = PaymentHash(Sha256::hash(&preimage.0).to_byte_array());
282+
283+
if expected_payment_hash != payment_hash {
284+
log_error!(
285+
self.logger,
286+
"Failed to manually claim payment as the given preimage doesn't match the hash {}",
287+
payment_hash
288+
);
289+
return Err(Error::InvalidPaymentPreimage);
290+
}
291+
292+
if let Some(details) = self.payment_store.get(&payment_id) {
293+
if let Some(expected_amount_msat) = details.amount_msat {
294+
if claimable_amount_msat < expected_amount_msat {
295+
log_error!(
296+
self.logger,
297+
"Failed to manually claim payment {} as the claimable amount is less than expected",
298+
payment_id
299+
);
300+
return Err(Error::InvalidAmount);
301+
}
302+
}
303+
} else {
304+
log_error!(
305+
self.logger,
306+
"Failed to manually claim unknown payment with hash: {}",
307+
payment_hash
308+
);
309+
return Err(Error::InvalidPaymentHash);
310+
}
311+
312+
self.channel_manager.claim_funds(preimage);
313+
Ok(())
314+
}
315+
316+
/// Allows to manually fail payments with the given hash that have previously
317+
/// been registered via [`receive_for_hash`] or [`receive_variable_amount_for_hash`].
318+
///
319+
/// This should be called in reponse to a [`PaymentClaimable`] event if the payment needs to be
320+
/// failed back, e.g., if the correct preimage can't be retrieved in time before the claim
321+
/// deadline has been reached.
322+
///
323+
/// Will check that the payment is known before failing the payment, and will return an error
324+
/// otherwise.
325+
///
326+
/// [`receive_for_hash`]: Self::receive_for_hash
327+
/// [`receive_variable_amount_for_hash`]: Self::receive_variable_amount_for_hash
328+
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
329+
pub fn fail_for_hash(&self, payment_hash: PaymentHash) -> Result<(), Error> {
330+
let payment_id = PaymentId(payment_hash.0);
331+
332+
let update = PaymentDetailsUpdate {
333+
status: Some(PaymentStatus::Failed),
334+
..PaymentDetailsUpdate::new(payment_id)
335+
};
336+
337+
if !self.payment_store.update(&update)? {
338+
log_error!(
339+
self.logger,
340+
"Failed to manually fail unknown payment with hash: {}",
341+
payment_hash
342+
);
343+
return Err(Error::InvalidPaymentHash);
344+
}
345+
346+
self.channel_manager.fail_htlc_backwards(&payment_hash);
347+
Ok(())
348+
}
349+
257350
/// Returns a payable invoice that can be used to request and receive a payment of the amount
258351
/// given.
352+
///
353+
/// The inbound payment will be automatically claimed upon arrival.
259354
pub fn receive(
260355
&self, amount_msat: u64, description: &str, expiry_secs: u32,
261356
) -> Result<Bolt11Invoice, Error> {
262-
self.receive_inner(Some(amount_msat), description, expiry_secs)
357+
self.receive_inner(Some(amount_msat), description, expiry_secs, None)
358+
}
359+
360+
/// Returns a payable invoice that can be used to request a payment of the amount
361+
/// given for the given payment hash.
362+
///
363+
/// We will register the given payment hash and emit a [`PaymentClaimable`] event once
364+
/// the inbound payment arrives.
365+
///
366+
/// **Note:** users *MUST* handle this event and claim the payment manually via
367+
/// [`claim_for_hash`] as soon as they have obtained access to the preimage of the given
368+
/// payment hash. If they're unable to obtain the preimage, they *MUST* immediately fail the payment via
369+
/// [`fail_for_hash`].
370+
///
371+
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
372+
/// [`claim_for_hash`]: Self::claim_for_hash
373+
/// [`fail_for_hash`]: Self::fail_for_hash
374+
pub fn receive_for_hash(
375+
&self, amount_msat: u64, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
376+
) -> Result<Bolt11Invoice, Error> {
377+
self.receive_inner(Some(amount_msat), description, expiry_secs, Some(payment_hash))
263378
}
264379

265380
/// Returns a payable invoice that can be used to request and receive a payment for which the
266381
/// amount is to be determined by the user, also known as a "zero-amount" invoice.
382+
///
383+
/// The inbound payment will be automatically claimed upon arrival.
267384
pub fn receive_variable_amount(
268385
&self, description: &str, expiry_secs: u32,
269386
) -> Result<Bolt11Invoice, Error> {
270-
self.receive_inner(None, description, expiry_secs)
387+
self.receive_inner(None, description, expiry_secs, None)
388+
}
389+
390+
/// Returns a payable invoice that can be used to request a payment for the given payment hash
391+
/// and the amount to be determined by the user, also known as a "zero-amount" invoice.
392+
///
393+
/// We will register the given payment hash and emit a [`PaymentClaimable`] event once
394+
/// the inbound payment arrives.
395+
///
396+
/// **Note:** users *MUST* handle this event and claim the payment manually via
397+
/// [`claim_for_hash`] as soon as they have obtained access to the preimage of the given
398+
/// payment hash. If they're unable to obtain the preimage, they *MUST* immediately fail the payment via
399+
/// [`fail_for_hash`].
400+
///
401+
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
402+
/// [`claim_for_hash`]: Self::claim_for_hash
403+
/// [`fail_for_hash`]: Self::fail_for_hash
404+
pub fn receive_variable_amount_for_hash(
405+
&self, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
406+
) -> Result<Bolt11Invoice, Error> {
407+
self.receive_inner(None, description, expiry_secs, Some(payment_hash))
271408
}
272409

273410
fn receive_inner(
274411
&self, amount_msat: Option<u64>, description: &str, expiry_secs: u32,
412+
manual_claim_payment_hash: Option<PaymentHash>,
275413
) -> Result<Bolt11Invoice, Error> {
276414
let currency = Currency::from(self.config.network);
277415
let keys_manager = Arc::clone(&self.keys_manager);
278-
let invoice = match lightning_invoice::utils::create_invoice_from_channelmanager(
279-
&self.channel_manager,
280-
keys_manager,
281-
Arc::clone(&self.logger),
282-
currency,
283-
amount_msat,
284-
description.to_string(),
285-
expiry_secs,
286-
None,
287-
) {
288-
Ok(inv) => {
289-
log_info!(self.logger, "Invoice created: {}", inv);
290-
inv
291-
},
292-
Err(e) => {
293-
log_error!(self.logger, "Failed to create invoice: {}", e);
294-
return Err(Error::InvoiceCreationFailed);
295-
},
416+
let duration = SystemTime::now()
417+
.duration_since(SystemTime::UNIX_EPOCH)
418+
.expect("for the foreseeable future this shouldn't happen");
419+
420+
let invoice = {
421+
let invoice_res = if let Some(payment_hash) = manual_claim_payment_hash {
422+
lightning_invoice::utils::create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash(
423+
&self.channel_manager,
424+
keys_manager,
425+
Arc::clone(&self.logger),
426+
currency,
427+
amount_msat,
428+
description.to_string(),
429+
duration,
430+
expiry_secs,
431+
payment_hash,
432+
None,
433+
)
434+
} else {
435+
lightning_invoice::utils::create_invoice_from_channelmanager_and_duration_since_epoch(
436+
&self.channel_manager,
437+
keys_manager,
438+
Arc::clone(&self.logger),
439+
currency,
440+
amount_msat,
441+
description.to_string(),
442+
duration,
443+
expiry_secs,
444+
None,
445+
)
446+
};
447+
448+
match invoice_res {
449+
Ok(inv) => {
450+
log_info!(self.logger, "Invoice created: {}", inv);
451+
inv
452+
},
453+
Err(e) => {
454+
log_error!(self.logger, "Failed to create invoice: {}", e);
455+
return Err(Error::InvoiceCreationFailed);
456+
},
457+
}
296458
};
297459

298460
let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array());
461+
let payment_secret = invoice.payment_secret();
299462
let id = PaymentId(payment_hash.0);
463+
let preimage = if manual_claim_payment_hash.is_none() {
464+
// If the user hasn't registered a custom payment hash, we're positive ChannelManager
465+
// will know the preimage at this point.
466+
let res = self
467+
.channel_manager
468+
.get_payment_preimage(payment_hash, payment_secret.clone())
469+
.ok();
470+
debug_assert!(res.is_some(), "We just let ChannelManager create an inbound payment, it can't have forgotten the preimage by now.");
471+
res
472+
} else {
473+
None
474+
};
300475
let payment = PaymentDetails {
301476
id,
302477
kind: PaymentKind::Bolt11 {
303478
hash: payment_hash,
304-
preimage: None,
305-
secret: Some(invoice.payment_secret().clone()),
479+
preimage,
480+
secret: Some(payment_secret.clone()),
306481
},
307482

308483
amount_msat,
@@ -422,17 +597,20 @@ impl Bolt11Payment {
422597

423598
// Register payment in payment store.
424599
let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array());
600+
let payment_secret = invoice.payment_secret();
425601
let lsp_fee_limits = LSPFeeLimits {
426602
max_total_opening_fee_msat: lsp_total_opening_fee,
427603
max_proportional_opening_fee_ppm_msat: lsp_prop_opening_fee,
428604
};
429605
let id = PaymentId(payment_hash.0);
606+
let preimage =
607+
self.channel_manager.get_payment_preimage(payment_hash, payment_secret.clone()).ok();
430608
let payment = PaymentDetails {
431609
id,
432610
kind: PaymentKind::Bolt11Jit {
433611
hash: payment_hash,
434-
preimage: None,
435-
secret: Some(invoice.payment_secret().clone()),
612+
preimage,
613+
secret: Some(payment_secret.clone()),
436614
lsp_fee_limits,
437615
},
438616
amount_msat,

0 commit comments

Comments
 (0)