Skip to content

Commit 1b64309

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 c86d650 commit 1b64309

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
@@ -5402,12 +5402,14 @@ where
54025402
&self, invoice: &StaticInvoice, payment_id: PaymentId,
54035403
) -> Result<(), Bolt12PaymentError> {
54045404
let mut res = Ok(());
5405+
let hold_htlc_channels_res = self.hold_htlc_channels();
54055406
PersistenceNotifierGuard::optionally_notify(self, || {
54065407
let best_block_height = self.best_block.read().unwrap().height;
54075408
let features = self.bolt12_invoice_features();
54085409
let outbound_pmts_res = self.pending_outbound_payments.static_invoice_received(
54095410
invoice,
54105411
payment_id,
5412+
hold_htlc_channels_res.is_ok(),
54115413
features,
54125414
best_block_height,
54135415
self.duration_since_epoch(),
@@ -5447,6 +5449,43 @@ where
54475449
res
54485450
}
54495451

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