Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions lightning-types/src/features.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,6 +82,8 @@
//! (see [BOLT PR #1228](https://github.com/lightning/bolts/pull/1228) for more info).
//! - `Splice` - Allows replacing the currently-locked funding transaction with a new one
//! (see [BOLT PR #1160](https://github.com/lightning/bolts/pull/1160) for more information).
//! - `HtlcHold` - requires/supports holding HTLCs and forwarding on receipt of an onion message
Comment thread
valentinewallace marked this conversation as resolved.
//! (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 DownExpand Up@@ -166,6 +168,10 @@ mod sealed {
ZeroConf,
// Byte 7
Trampoline | SimpleClose | Splice,
// Byte 8 - 130
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
// Byte 131
HtlcHold,
]
);
define_context!(
Expand All@@ -191,6 +197,10 @@ mod sealed {
,,,,,,,,,,,,,,,,,,,,,,,,
// Byte 32
DnsResolver,
// Byte 33 - 130
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
// Byte 131
HtlcHold,
]
);
define_context!(ChannelContext, []);
Expand DownExpand Up@@ -700,6 +710,17 @@ mod sealed {
supports_dns_resolution,
requires_dns_resolution
);
define_feature!(
1053, // The BOLTs PR uses feature bit 52/53, so add +1000 for the experimental bit
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
);

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

Expand Down
18 changes: 16 additions & 2 deletions lightning/src/blinded_path/message.rs
Original file line numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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
69 changes: 54 additions & 15 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff 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 DownExpand Up@@ -283,6 +284,24 @@ 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::Pending { update_add_htlc } => {
update_add_htlc.hold_htlc.is_some()
},
InboundHTLCResolution::Resolved { .. } => false,
},
InboundHTLCState::Committed | InboundHTLCState::LocalRemoved(_) => false,
}
}
}

struct InboundHTLCOutput {
Expand DownExpand Up@@ -1606,12 +1625,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 DownExpand Up@@ -8712,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 DownExpand Up@@ -8787,7 +8807,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 DownExpand Up@@ -8877,7 +8897,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 DownExpand Up@@ -8905,7 +8927,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 DownExpand Up@@ -8976,9 +8998,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@@ -8991,13 +9016,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 DownExpand Up@@ -9045,6 +9079,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 DownExpand Up@@ -9144,13 +9179,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 DownExpand Up@@ -9369,7 +9406,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 DownExpand Up@@ -16633,6 +16670,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@@ -16647,6 +16685,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