Skip to content

Commit 560150d

Browse files
Merge pull request #4045 from valentinewallace/2025-08-async-send-lsp
Async send always-online counterparty side
2 parents 867f084 + 6aa52e9 commit 560150d

File tree

14 files changed

+448
-112
lines changed

14 files changed

+448
-112
lines changed

lightning-types/src/features.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@
8282
//! (see [BOLT PR #1228](https://github.com/lightning/bolts/pull/1228) for more info).
8383
//! - `Splice` - Allows replacing the currently-locked funding transaction with a new one
8484
//! (see [BOLT PR #1160](https://github.com/lightning/bolts/pull/1160) for more information).
85+
//! - `HtlcHold` - requires/supports holding HTLCs and forwarding on receipt of an onion message
86+
//! (see [BOLT-2](https://github.com/lightning/bolts/pull/989/files) for more information).
8587
//!
8688
//! LDK knows about the following features, but does not support them:
8789
//! - `AnchorsNonzeroFeeHtlcTx` - the initial version of anchor outputs, which was later found to be
@@ -166,6 +168,10 @@ mod sealed {
166168
ZeroConf,
167169
// Byte 7
168170
Trampoline | SimpleClose | Splice,
171+
// Byte 8 - 130
172+
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
173+
// Byte 131
174+
HtlcHold,
169175
]
170176
);
171177
define_context!(
@@ -191,6 +197,10 @@ mod sealed {
191197
,,,,,,,,,,,,,,,,,,,,,,,,
192198
// Byte 32
193199
DnsResolver,
200+
// Byte 33 - 130
201+
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
202+
// Byte 131
203+
HtlcHold,
194204
]
195205
);
196206
define_context!(ChannelContext, []);
@@ -700,6 +710,17 @@ mod sealed {
700710
supports_dns_resolution,
701711
requires_dns_resolution
702712
);
713+
define_feature!(
714+
1053, // The BOLTs PR uses feature bit 52/53, so add +1000 for the experimental bit
715+
HtlcHold,
716+
[InitContext, NodeContext],
717+
"Feature flags for holding HTLCs and forwarding on receipt of an onion message",
718+
set_htlc_hold_optional,
719+
set_htlc_hold_required,
720+
clear_htlc_hold,
721+
supports_htlc_hold,
722+
requires_htlc_hold
723+
);
703724

704725
// Note: update the module-level docs when a new feature bit is added!
705726

lightning/src/blinded_path/message.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::blinded_path::{BlindedHop, BlindedPath, Direction, IntroductionNode,
1919
use crate::crypto::streams::ChaChaPolyReadAdapter;
2020
use crate::io;
2121
use crate::io::Cursor;
22-
use crate::ln::channelmanager::PaymentId;
22+
use crate::ln::channelmanager::{InterceptId, PaymentId};
2323
use crate::ln::msgs::DecodeError;
2424
use crate::ln::onion_utils;
2525
use crate::offers::nonce::Nonce;
@@ -556,7 +556,7 @@ pub enum AsyncPaymentsContext {
556556
},
557557
/// Context contained within the reply [`BlindedMessagePath`] we put in outbound
558558
/// [`HeldHtlcAvailable`] messages, provided back to us in corresponding [`ReleaseHeldHtlc`]
559-
/// messages.
559+
/// messages if we are an always-online sender paying an async recipient.
560560
///
561561
/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
562562
/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
@@ -577,6 +577,17 @@ pub enum AsyncPaymentsContext {
577577
/// able to trivially ask if we're online forever.
578578
path_absolute_expiry: core::time::Duration,
579579
},
580+
/// Context contained within the reply [`BlindedMessagePath`] put in outbound
581+
/// [`HeldHtlcAvailable`] messages, provided back to the async sender's always-online counterparty
582+
/// in corresponding [`ReleaseHeldHtlc`] messages.
583+
///
584+
/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
585+
/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
586+
ReleaseHeldHtlc {
587+
/// An identifier for the HTLC that should be released by us as the sender's always-online
588+
/// channel counterparty to the often-offline recipient.
589+
intercept_id: InterceptId,
590+
},
580591
}
581592

582593
impl_writeable_tlv_based_enum!(MessageContext,
@@ -632,6 +643,9 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
632643
(2, invoice_slot, required),
633644
(4, path_absolute_expiry, required),
634645
},
646+
(6, ReleaseHeldHtlc) => {
647+
(0, intercept_id, required),
648+
},
635649
);
636650

637651
/// Contains a simple nonce for use in a blinded path's context.

lightning/src/ln/blinded_payment_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1522,6 +1522,7 @@ fn update_add_msg(
15221522
onion_routing_packet,
15231523
skimmed_fee_msat: None,
15241524
blinding_point,
1525+
hold_htlc: None,
15251526
}
15261527
}
15271528

lightning/src/ln/channel.rs

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use bitcoin::secp256k1::{ecdsa::Signature, Secp256k1};
2626
use bitcoin::secp256k1::{PublicKey, SecretKey};
2727
use bitcoin::{secp256k1, sighash, FeeRate, Sequence, TxIn};
2828

29+
use crate::blinded_path::message::BlindedMessagePath;
2930
use crate::chain::chaininterface::{
3031
fee_for_weight, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator,
3132
};
@@ -273,6 +274,24 @@ impl InboundHTLCState {
273274
_ => None,
274275
}
275276
}
277+
278+
/// Whether we need to hold onto this HTLC until receipt of a corresponding [`ReleaseHeldHtlc`]
279+
/// onion message.
280+
///
281+
/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
282+
fn should_hold_htlc(&self) -> bool {
283+
match self {
284+
InboundHTLCState::RemoteAnnounced(res)
285+
| InboundHTLCState::AwaitingRemoteRevokeToAnnounce(res)
286+
| InboundHTLCState::AwaitingAnnouncedRemoteRevoke(res) => match res {
287+
InboundHTLCResolution::Pending { update_add_htlc } => {
288+
update_add_htlc.hold_htlc.is_some()
289+
},
290+
InboundHTLCResolution::Resolved { .. } => false,
291+
},
292+
InboundHTLCState::Committed | InboundHTLCState::LocalRemoved(_) => false,
293+
}
294+
}
276295
}
277296

278297
struct InboundHTLCOutput {
@@ -1588,12 +1607,12 @@ where
15881607
}
15891608

15901609
#[rustfmt::skip]
1591-
pub fn signer_maybe_unblocked<L: Deref>(
1592-
&mut self, chain_hash: ChainHash, logger: &L,
1593-
) -> Option<SignerResumeUpdates> where L::Target: Logger {
1610+
pub fn signer_maybe_unblocked<L: Deref, CBP>(
1611+
&mut self, chain_hash: ChainHash, logger: &L, path_for_release_htlc: CBP
1612+
) -> Option<SignerResumeUpdates> where L::Target: Logger, CBP: Fn(u64) -> BlindedMessagePath {
15941613
match &mut self.phase {
15951614
ChannelPhase::Undefined => unreachable!(),
1596-
ChannelPhase::Funded(chan) => Some(chan.signer_maybe_unblocked(logger)),
1615+
ChannelPhase::Funded(chan) => Some(chan.signer_maybe_unblocked(logger, path_for_release_htlc)),
15971616
ChannelPhase::UnfundedOutboundV1(chan) => {
15981617
let (open_channel, funding_created) = chan.signer_maybe_unblocked(chain_hash, logger);
15991618
Some(SignerResumeUpdates {
@@ -8901,13 +8920,14 @@ where
89018920
/// successfully and we should restore normal operation. Returns messages which should be sent
89028921
/// to the remote side.
89038922
#[rustfmt::skip]
8904-
pub fn monitor_updating_restored<L: Deref, NS: Deref>(
8923+
pub fn monitor_updating_restored<L: Deref, NS: Deref, CBP>(
89058924
&mut self, logger: &L, node_signer: &NS, chain_hash: ChainHash,
8906-
user_config: &UserConfig, best_block_height: u32
8925+
user_config: &UserConfig, best_block_height: u32, path_for_release_htlc: CBP
89078926
) -> MonitorRestoreUpdates
89088927
where
89098928
L::Target: Logger,
8910-
NS::Target: NodeSigner
8929+
NS::Target: NodeSigner,
8930+
CBP: Fn(u64) -> BlindedMessagePath
89118931
{
89128932
assert!(self.context.channel_state.is_monitor_update_in_progress());
89138933
self.context.channel_state.clear_monitor_update_in_progress();
@@ -8976,7 +8996,7 @@ where
89768996
}
89778997

89788998
let mut raa = if self.context.monitor_pending_revoke_and_ack {
8979-
self.get_last_revoke_and_ack(logger)
8999+
self.get_last_revoke_and_ack(path_for_release_htlc, logger)
89809000
} else { None };
89819001
let mut commitment_update = if self.context.monitor_pending_commitment_signed {
89829002
self.get_last_commitment_update_for_send(logger).ok()
@@ -9066,7 +9086,9 @@ where
90669086
/// Indicates that the signer may have some signatures for us, so we should retry if we're
90679087
/// blocked.
90689088
#[rustfmt::skip]
9069-
pub fn signer_maybe_unblocked<L: Deref>(&mut self, logger: &L) -> SignerResumeUpdates where L::Target: Logger {
9089+
pub fn signer_maybe_unblocked<L: Deref, CBP>(
9090+
&mut self, logger: &L, path_for_release_htlc: CBP
9091+
) -> SignerResumeUpdates where L::Target: Logger, CBP: Fn(u64) -> BlindedMessagePath {
90709092
if !self.holder_commitment_point.can_advance() {
90719093
log_trace!(logger, "Attempting to update holder per-commitment point...");
90729094
self.holder_commitment_point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);
@@ -9094,7 +9116,7 @@ where
90949116
} else { None };
90959117
let mut revoke_and_ack = if self.context.signer_pending_revoke_and_ack {
90969118
log_trace!(logger, "Attempting to generate pending revoke and ack...");
9097-
self.get_last_revoke_and_ack(logger)
9119+
self.get_last_revoke_and_ack(path_for_release_htlc, logger)
90989120
} else { None };
90999121

91009122
if self.context.resend_order == RAACommitmentOrder::CommitmentFirst
@@ -9165,9 +9187,12 @@ where
91659187
}
91669188
}
91679189

9168-
fn get_last_revoke_and_ack<L: Deref>(&mut self, logger: &L) -> Option<msgs::RevokeAndACK>
9190+
fn get_last_revoke_and_ack<CBP, L: Deref>(
9191+
&mut self, path_for_release_htlc: CBP, logger: &L,
9192+
) -> Option<msgs::RevokeAndACK>
91699193
where
91709194
L::Target: Logger,
9195+
CBP: Fn(u64) -> BlindedMessagePath,
91719196
{
91729197
debug_assert!(
91739198
self.holder_commitment_point.next_transaction_number() <= INITIAL_COMMITMENT_NUMBER - 2
@@ -9180,13 +9205,22 @@ where
91809205
.ok();
91819206
if let Some(per_commitment_secret) = per_commitment_secret {
91829207
if self.holder_commitment_point.can_advance() {
9208+
let mut release_htlc_message_paths = Vec::new();
9209+
for htlc in &self.context.pending_inbound_htlcs {
9210+
if htlc.state.should_hold_htlc() {
9211+
let path = path_for_release_htlc(htlc.htlc_id);
9212+
release_htlc_message_paths.push((htlc.htlc_id, path));
9213+
}
9214+
}
9215+
91839216
self.context.signer_pending_revoke_and_ack = false;
91849217
return Some(msgs::RevokeAndACK {
91859218
channel_id: self.context.channel_id,
91869219
per_commitment_secret,
91879220
next_per_commitment_point: self.holder_commitment_point.next_point(),
91889221
#[cfg(taproot)]
91899222
next_local_nonce: None,
9223+
release_htlc_message_paths,
91909224
});
91919225
}
91929226
}
@@ -9234,6 +9268,7 @@ where
92349268
onion_routing_packet: (**onion_packet).clone(),
92359269
skimmed_fee_msat: htlc.skimmed_fee_msat,
92369270
blinding_point: htlc.blinding_point,
9271+
hold_htlc: None, // Will be set by the async sender when support is added
92379272
});
92389273
}
92399274
}
@@ -9333,13 +9368,15 @@ where
93339368
/// May panic if some calls other than message-handling calls (which will all Err immediately)
93349369
/// have been called between remove_uncommitted_htlcs_and_mark_paused and this call.
93359370
#[rustfmt::skip]
9336-
pub fn channel_reestablish<L: Deref, NS: Deref>(
9371+
pub fn channel_reestablish<L: Deref, NS: Deref, CBP>(
93379372
&mut self, msg: &msgs::ChannelReestablish, logger: &L, node_signer: &NS,
9338-
chain_hash: ChainHash, user_config: &UserConfig, best_block: &BestBlock
9373+
chain_hash: ChainHash, user_config: &UserConfig, best_block: &BestBlock,
9374+
path_for_release_htlc: CBP,
93399375
) -> Result<ReestablishResponses, ChannelError>
93409376
where
93419377
L::Target: Logger,
9342-
NS::Target: NodeSigner
9378+
NS::Target: NodeSigner,
9379+
CBP: Fn(u64) -> BlindedMessagePath
93439380
{
93449381
if !self.context.channel_state.is_peer_disconnected() {
93459382
// While BOLT 2 doesn't indicate explicitly we should error this channel here, it
@@ -9555,7 +9592,7 @@ where
95559592
self.context.monitor_pending_revoke_and_ack = true;
95569593
None
95579594
} else {
9558-
self.get_last_revoke_and_ack(logger)
9595+
self.get_last_revoke_and_ack(path_for_release_htlc, logger)
95599596
}
95609597
} else {
95619598
debug_assert!(false, "All values should have been handled in the four cases above");
@@ -16782,6 +16819,7 @@ mod tests {
1678216819
chain_hash,
1678316820
&config,
1678416821
0,
16822+
|_| unreachable!()
1678516823
);
1678616824

1678716825
// Receive funding_signed, but the channel will be configured to hold sending channel_ready and
@@ -16796,6 +16834,7 @@ mod tests {
1679616834
chain_hash,
1679716835
&config,
1679816836
0,
16837+
|_| unreachable!()
1679916838
);
1680016839
// Our channel_ready shouldn't be sent yet, even with trust_own_funding_0conf set,
1680116840
// as the funding transaction depends on all channels in the batch becoming ready.

0 commit comments

Comments
 (0)