Skip to content

Commit 7431c35

Browse files
Add hold_htlcs field in StaticInvReceived outbounds
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. We don't yet ever set this flag, but lay the groundwork by including the field in the outbound payment variant for static invoices. We also add a helper method to gather channels for nodes that advertise support for the hold_htlc feature, which will be used in the next commit. See-also <lightning/bolts#989>
1 parent a58db22 commit 7431c35

File tree

3 files changed

+76
-2
lines changed

3 files changed

+76
-2
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5398,12 +5398,14 @@ where
53985398
&self, invoice: &StaticInvoice, payment_id: PaymentId,
53995399
) -> Result<(), Bolt12PaymentError> {
54005400
let mut res = Ok(());
5401+
let hold_htlc_channels_res = self.hold_htlc_channels();
54015402
PersistenceNotifierGuard::optionally_notify(self, || {
54025403
let best_block_height = self.best_block.read().unwrap().height;
54035404
let features = self.bolt12_invoice_features();
54045405
let outbound_pmts_res = self.pending_outbound_payments.static_invoice_received(
54055406
invoice,
54065407
payment_id,
5408+
hold_htlc_channels_res.is_ok(),
54075409
features,
54085410
best_block_height,
54095411
self.duration_since_epoch(),
@@ -5443,6 +5445,43 @@ where
54435445
res
54445446
}
54455447

5448+
/// Returns a list of channels where our counterparty supports
5449+
/// [`InitFeatures::supports_htlc_hold`], or an error if there are none or we detect that we are
5450+
/// an announced node. Useful for sending async payments to [`StaticInvoice`]s.
5451+
fn hold_htlc_channels(&self) -> Result<Vec<ChannelDetails>, ()> {
5452+
let should_send_async = {
5453+
let cfg = self.config.read().unwrap();
5454+
cfg.hold_outbound_htlcs_at_next_hop
5455+
&& !cfg.channel_handshake_config.announce_for_forwarding
5456+
&& cfg.channel_handshake_limits.force_announced_channel_preference
5457+
};
5458+
if !should_send_async {
5459+
return Err(());
5460+
}
5461+
5462+
let any_announced_channels = AtomicBool::new(false);
5463+
let hold_htlc_channels =
5464+
self.list_funded_channels_with_filter(|&(init_features, _, ref channel)| {
5465+
// If we have an announced channel, we are a node that is expected to be always-online and
5466+
// shouldn't be relying on channel counterparties to hold onto our HTLCs for us while
5467+
// waiting for the payment recipient to come online.
5468+
if channel.context().should_announce() {
5469+
any_announced_channels.store(true, Ordering::Relaxed);
5470+
}
5471+
if any_announced_channels.load(Ordering::Relaxed) {
5472+
return false;
5473+
}
5474+
5475+
init_features.supports_htlc_hold() && channel.context().is_live()
5476+
});
5477+
5478+
if any_announced_channels.load(Ordering::Relaxed) || hold_htlc_channels.is_empty() {
5479+
Err(())
5480+
} else {
5481+
Ok(hold_htlc_channels)
5482+
}
5483+
}
5484+
54465485
fn send_payment_for_static_invoice(
54475486
&self, payment_id: PaymentId,
54485487
) -> Result<(), Bolt12PaymentError> {

lightning/src/ln/outbound_payment.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ pub(crate) enum PendingOutboundPayment {
103103
route_params: RouteParameters,
104104
invoice_request: InvoiceRequest,
105105
static_invoice: StaticInvoice,
106+
// Whether we should pay the static invoice asynchronously, i.e. by setting
107+
// [`UpdateAddHTLC::hold_htlc`] so our channel counterparty(s) hold the HTLC(s) for us until the
108+
// recipient comes online, allowing us to go offline after locking in the HTLC(s).
109+
hold_htlcs_at_next_hop: bool,
106110
// The deadline as duration since the Unix epoch for the async recipient to come online,
107111
// after which we'll fail the payment.
108112
//
@@ -1107,8 +1111,9 @@ impl OutboundPayments {
11071111
}
11081112

11091113
pub(super) fn static_invoice_received<ES: Deref>(
1110-
&self, invoice: &StaticInvoice, payment_id: PaymentId, features: Bolt12InvoiceFeatures,
1111-
best_block_height: u32, duration_since_epoch: Duration, entropy_source: ES,
1114+
&self, invoice: &StaticInvoice, payment_id: PaymentId, hold_htlcs_at_next_hop: bool,
1115+
features: Bolt12InvoiceFeatures, best_block_height: u32, duration_since_epoch: Duration,
1116+
entropy_source: ES,
11121117
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
11131118
) -> Result<(), Bolt12PaymentError>
11141119
where
@@ -1192,6 +1197,13 @@ impl OutboundPayments {
11921197
RetryableSendFailure::OnionPacketSizeExceeded,
11931198
));
11941199
}
1200+
1201+
// If we expect the HTLCs for this payment to be held at our next-hop counterparty, don't
1202+
// retry the payment. In future iterations of this feature, we will send this payment via
1203+
// trampoline and the counterparty will retry on our behalf.
1204+
if hold_htlcs_at_next_hop {
1205+
*retry_strategy = Retry::Attempts(0);
1206+
}
11951207
let absolute_expiry =
11961208
duration_since_epoch.saturating_add(ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY);
11971209

@@ -1200,6 +1212,7 @@ impl OutboundPayments {
12001212
keysend_preimage,
12011213
retry_strategy: *retry_strategy,
12021214
route_params,
1215+
hold_htlcs_at_next_hop,
12031216
invoice_request: retryable_invoice_request
12041217
.take()
12051218
.ok_or(Bolt12PaymentError::UnexpectedInvoice)?
@@ -2759,6 +2772,12 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
27592772
// HTLCs are in-flight.
27602773
(9, StaticInvoiceReceived) => {
27612774
(0, payment_hash, required),
2775+
// Added in 0.2. If this field is set when this variant is created, the HTLCs are sent
2776+
// immediately after and the pending outbound is also immediately transitioned to Retryable.
2777+
// However, if we crash and then downgrade before the transition to Retryable, this payment will
2778+
// sit in outbounds until it either times out in `remove_stale_payments` or is manually
2779+
// abandoned.
2780+
(1, hold_htlcs_at_next_hop, required),
27622781
(2, keysend_preimage, required),
27632782
(4, retry_strategy, required),
27642783
(6, route_params, required),
@@ -3418,6 +3437,7 @@ mod tests {
34183437
invoice_request: dummy_invoice_request(),
34193438
static_invoice: dummy_static_invoice(),
34203439
expiry_time: Duration::from_secs(absolute_expiry + 2),
3440+
hold_htlcs_at_next_hop: false
34213441
};
34223442
outbounds.insert(payment_id, outbound);
34233443
core::mem::drop(outbounds);
@@ -3468,6 +3488,7 @@ mod tests {
34683488
invoice_request: dummy_invoice_request(),
34693489
static_invoice: dummy_static_invoice(),
34703490
expiry_time: now(),
3491+
hold_htlcs_at_next_hop: false,
34713492
};
34723493
outbounds.insert(payment_id, outbound);
34733494
core::mem::drop(outbounds);

lightning/src/util/config.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,18 @@ pub struct UserConfig {
947947
/// Default value: `false`
948948
#[cfg(test)]
949949
pub enable_htlc_hold: bool,
950+
/// If this is set to true, then if we as an often-offline payer receive a [`StaticInvoice`] to
951+
/// pay, we will attempt to hold the corresponding outbound HTLCs with our next-hop channel
952+
/// counterparty(s) that support the `htlc_hold` feature. This allows our node to go offline once
953+
/// the HTLCs are locked in even though the recipient may not yet be online to receive them.
954+
///
955+
/// This option only applies if we are a private node, and will be ignored if we are an announced
956+
/// node that is expected to be online at all times.
957+
///
958+
/// Default value: `true`
959+
///
960+
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
961+
pub hold_outbound_htlcs_at_next_hop: bool,
950962
}
951963

952964
impl Default for UserConfig {
@@ -963,6 +975,7 @@ impl Default for UserConfig {
963975
enable_dual_funded_channels: false,
964976
#[cfg(test)]
965977
enable_htlc_hold: false,
978+
hold_outbound_htlcs_at_next_hop: true,
966979
}
967980
}
968981
}
@@ -983,6 +996,7 @@ impl Readable for UserConfig {
983996
accept_intercept_htlcs: Readable::read(reader)?,
984997
manually_handle_bolt12_invoices: Readable::read(reader)?,
985998
enable_dual_funded_channels: Readable::read(reader)?,
999+
hold_outbound_htlcs_at_next_hop: Readable::read(reader)?,
9861000
})
9871001
}
9881002
}

0 commit comments

Comments
 (0)