Skip to content

Commit b67df7b

Browse files
Set UpdateAddHTLC::hold_htlc for offline payees
As part of supporting sending payments as an often-offline sender, the sender needs to be able to set a flag in their update_add_htlc message indicating that the HTLC should be held until receipt of a release_held_htlc onion message from the often-offline payment recipient. The prior commits laid groundwork to finally set the flag here in this commit. See-also <lightning/bolts#989>
1 parent 7431c35 commit b67df7b

File tree

3 files changed

+79
-38
lines changed

3 files changed

+79
-38
lines changed

lightning/src/ln/channel.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9268,7 +9268,7 @@ where
92689268
onion_routing_packet: (**onion_packet).clone(),
92699269
skimmed_fee_msat: htlc.skimmed_fee_msat,
92709270
blinding_point: htlc.blinding_point,
9271-
hold_htlc: None, // Will be set by the async sender when support is added
9271+
hold_htlc: htlc.source.hold_htlc_at_next_hop().then(|| ()),
92729272
});
92739273
}
92749274
}

lightning/src/ln/channelmanager.rs

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,13 @@ impl HTLCSource {
844844
_ => None,
845845
}
846846
}
847+
848+
pub(crate) fn hold_htlc_at_next_hop(&self) -> bool {
849+
match self {
850+
Self::OutboundRoute { hold_htlc, .. } => hold_htlc.is_some(),
851+
_ => false,
852+
}
853+
}
847854
}
848855

849856
/// This enum is used to specify which error data to send to peers when failing back an HTLC
@@ -4943,6 +4950,7 @@ where
49434950
invoice_request: None,
49444951
bolt12_invoice: None,
49454952
session_priv_bytes,
4953+
hold_htlc_at_next_hop: false,
49464954
})
49474955
}
49484956

@@ -4958,6 +4966,7 @@ where
49584966
invoice_request,
49594967
bolt12_invoice,
49604968
session_priv_bytes,
4969+
hold_htlc_at_next_hop,
49614970
} = args;
49624971
// The top-level caller should hold the total_consistency_lock read lock.
49634972
debug_assert!(self.total_consistency_lock.try_write().is_err());
@@ -5039,7 +5048,7 @@ where
50395048
first_hop_htlc_msat: htlc_msat,
50405049
payment_id,
50415050
bolt12_invoice: bolt12_invoice.cloned(),
5042-
hold_htlc: None,
5051+
hold_htlc: hold_htlc_at_next_hop.then(|| ()),
50435052
};
50445053
let send_res = chan.send_htlc_and_commit(
50455054
htlc_msat,
@@ -5425,19 +5434,35 @@ where
54255434
},
54265435
};
54275436

5428-
let enqueue_held_htlc_available_res = self.flow.enqueue_held_htlc_available(
5429-
invoice,
5430-
payment_id,
5431-
self.get_peers_for_blinded_path(),
5432-
);
5433-
if enqueue_held_htlc_available_res.is_err() {
5434-
self.abandon_payment_with_reason(
5437+
// If the call to `Self::hold_htlc_channels` succeeded, then we are a private node and can
5438+
// hold the HTLCs for this payment at our next-hop channel counterparty until the recipient
5439+
// comes online. This allows us to go offline after locking in the HTLCs.
5440+
if let Ok(channels) = hold_htlc_channels_res {
5441+
if let Err(e) =
5442+
self.send_payment_for_static_invoice_no_persist(payment_id, channels)
5443+
{
5444+
log_trace!(
5445+
self.logger,
5446+
"Failed to send held HTLC with payment id {}: {:?}",
5447+
payment_id,
5448+
e
5449+
);
5450+
}
5451+
} else {
5452+
let enqueue_held_htlc_available_res = self.flow.enqueue_held_htlc_available(
5453+
invoice,
54355454
payment_id,
5436-
PaymentFailureReason::BlindedPathCreationFailed,
5455+
self.get_peers_for_blinded_path(),
54375456
);
5438-
res = Err(Bolt12PaymentError::BlindedPathCreationFailed);
5439-
return NotifyOption::DoPersist;
5440-
};
5457+
if enqueue_held_htlc_available_res.is_err() {
5458+
self.abandon_payment_with_reason(
5459+
payment_id,
5460+
PaymentFailureReason::BlindedPathCreationFailed,
5461+
);
5462+
res = Err(Bolt12PaymentError::BlindedPathCreationFailed);
5463+
return NotifyOption::DoPersist;
5464+
};
5465+
}
54415466

54425467
NotifyOption::DoPersist
54435468
});
@@ -5482,26 +5507,15 @@ where
54825507
}
54835508
}
54845509

5510+
/// If we want the HTLCs for this payment to be held at the next-hop channel counterparty, use
5511+
/// [`Self::hold_htlc_channels`] and pass the resulting channels in here.
54855512
fn send_payment_for_static_invoice(
5486-
&self, payment_id: PaymentId,
5513+
&self, payment_id: PaymentId, first_hops: Vec<ChannelDetails>,
54875514
) -> Result<(), Bolt12PaymentError> {
5488-
let best_block_height = self.best_block.read().unwrap().height;
54895515
let mut res = Ok(());
54905516
PersistenceNotifierGuard::optionally_notify(self, || {
5491-
let outbound_pmts_res = self.pending_outbound_payments.send_payment_for_static_invoice(
5492-
payment_id,
5493-
&self.router,
5494-
self.list_usable_channels(),
5495-
|| self.compute_inflight_htlcs(),
5496-
&self.entropy_source,
5497-
&self.node_signer,
5498-
&self,
5499-
&self.secp_ctx,
5500-
best_block_height,
5501-
&self.logger,
5502-
&self.pending_events,
5503-
|args| self.send_payment_along_path(args),
5504-
);
5517+
let outbound_pmts_res =
5518+
self.send_payment_for_static_invoice_no_persist(payment_id, first_hops);
55055519
match outbound_pmts_res {
55065520
Err(Bolt12PaymentError::UnexpectedInvoice)
55075521
| Err(Bolt12PaymentError::DuplicateInvoice) => {
@@ -5517,6 +5531,27 @@ where
55175531
res
55185532
}
55195533

5534+
/// Useful if the caller is already triggering a persist of the `ChannelManager`.
5535+
fn send_payment_for_static_invoice_no_persist(
5536+
&self, payment_id: PaymentId, first_hops: Vec<ChannelDetails>,
5537+
) -> Result<(), Bolt12PaymentError> {
5538+
let best_block_height = self.best_block.read().unwrap().height;
5539+
self.pending_outbound_payments.send_payment_for_static_invoice(
5540+
payment_id,
5541+
&self.router,
5542+
first_hops,
5543+
|| self.compute_inflight_htlcs(),
5544+
&self.entropy_source,
5545+
&self.node_signer,
5546+
&self,
5547+
&self.secp_ctx,
5548+
best_block_height,
5549+
&self.logger,
5550+
&self.pending_events,
5551+
|args| self.send_payment_along_path(args),
5552+
)
5553+
}
5554+
55205555
/// If we are holding an HTLC on behalf of an often-offline sender, this method allows us to
55215556
/// create a path for the sender to use as the reply path when they send the recipient a
55225557
/// [`HeldHtlcAvailable`] onion message, so the recipient's [`ReleaseHeldHtlc`] response will be
@@ -14734,7 +14769,9 @@ where
1473414769
fn handle_release_held_htlc(&self, _message: ReleaseHeldHtlc, context: AsyncPaymentsContext) {
1473514770
match context {
1473614771
AsyncPaymentsContext::OutboundPayment { payment_id } => {
14737-
if let Err(e) = self.send_payment_for_static_invoice(payment_id) {
14772+
if let Err(e) =
14773+
self.send_payment_for_static_invoice(payment_id, self.list_usable_channels())
14774+
{
1473814775
log_trace!(
1473914776
self.logger,
1474014777
"Failed to release held HTLC with payment id {}: {:?}",

lightning/src/ln/outbound_payment.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,7 @@ pub(super) struct SendAlongPathArgs<'a> {
832832
pub invoice_request: Option<&'a InvoiceRequest>,
833833
pub bolt12_invoice: Option<&'a PaidBolt12Invoice>,
834834
pub session_priv_bytes: [u8; 32],
835+
pub hold_htlc_at_next_hop: bool,
835836
}
836837

837838
pub(super) struct OutboundPayments {
@@ -1064,26 +1065,29 @@ impl OutboundPayments {
10641065

10651066
let payment_params = Some(route_params.payment_params.clone());
10661067
let mut outbounds = self.pending_outbound_payments.lock().unwrap();
1067-
let onion_session_privs = match outbounds.entry(payment_id) {
1068+
let (onion_session_privs, hold_htlcs_at_next_hop) = match outbounds.entry(payment_id) {
10681069
hash_map::Entry::Occupied(entry) => match entry.get() {
10691070
PendingOutboundPayment::InvoiceReceived { .. } => {
10701071
let (retryable_payment, onion_session_privs) = Self::create_pending_payment(
10711072
payment_hash, recipient_onion.clone(), keysend_preimage, None, Some(bolt12_invoice.clone()), &route,
10721073
Some(retry_strategy), payment_params, entropy_source, best_block_height,
10731074
);
10741075
*entry.into_mut() = retryable_payment;
1075-
onion_session_privs
1076+
(onion_session_privs, false)
10761077
},
10771078
PendingOutboundPayment::StaticInvoiceReceived { .. } => {
1078-
let invreq = if let PendingOutboundPayment::StaticInvoiceReceived { invoice_request, .. } = entry.remove() {
1079-
invoice_request
1080-
} else { unreachable!() };
1079+
let (invreq, hold_htlcs_at_next_hop) =
1080+
if let PendingOutboundPayment::StaticInvoiceReceived {
1081+
invoice_request, hold_htlcs_at_next_hop, ..
1082+
} = entry.remove() {
1083+
(invoice_request, hold_htlcs_at_next_hop)
1084+
} else { unreachable!() };
10811085
let (retryable_payment, onion_session_privs) = Self::create_pending_payment(
10821086
payment_hash, recipient_onion.clone(), keysend_preimage, Some(invreq), Some(bolt12_invoice.clone()), &route,
10831087
Some(retry_strategy), payment_params, entropy_source, best_block_height
10841088
);
10851089
outbounds.insert(payment_id, retryable_payment);
1086-
onion_session_privs
1090+
(onion_session_privs, hold_htlcs_at_next_hop)
10871091
},
10881092
_ => return Err(Bolt12PaymentError::DuplicateInvoice),
10891093
},
@@ -1093,7 +1097,7 @@ impl OutboundPayments {
10931097

10941098
let result = self.pay_route_internal(
10951099
&route, payment_hash, &recipient_onion, keysend_preimage, invoice_request, Some(&bolt12_invoice), payment_id,
1096-
Some(route_params.final_value_msat), &onion_session_privs, false, node_signer,
1100+
Some(route_params.final_value_msat), &onion_session_privs, hold_htlcs_at_next_hop, node_signer,
10971101
best_block_height, &send_payment_along_path
10981102
);
10991103
log_info!(
@@ -2130,7 +2134,7 @@ impl OutboundPayments {
21302134
let path_res = send_payment_along_path(SendAlongPathArgs {
21312135
path: &path, payment_hash: &payment_hash, recipient_onion, total_value,
21322136
cur_height, payment_id, keysend_preimage: &keysend_preimage, invoice_request,
2133-
bolt12_invoice,
2137+
bolt12_invoice, hold_htlc_at_next_hop: hold_htlcs_at_next_hop,
21342138
session_priv_bytes: *session_priv_bytes
21352139
});
21362140
results.push(path_res);

0 commit comments

Comments
 (0)