Skip to content
Open
17 changes: 15 additions & 2 deletions lightning-types/src/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@
//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#channel-quiescence) for more information).
//! - `ZeroFeeCommitments` - A channel type which always uses zero transaction fee on commitment transactions.
//! (see [BOLT PR #1228](https://github.com/lightning/bolts/pull/1228) for more info).
//! - `HtlcHold` - requires/supports holding HTLCs and forwarding on receipt of an onion message
//! (see [BOLT-2](https://github.com/lightning/bolts/pull/989/files) for more information).
//!
//! LDK knows about the following features, but does not support them:
//! - `AnchorsNonzeroFeeHtlcTx` - the initial version of anchor outputs, which was later found to be
Expand Down Expand Up @@ -161,7 +163,7 @@ mod sealed {
// Byte 5
ProvideStorage | ChannelType | SCIDPrivacy | AnchorZeroFeeCommitments,
// Byte 6
ZeroConf,
ZeroConf | HtlcHold,
// Byte 7
Trampoline | SimpleClose,
]
Expand All @@ -182,7 +184,7 @@ mod sealed {
// Byte 5
ProvideStorage | ChannelType | SCIDPrivacy | AnchorZeroFeeCommitments,
// Byte 6
ZeroConf | Keysend,
ZeroConf | HtlcHold | Keysend,
// Byte 7
Trampoline | SimpleClose,
// Byte 8 - 31
Expand Down Expand Up @@ -640,6 +642,17 @@ mod sealed {
define_feature!(51, ZeroConf, [InitContext, NodeContext, ChannelTypeContext],
"Feature flags for accepting channels with zero confirmations. Called `option_zeroconf` in the BOLTs",
set_zero_conf_optional, set_zero_conf_required, supports_zero_conf, requires_zero_conf);
define_feature!(
53,
HtlcHold,
[InitContext, NodeContext],
"Feature flags for holding HTLCs and forwarding on receipt of an onion message",
set_htlc_hold_optional,
set_htlc_hold_required,
clear_htlc_hold,
supports_htlc_hold,
requires_htlc_hold
);
define_feature!(
55,
Keysend,
Expand Down
18 changes: 16 additions & 2 deletions lightning/src/blinded_path/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::blinded_path::{BlindedHop, BlindedPath, Direction, IntroductionNode,
use crate::crypto::streams::ChaChaPolyReadAdapter;
use crate::io;
use crate::io::Cursor;
use crate::ln::channelmanager::PaymentId;
use crate::ln::channelmanager::{InterceptId, PaymentId};
use crate::ln::msgs::DecodeError;
use crate::ln::onion_utils;
use crate::offers::nonce::Nonce;
Expand Down Expand Up @@ -556,7 +556,7 @@ pub enum AsyncPaymentsContext {
},
/// Context contained within the reply [`BlindedMessagePath`] we put in outbound
/// [`HeldHtlcAvailable`] messages, provided back to us in corresponding [`ReleaseHeldHtlc`]
/// messages.
/// messages if we are an always-online sender paying an async recipient.
///
/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
Expand All @@ -577,6 +577,17 @@ pub enum AsyncPaymentsContext {
/// able to trivially ask if we're online forever.
path_absolute_expiry: core::time::Duration,
},
/// Context contained within the reply [`BlindedMessagePath`] put in outbound
/// [`HeldHtlcAvailable`] messages, provided back to the async sender's always-online counterparty
/// in corresponding [`ReleaseHeldHtlc`] messages.
///
/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
ReleaseHeldHtlc {
/// An identifier for the HTLC that should be released by us as the sender's always-online
/// channel counterparty to the often-offline recipient.
intercept_id: InterceptId,
},
}

impl_writeable_tlv_based_enum!(MessageContext,
Expand Down Expand Up @@ -632,6 +643,9 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
(2, invoice_slot, required),
(4, path_absolute_expiry, required),
},
(6, ReleaseHeldHtlc) => {
(0, intercept_id, required),
},
);

/// Contains a simple nonce for use in a blinded path's context.
Expand Down
1 change: 1 addition & 0 deletions lightning/src/ln/blinded_payment_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1522,6 +1522,7 @@ fn update_add_msg(
onion_routing_packet,
skimmed_fee_msat: None,
blinding_point,
hold_htlc: None,
}
}

Expand Down
74 changes: 59 additions & 15 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use bitcoin::{secp256k1, sighash, TxIn};
#[cfg(splicing)]
use bitcoin::{FeeRate, Sequence};

use crate::blinded_path::message::BlindedMessagePath;
use crate::chain::chaininterface::{
fee_for_weight, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator,
};
Expand Down Expand Up @@ -282,6 +283,29 @@ impl InboundHTLCState {
_ => None,
}
}

/// Whether we need to hold onto this HTLC until receipt of a corresponding [`ReleaseHeldHtlc`]
/// onion message.
///
/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
fn should_hold_htlc(&self) -> bool {
match self {
InboundHTLCState::RemoteAnnounced(res)
| InboundHTLCState::AwaitingRemoteRevokeToAnnounce(res)
| InboundHTLCState::AwaitingAnnouncedRemoteRevoke(res) => match res {
InboundHTLCResolution::Resolved { pending_htlc_status } => {
match pending_htlc_status {
PendingHTLCStatus::Forward(info) => info.routing.should_hold_htlc(),
_ => false,
}
},
InboundHTLCResolution::Pending { update_add_htlc } => {
update_add_htlc.hold_htlc.is_some()
},
},
InboundHTLCState::Committed | InboundHTLCState::LocalRemoved(_) => false,
}
}
}

struct InboundHTLCOutput {
Expand Down Expand Up @@ -1602,12 +1626,12 @@ where
}

#[rustfmt::skip]
pub fn signer_maybe_unblocked<L: Deref>(
&mut self, chain_hash: ChainHash, logger: &L,
) -> Option<SignerResumeUpdates> where L::Target: Logger {
pub fn signer_maybe_unblocked<L: Deref, CBP>(
&mut self, chain_hash: ChainHash, logger: &L, path_for_release_htlc: CBP
) -> Option<SignerResumeUpdates> where L::Target: Logger, CBP: Fn(u64) -> BlindedMessagePath {
match &mut self.phase {
ChannelPhase::Undefined => unreachable!(),
ChannelPhase::Funded(chan) => Some(chan.signer_maybe_unblocked(logger)),
ChannelPhase::Funded(chan) => Some(chan.signer_maybe_unblocked(logger, path_for_release_htlc)),
ChannelPhase::UnfundedOutboundV1(chan) => {
let (open_channel, funding_created) = chan.signer_maybe_unblocked(chain_hash, logger);
Some(SignerResumeUpdates {
Expand Down Expand Up @@ -8707,13 +8731,14 @@ where
/// successfully and we should restore normal operation. Returns messages which should be sent
/// to the remote side.
#[rustfmt::skip]
pub fn monitor_updating_restored<L: Deref, NS: Deref>(
pub fn monitor_updating_restored<L: Deref, NS: Deref, CBP>(
&mut self, logger: &L, node_signer: &NS, chain_hash: ChainHash,
user_config: &UserConfig, best_block_height: u32
user_config: &UserConfig, best_block_height: u32, path_for_release_htlc: CBP
) -> MonitorRestoreUpdates
where
L::Target: Logger,
NS::Target: NodeSigner
NS::Target: NodeSigner,
CBP: Fn(u64) -> BlindedMessagePath
{
assert!(self.context.channel_state.is_monitor_update_in_progress());
self.context.channel_state.clear_monitor_update_in_progress();
Expand Down Expand Up @@ -8770,7 +8795,7 @@ where
}

let mut raa = if self.context.monitor_pending_revoke_and_ack {
self.get_last_revoke_and_ack(logger)
self.get_last_revoke_and_ack(path_for_release_htlc, logger)
} else { None };
let mut commitment_update = if self.context.monitor_pending_commitment_signed {
self.get_last_commitment_update_for_send(logger).ok()
Expand Down Expand Up @@ -8859,7 +8884,9 @@ where
/// Indicates that the signer may have some signatures for us, so we should retry if we're
/// blocked.
#[rustfmt::skip]
pub fn signer_maybe_unblocked<L: Deref>(&mut self, logger: &L) -> SignerResumeUpdates where L::Target: Logger {
pub fn signer_maybe_unblocked<L: Deref, CBP>(
&mut self, logger: &L, path_for_release_htlc: CBP
) -> SignerResumeUpdates where L::Target: Logger, CBP: Fn(u64) -> BlindedMessagePath {
if !self.holder_commitment_point.can_advance() {
log_trace!(logger, "Attempting to update holder per-commitment point...");
self.holder_commitment_point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);
Expand Down Expand Up @@ -8887,7 +8914,7 @@ where
} else { None };
let mut revoke_and_ack = if self.context.signer_pending_revoke_and_ack {
log_trace!(logger, "Attempting to generate pending revoke and ack...");
self.get_last_revoke_and_ack(logger)
self.get_last_revoke_and_ack(path_for_release_htlc, logger)
} else { None };

if self.context.resend_order == RAACommitmentOrder::CommitmentFirst
Expand Down Expand Up @@ -8958,9 +8985,12 @@ where
}
}

fn get_last_revoke_and_ack<L: Deref>(&mut self, logger: &L) -> Option<msgs::RevokeAndACK>
fn get_last_revoke_and_ack<CBP, L: Deref>(
&mut self, path_for_release_htlc: CBP, logger: &L,
) -> Option<msgs::RevokeAndACK>
where
L::Target: Logger,
CBP: Fn(u64) -> BlindedMessagePath,
{
debug_assert!(
self.holder_commitment_point.next_transaction_number() <= INITIAL_COMMITMENT_NUMBER - 2
Expand All @@ -8973,13 +9003,22 @@ where
.ok();
if let Some(per_commitment_secret) = per_commitment_secret {
if self.holder_commitment_point.can_advance() {
let mut release_htlc_message_paths = Vec::new();
for htlc in &self.context.pending_inbound_htlcs {
if htlc.state.should_hold_htlc() {
let path = path_for_release_htlc(htlc.htlc_id);
release_htlc_message_paths.push((htlc.htlc_id, path));
}
}

self.context.signer_pending_revoke_and_ack = false;
return Some(msgs::RevokeAndACK {
channel_id: self.context.channel_id,
per_commitment_secret,
next_per_commitment_point: self.holder_commitment_point.next_point(),
#[cfg(taproot)]
next_local_nonce: None,
release_htlc_message_paths,
});
}
}
Expand Down Expand Up @@ -9027,6 +9066,7 @@ where
onion_routing_packet: (**onion_packet).clone(),
skimmed_fee_msat: htlc.skimmed_fee_msat,
blinding_point: htlc.blinding_point,
hold_htlc: None, // Will be set by the async sender when support is added
});
}
}
Expand Down Expand Up @@ -9126,13 +9166,15 @@ where
/// May panic if some calls other than message-handling calls (which will all Err immediately)
/// have been called between remove_uncommitted_htlcs_and_mark_paused and this call.
#[rustfmt::skip]
pub fn channel_reestablish<L: Deref, NS: Deref>(
pub fn channel_reestablish<L: Deref, NS: Deref, CBP>(
&mut self, msg: &msgs::ChannelReestablish, logger: &L, node_signer: &NS,
chain_hash: ChainHash, user_config: &UserConfig, best_block: &BestBlock
chain_hash: ChainHash, user_config: &UserConfig, best_block: &BestBlock,
path_for_release_htlc: CBP,
) -> Result<ReestablishResponses, ChannelError>
where
L::Target: Logger,
NS::Target: NodeSigner
NS::Target: NodeSigner,
CBP: Fn(u64) -> BlindedMessagePath
{
if !self.context.channel_state.is_peer_disconnected() {
// While BOLT 2 doesn't indicate explicitly we should error this channel here, it
Expand Down Expand Up @@ -9233,7 +9275,7 @@ where
self.context.monitor_pending_revoke_and_ack = true;
None
} else {
self.get_last_revoke_and_ack(logger)
self.get_last_revoke_and_ack(path_for_release_htlc, logger)
}
} else {
debug_assert!(false, "All values should have been handled in the four cases above");
Expand Down Expand Up @@ -16488,6 +16530,7 @@ mod tests {
chain_hash,
&config,
0,
|_| unreachable!()
);

// Receive funding_signed, but the channel will be configured to hold sending channel_ready and
Expand All @@ -16502,6 +16545,7 @@ mod tests {
chain_hash,
&config,
0,
|_| unreachable!()
);
// Our channel_ready shouldn't be sent yet, even with trust_own_funding_0conf set,
// as the funding transaction depends on all channels in the batch becoming ready.
Expand Down
Loading
Loading