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 number Diff line number Diff 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
//! (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 @@ -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 Down Expand 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 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
69 changes: 54 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 @@ -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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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