Skip to content

Commit 28dc289

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 00c52ec commit 28dc289

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
@@ -5375,12 +5375,14 @@ where
53755375
&self, invoice: &StaticInvoice, payment_id: PaymentId,
53765376
) -> Result<(), Bolt12PaymentError> {
53775377
let mut res = Ok(());
5378+
let hold_htlc_channels_res = self.hold_htlc_channels();
53785379
PersistenceNotifierGuard::optionally_notify(self, || {
53795380
let best_block_height = self.best_block.read().unwrap().height;
53805381
let features = self.bolt12_invoice_features();
53815382
let outbound_pmts_res = self.pending_outbound_payments.static_invoice_received(
53825383
invoice,
53835384
payment_id,
5385+
hold_htlc_channels_res.is_ok(),
53845386
features,
53855387
best_block_height,
53865388
self.duration_since_epoch(),
@@ -5420,6 +5422,43 @@ where
54205422
res
54215423
}
54225424

5425+
/// Returns a list of channels where our counterparty supports
5426+
/// [`InitFeatures::supports_htlc_hold`], or an error if there are none or we detect that we are
5427+
/// an announced node. Useful for sending async payments to [`StaticInvoice`]s.
5428+
fn hold_htlc_channels(&self) -> Result<Vec<ChannelDetails>, ()> {
5429+
let should_send_async = {
5430+
let cfg = self.config.read().unwrap();
5431+
cfg.hold_outbound_htlcs_at_next_hop
5432+
&& !cfg.channel_handshake_config.announce_for_forwarding
5433+
&& cfg.channel_handshake_limits.force_announced_channel_preference
5434+
};
5435+
if !should_send_async {
5436+
return Err(());
5437+
}
5438+
5439+
let any_announced_channels = AtomicBool::new(false);
5440+
let hold_htlc_channels =
5441+
self.list_funded_channels_with_filter(|&(init_features, _, ref channel)| {
5442+
// If we have an announced channel, we are a node that is expected to be always-online and
5443+
// shouldn't be relying on channel counterparties to hold onto our HTLCs for us while
5444+
// waiting for the payment recipient to come online.
5445+
if channel.context().should_announce() {
5446+
any_announced_channels.store(true, Ordering::Relaxed);
5447+
}
5448+
if any_announced_channels.load(Ordering::Relaxed) {
5449+
return false;
5450+
}
5451+
5452+
init_features.supports_htlc_hold() && channel.context().is_live()
5453+
});
5454+
5455+
if any_announced_channels.load(Ordering::Relaxed) || hold_htlc_channels.is_empty() {
5456+
Err(())
5457+
} else {
5458+
Ok(hold_htlc_channels)
5459+
}
5460+
}
5461+
54235462
fn send_payment_for_static_invoice(
54245463
&self, payment_id: PaymentId,
54255464
) -> 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
@@ -935,6 +935,18 @@ pub struct UserConfig {
935935
///
936936
/// Default value: `false`
937937
pub enable_dual_funded_channels: bool,
938+
/// If this is set to true, then if we receive a [`StaticInvoice`] to pay, we will attempt to hold
939+
/// the corresponding outbound HTLCs with our next-hop channel counterparty(s) that support the
940+
/// `htlc_hold` feature. This allows our node to go offline once the HTLCs are locked in even
941+
/// though the recipient may not yet be online to receive them.
942+
///
943+
/// This option only applies if we are a private node, and will be ignored if we are an announced
944+
/// node that is expected to be online at all times.
945+
///
946+
/// Default value: `true`
947+
///
948+
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
949+
pub hold_outbound_htlcs_at_next_hop: bool,
938950
}
939951

940952
impl Default for UserConfig {
@@ -949,6 +961,7 @@ impl Default for UserConfig {
949961
accept_intercept_htlcs: false,
950962
manually_handle_bolt12_invoices: false,
951963
enable_dual_funded_channels: false,
964+
hold_outbound_htlcs_at_next_hop: true,
952965
}
953966
}
954967
}
@@ -969,6 +982,7 @@ impl Readable for UserConfig {
969982
accept_intercept_htlcs: Readable::read(reader)?,
970983
manually_handle_bolt12_invoices: Readable::read(reader)?,
971984
enable_dual_funded_channels: Readable::read(reader)?,
985+
send_payments_async: Readable::read(reader)?,
972986
})
973987
}
974988
}

0 commit comments

Comments
 (0)