@@ -842,6 +842,13 @@ impl HTLCSource {
842
842
_ => None,
843
843
}
844
844
}
845
+
846
+ pub(crate) fn hold_htlc_at_next_hop(&self) -> bool {
847
+ match self {
848
+ Self::OutboundRoute { hold_htlc, .. } => hold_htlc.is_some(),
849
+ _ => false,
850
+ }
851
+ }
845
852
}
846
853
847
854
/// This enum is used to specify which error data to send to peers when failing back an HTLC
@@ -4930,6 +4937,7 @@ where
4930
4937
invoice_request: None,
4931
4938
bolt12_invoice: None,
4932
4939
session_priv_bytes,
4940
+ hold_htlc_at_next_hop: false,
4933
4941
})
4934
4942
}
4935
4943
@@ -4945,6 +4953,7 @@ where
4945
4953
invoice_request,
4946
4954
bolt12_invoice,
4947
4955
session_priv_bytes,
4956
+ hold_htlc_at_next_hop,
4948
4957
} = args;
4949
4958
// The top-level caller should hold the total_consistency_lock read lock.
4950
4959
debug_assert!(self.total_consistency_lock.try_write().is_err());
@@ -5026,7 +5035,7 @@ where
5026
5035
first_hop_htlc_msat: htlc_msat,
5027
5036
payment_id,
5028
5037
bolt12_invoice: bolt12_invoice.cloned(),
5029
- hold_htlc: None ,
5038
+ hold_htlc: hold_htlc_at_next_hop.then(|| ()) ,
5030
5039
};
5031
5040
let send_res = chan.send_htlc_and_commit(
5032
5041
htlc_msat,
@@ -5412,19 +5421,35 @@ where
5412
5421
},
5413
5422
};
5414
5423
5415
- let enqueue_held_htlc_available_res = self.flow.enqueue_held_htlc_available(
5416
- invoice,
5417
- payment_id,
5418
- self.get_peers_for_blinded_path(),
5419
- );
5420
- if enqueue_held_htlc_available_res.is_err() {
5421
- self.abandon_payment_with_reason(
5424
+ // If the call to `Self::hold_htlc_channels` succeeded, then we are a private node and can
5425
+ // hold the HTLCs for this payment at our next-hop channel counterparty until the recipient
5426
+ // comes online. This allows us to go offline after locking in the HTLCs.
5427
+ if let Ok(channels) = hold_htlc_channels_res {
5428
+ if let Err(e) =
5429
+ self.send_payment_for_static_invoice_no_persist(payment_id, channels)
5430
+ {
5431
+ log_trace!(
5432
+ self.logger,
5433
+ "Failed to send held HTLC with payment id {}: {:?}",
5434
+ payment_id,
5435
+ e
5436
+ );
5437
+ }
5438
+ } else {
5439
+ let enqueue_held_htlc_available_res = self.flow.enqueue_held_htlc_available(
5440
+ invoice,
5422
5441
payment_id,
5423
- PaymentFailureReason::BlindedPathCreationFailed ,
5442
+ self.get_peers_for_blinded_path() ,
5424
5443
);
5425
- res = Err(Bolt12PaymentError::BlindedPathCreationFailed);
5426
- return NotifyOption::DoPersist;
5427
- };
5444
+ if enqueue_held_htlc_available_res.is_err() {
5445
+ self.abandon_payment_with_reason(
5446
+ payment_id,
5447
+ PaymentFailureReason::BlindedPathCreationFailed,
5448
+ );
5449
+ res = Err(Bolt12PaymentError::BlindedPathCreationFailed);
5450
+ return NotifyOption::DoPersist;
5451
+ };
5452
+ }
5428
5453
5429
5454
NotifyOption::DoPersist
5430
5455
});
@@ -5469,26 +5494,15 @@ where
5469
5494
}
5470
5495
}
5471
5496
5497
+ /// If we want the HTLCs for this payment to be held at the next-hop channel counterparty, use
5498
+ /// [`Self::hold_htlc_channels`] and pass the resulting channels in here.
5472
5499
fn send_payment_for_static_invoice(
5473
- &self, payment_id: PaymentId,
5500
+ &self, payment_id: PaymentId, first_hops: Vec<ChannelDetails>,
5474
5501
) -> Result<(), Bolt12PaymentError> {
5475
- let best_block_height = self.best_block.read().unwrap().height;
5476
5502
let mut res = Ok(());
5477
5503
PersistenceNotifierGuard::optionally_notify(self, || {
5478
- let outbound_pmts_res = self.pending_outbound_payments.send_payment_for_static_invoice(
5479
- payment_id,
5480
- &self.router,
5481
- self.list_usable_channels(),
5482
- || self.compute_inflight_htlcs(),
5483
- &self.entropy_source,
5484
- &self.node_signer,
5485
- &self,
5486
- &self.secp_ctx,
5487
- best_block_height,
5488
- &self.logger,
5489
- &self.pending_events,
5490
- |args| self.send_payment_along_path(args),
5491
- );
5504
+ let outbound_pmts_res =
5505
+ self.send_payment_for_static_invoice_no_persist(payment_id, first_hops);
5492
5506
match outbound_pmts_res {
5493
5507
Err(Bolt12PaymentError::UnexpectedInvoice)
5494
5508
| Err(Bolt12PaymentError::DuplicateInvoice) => {
@@ -5504,6 +5518,27 @@ where
5504
5518
res
5505
5519
}
5506
5520
5521
+ /// Useful if the caller is already triggering a persist of the `ChannelManager`.
5522
+ fn send_payment_for_static_invoice_no_persist(
5523
+ &self, payment_id: PaymentId, first_hops: Vec<ChannelDetails>,
5524
+ ) -> Result<(), Bolt12PaymentError> {
5525
+ let best_block_height = self.best_block.read().unwrap().height;
5526
+ self.pending_outbound_payments.send_payment_for_static_invoice(
5527
+ payment_id,
5528
+ &self.router,
5529
+ first_hops,
5530
+ || self.compute_inflight_htlcs(),
5531
+ &self.entropy_source,
5532
+ &self.node_signer,
5533
+ &self,
5534
+ &self.secp_ctx,
5535
+ best_block_height,
5536
+ &self.logger,
5537
+ &self.pending_events,
5538
+ |args| self.send_payment_along_path(args),
5539
+ )
5540
+ }
5541
+
5507
5542
/// If we are holding an HTLC on behalf of an often-offline sender, this method allows us to
5508
5543
/// create a path for the sender to use as the reply path when they send the recipient a
5509
5544
/// [`HeldHtlcAvailable`] onion message, so the recipient's [`ReleaseHeldHtlc`] response will be
@@ -14781,7 +14816,9 @@ where
14781
14816
fn handle_release_held_htlc(&self, _message: ReleaseHeldHtlc, context: AsyncPaymentsContext) {
14782
14817
match context {
14783
14818
AsyncPaymentsContext::OutboundPayment { payment_id } => {
14784
- if let Err(e) = self.send_payment_for_static_invoice(payment_id) {
14819
+ if let Err(e) =
14820
+ self.send_payment_for_static_invoice(payment_id, self.list_usable_channels())
14821
+ {
14785
14822
log_trace!(
14786
14823
self.logger,
14787
14824
"Failed to release held HTLC with payment id {}: {:?}",
0 commit comments