9 changes: 9 additions & 0 deletions lightning-types/src/features.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1024,6 +1024,15 @@ impl ChannelTypeFeatures {
<sealed::ChannelTypeContext as sealed::AnchorsZeroFeeHtlcTx>::set_required_bit(&mut ret);
ret
}

/// Constructs a ChannelTypeFeatures with zero fee commitment anchors support.
pub fn anchors_zero_fee_commitments() -> Self {
let mut ret = Self::empty();
<sealed::ChannelTypeContext as sealed::AnchorZeroFeeCommitments>::set_required_bit(
&mut ret,
);
ret
}
}

impl<T: sealed::Context> Features<T> {
Expand Down
9 changes: 7 additions & 2 deletions lightning/src/ln/chan_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,8 +58,13 @@ use crate::prelude::*;
/// 483 for non-zero-fee-commitment channels and 114 for zero-fee-commitment channels.
///
/// Actual maximums can be set equal to or below this value by each channel participant.
pub fn max_htlcs(_channel_type: &ChannelTypeFeatures) -> u16 {
483
pub fn max_htlcs(channel_type: &ChannelTypeFeatures) -> u16 {
if channel_type.supports_anchor_zero_fee_commitments() {
// TRUC restricts the size of our commitment transactions to 10K vB rather than 100K vB
114
} else {
483
}
}
/// The weight of a BIP141 witnessScript for a BOLT3's "offered HTLC output" on a commitment transaction, non-anchor variant.
pub const OFFERED_HTLC_SCRIPT_WEIGHT: usize = 133;
Expand Down
188 changes: 144 additions & 44 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1557,6 +1557,7 @@ impl<SP: Deref> Channel<SP> where

pub fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<Option<OpenChannelMessage>, ()>
where
F::Target: FeeEstimator,
Expand All@@ -1567,13 +1568,17 @@ impl<SP: Deref> Channel<SP> where
ChannelPhase::Funded(_) => Ok(None),
ChannelPhase::UnfundedOutboundV1(chan) => {
let logger = WithChannelContext::from(logger, &chan.context, None);
chan.maybe_handle_error_without_close(chain_hash, fee_estimator, &&logger)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, &&logger, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V1(msg)))
},
ChannelPhase::UnfundedInboundV1(_) => Ok(None),
ChannelPhase::UnfundedV2(chan) => {
if chan.funding.is_outbound() {
chan.maybe_handle_error_without_close(chain_hash, fee_estimator)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V2(msg)))
} else {
Ok(None)
Expand DownExpand Up@@ -3072,12 +3077,18 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
debug_assert!(!channel_type.supports_any_optional_bits());
debug_assert!(!channel_type.requires_unknown_bits_from(&channelmanager::provided_channel_type_features(&config)));

let (commitment_conf_target, anchor_outputs_value_msat) = if channel_type.supports_anchors_zero_fee_htlc_tx() {
(ConfirmationTarget::AnchorChannelFee, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
(ConfirmationTarget::NonAnchorChannelFee, 0)
};
let commitment_feerate = fee_estimator.bounded_sat_per_1000_weight(commitment_conf_target);
let (commitment_feerate, anchor_outputs_value_msat) =
if channel_type.supports_anchor_zero_fee_commitments() {
(0, 0)
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee);
(feerate, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
(feerate, 0)
};
Comment thread
tankyleo marked this conversation as resolved.

let value_to_self_msat = channel_value_satoshis * 1000 - push_msat;
let commitment_tx_fee = commit_tx_fee_sat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type) * 1000;
Expand DownExpand Up@@ -4868,7 +4879,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
/// of the channel type we tried, not of our ability to open any channel at all. We can see if a
/// downgrade of channel features would be possible so that we can still open the channel.
pub(crate) fn maybe_downgrade_channel_features<F: Deref>(
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<(), ()>
where
F::Target: FeeEstimator
Expand All@@ -4885,25 +4897,47 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
// We've exhausted our options
return Err(());
}

// We should never have negotiated `anchors_nonzero_fee_htlc_tx` because it can result in a
// loss of funds.
let channel_type = &funding.channel_transaction_parameters.channel_type_features;
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this assert up because we should never negotiate a non-zero-htlc-anchor channel, and it's a risk of lost funds if we do.

Couldn't see a historical reason why we only checked it if the channel type was supports_anchors_zero_fee_htlc_tx - went back a few commits and couldn't find reasoning. cc @TheBlueMatt


// We support opening a few different types of channels. Try removing our additional
// features one by one until we've either arrived at our default or the counterparty has
// accepted one.
//
// Due to the order below, we may not negotiate `option_anchors_zero_fee_htlc_tx` if the
// counterparty doesn't support `option_scid_privacy`. Since `get_initial_channel_type`
// checks whether the counterparty supports every feature, this would only happen if the
// counterparty is advertising the feature, but rejecting channels proposing the feature for
// whatever reason.
let channel_type = &mut funding.channel_transaction_parameters.channel_type_features;
if channel_type.supports_anchors_zero_fee_htlc_tx() {
channel_type.clear_anchors_zero_fee_htlc_tx();
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());
// accepted one. Features are un-set for the current channel type or any that come before
// it in our order of preference, allowing us to negotiate the "next best" based on the
// counterparty's remaining features per our ranking in `get_initial_channel_type`.
let mut eligible_features = their_features.clone();
if channel_type.supports_anchor_zero_fee_commitments() {
eligible_features.clear_anchor_zero_fee_commitments();
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
eligible_features.clear_anchor_zero_fee_commitments();
eligible_features.clear_anchors_zero_fee_htlc_tx();
} else if channel_type.supports_scid_privacy() {
channel_type.clear_scid_privacy();
eligible_features.clear_scid_privacy();
eligible_features.clear_anchors_zero_fee_htlc_tx();
eligible_features.clear_anchor_zero_fee_commitments();
}

let next_channel_type = get_initial_channel_type(user_config, &eligible_features);

// Note that we can't get `anchor_zero_fee_commitments` type here, which requires zero
// fees, because we downgrade from this channel type first. If there were a superior
// channel type that downgrades to `anchor_zero_fee_commitments`, we'd need to handle
// fee setting differently here. If we proceeded to open a `anchor_zero_fee_commitments`
// channel with non-zero fees, we could produce a non-standard commitment transaction that
// puts us at risk of losing funds. We would expect our peer to reject such a channel
// open, but we don't want to rely on their validation.
assert!(!next_channel_type.supports_anchor_zero_fee_commitments());
Comment thread
carlaKC marked this conversation as resolved.
let conf_target = if next_channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::AnchorChannelFee
} else {
*channel_type = ChannelTypeFeatures::only_static_remote_key();
}
ConfirmationTarget::NonAnchorChannelFee
};
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(conf_target);
funding.channel_transaction_parameters.channel_type_features = next_channel_type;
Comment thread
tankyleo marked this conversation as resolved.

Ok(())
}

Expand DownExpand Up@@ -5262,6 +5296,15 @@ impl<SP: Deref> FundedChannel<SP> where
feerate_per_kw: u32, cur_feerate_per_kw: Option<u32>, logger: &L
) -> Result<(), ChannelError> where F::Target: FeeEstimator, L::Target: Logger,
{
if channel_type.supports_anchor_zero_fee_commitments() {
if feerate_per_kw != 0 {
let err = "Zero Fee Channels must never attempt to use a fee".to_owned();
return Err(ChannelError::close(err));
} else {
return Ok(());
}
}

let lower_limit_conf_target = if channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee
} else {
Expand DownExpand Up@@ -9893,13 +9936,16 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannel` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannel, ()>
where
F::Target: FeeEstimator,
L::Target: Logger,
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
self.get_open_channel(chain_hash, logger).ok_or(())
}

Expand DownExpand Up@@ -10078,8 +10124,9 @@ pub(super) fn channel_type_from_open_channel(

// We only support the channel types defined by the `ChannelManager` in
// `provided_channel_type_features`. The channel type must always support
// `static_remote_key`.
if !channel_type.requires_static_remote_key() {
// `static_remote_key`, either implicitly with `option_zero_fee_commitments`
// or explicitly.
if !channel_type.requires_static_remote_key() && !channel_type.requires_anchor_zero_fee_commitments() {
return Err(ChannelError::close("Channel Type was not understood - we require static remote key".to_owned()));
}
// Make sure we support all of the features behind the channel type.
Expand DownExpand Up@@ -10405,12 +10452,15 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannelV2` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannelV2, ()>
where
F::Target: FeeEstimator
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
Ok(self.get_open_channel_v2(chain_hash))
}

Expand DownExpand Up@@ -10663,10 +10713,21 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures)
ret.set_scid_privacy_required();
}

// Optionally, if the user would like to negotiate the `anchors_zero_fee_htlc_tx` option, we
// set it now. If they don't understand it, we'll fall back to our default of
// `only_static_remotekey`.
if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
// Optionally, if the user would like to negotiate `option_zero_fee_commitments` we set it now.
// If they don't understand it (or we don't want it), we check the same conditions for
// `option_anchors_zero_fee_htlc_tx`. The counterparty can still refuse the channel and we'll
// try to fall back (all the way to `only_static_remotekey`).
#[cfg(not(test))]
let negotiate_zero_fee_commitments = false;

#[cfg(test)]
let negotiate_zero_fee_commitments = config.channel_handshake_config.negotiate_anchor_zero_fee_commitments;

if negotiate_zero_fee_commitments && their_features.supports_anchor_zero_fee_commitments() {
ret.set_anchor_zero_fee_commitments_required();
// `option_static_remote_key` is assumed by `option_zero_fee_commitments`.
ret.clear_static_remote_key();
} else if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
their_features.supports_anchors_zero_fee_htlc_tx() {
ret.set_anchors_zero_fee_htlc_tx_required();
}
Expand DownExpand Up@@ -13267,6 +13328,45 @@ mod tests {
fn test_supports_anchors_zero_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_htlc_tx`, it is the
// resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments`, it is
// the resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;
Comment thread
tankyleo marked this conversation as resolved.

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments_and_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments` and
// `anchors_zero_fee_htlc_tx`, the resulting `channel_type` is
// `anchors_zero_fee_commitments`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

fn do_test_supports_channel_type(config: UserConfig, expected_channel_type: ChannelTypeFeatures) {
let secp_ctx = Secp256k1::new();
let fee_estimator = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
let network = Network::Testnet;
Expand All@@ -13276,21 +13376,13 @@ mod tests {
let node_id_a = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[1; 32]).unwrap());
let node_id_b = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[2; 32]).unwrap());

let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

// It is not enough for just the initiator to signal `option_anchors_zero_fee_htlc_tx`, both
// need to signal it.
// Assert that we get `static_remotekey` when no custom config is negotiated.
let channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
&channelmanager::provided_init_features(&UserConfig::default()), 10000000, 100000, 42,
&config, 0, 42, None, &logger
).unwrap();
assert!(!channel_a.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx());

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();
assert_eq!(channel_a.funding.get_channel_type(), &ChannelTypeFeatures::only_static_remote_key());

let mut channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
Expand All@@ -13307,6 +13399,14 @@ mod tests {

assert_eq!(channel_a.funding.get_channel_type(), &expected_channel_type);
assert_eq!(channel_b.funding.get_channel_type(), &expected_channel_type);

if expected_channel_type.supports_anchor_zero_fee_commitments() {
assert_eq!(channel_a.context.feerate_per_kw, 0);
assert_eq!(channel_b.context.feerate_per_kw, 0);
} else {
assert_ne!(channel_a.context.feerate_per_kw, 0);
assert_ne!(channel_b.context.feerate_per_kw, 0);
}
}

#[test]
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
9 changes: 9 additions & 0 deletions lightning-types/src/features.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1024,6 +1024,15 @@ impl ChannelTypeFeatures {
<sealed::ChannelTypeContext as sealed::AnchorsZeroFeeHtlcTx>::set_required_bit(&mut ret);
ret
}

/// Constructs a ChannelTypeFeatures with zero fee commitment anchors support.
pub fn anchors_zero_fee_commitments() -> Self {
let mut ret = Self::empty();
<sealed::ChannelTypeContext as sealed::AnchorZeroFeeCommitments>::set_required_bit(
&mut ret,
);
ret
}
}

impl<T: sealed::Context> Features<T> {
Expand Down
9 changes: 7 additions & 2 deletions lightning/src/ln/chan_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,8 +58,13 @@ use crate::prelude::*;
/// 483 for non-zero-fee-commitment channels and 114 for zero-fee-commitment channels.
///
/// Actual maximums can be set equal to or below this value by each channel participant.
pub fn max_htlcs(_channel_type: &ChannelTypeFeatures) -> u16 {
483
pub fn max_htlcs(channel_type: &ChannelTypeFeatures) -> u16 {
if channel_type.supports_anchor_zero_fee_commitments() {
// TRUC restricts the size of our commitment transactions to 10K vB rather than 100K vB
114
} else {
483
}
}
/// The weight of a BIP141 witnessScript for a BOLT3's "offered HTLC output" on a commitment transaction, non-anchor variant.
pub const OFFERED_HTLC_SCRIPT_WEIGHT: usize = 133;
Expand Down
188 changes: 144 additions & 44 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1557,6 +1557,7 @@ impl<SP: Deref> Channel<SP> where

pub fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<Option<OpenChannelMessage>, ()>
where
F::Target: FeeEstimator,
Expand All@@ -1567,13 +1568,17 @@ impl<SP: Deref> Channel<SP> where
ChannelPhase::Funded(_) => Ok(None),
ChannelPhase::UnfundedOutboundV1(chan) => {
let logger = WithChannelContext::from(logger, &chan.context, None);
chan.maybe_handle_error_without_close(chain_hash, fee_estimator, &&logger)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, &&logger, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V1(msg)))
},
ChannelPhase::UnfundedInboundV1(_) => Ok(None),
ChannelPhase::UnfundedV2(chan) => {
if chan.funding.is_outbound() {
chan.maybe_handle_error_without_close(chain_hash, fee_estimator)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V2(msg)))
} else {
Ok(None)
Expand DownExpand Up@@ -3072,12 +3077,18 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
debug_assert!(!channel_type.supports_any_optional_bits());
debug_assert!(!channel_type.requires_unknown_bits_from(&channelmanager::provided_channel_type_features(&config)));

let (commitment_conf_target, anchor_outputs_value_msat) = if channel_type.supports_anchors_zero_fee_htlc_tx() {
(ConfirmationTarget::AnchorChannelFee, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
(ConfirmationTarget::NonAnchorChannelFee, 0)
};
let commitment_feerate = fee_estimator.bounded_sat_per_1000_weight(commitment_conf_target);
let (commitment_feerate, anchor_outputs_value_msat) =
if channel_type.supports_anchor_zero_fee_commitments() {
(0, 0)
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee);
(feerate, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
(feerate, 0)
};
Comment thread
tankyleo marked this conversation as resolved.

let value_to_self_msat = channel_value_satoshis * 1000 - push_msat;
let commitment_tx_fee = commit_tx_fee_sat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type) * 1000;
Expand DownExpand Up@@ -4868,7 +4879,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
/// of the channel type we tried, not of our ability to open any channel at all. We can see if a
/// downgrade of channel features would be possible so that we can still open the channel.
pub(crate) fn maybe_downgrade_channel_features<F: Deref>(
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<(), ()>
where
F::Target: FeeEstimator
Expand All@@ -4885,25 +4897,47 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
// We've exhausted our options
return Err(());
}

// We should never have negotiated `anchors_nonzero_fee_htlc_tx` because it can result in a
// loss of funds.
let channel_type = &funding.channel_transaction_parameters.channel_type_features;
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this assert up because we should never negotiate a non-zero-htlc-anchor channel, and it's a risk of lost funds if we do.

Couldn't see a historical reason why we only checked it if the channel type was supports_anchors_zero_fee_htlc_tx - went back a few commits and couldn't find reasoning. cc @TheBlueMatt


// We support opening a few different types of channels. Try removing our additional
// features one by one until we've either arrived at our default or the counterparty has
// accepted one.
//
// Due to the order below, we may not negotiate `option_anchors_zero_fee_htlc_tx` if the
// counterparty doesn't support `option_scid_privacy`. Since `get_initial_channel_type`
// checks whether the counterparty supports every feature, this would only happen if the
// counterparty is advertising the feature, but rejecting channels proposing the feature for
// whatever reason.
let channel_type = &mut funding.channel_transaction_parameters.channel_type_features;
if channel_type.supports_anchors_zero_fee_htlc_tx() {
channel_type.clear_anchors_zero_fee_htlc_tx();
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());
// accepted one. Features are un-set for the current channel type or any that come before
// it in our order of preference, allowing us to negotiate the "next best" based on the
// counterparty's remaining features per our ranking in `get_initial_channel_type`.
let mut eligible_features = their_features.clone();
if channel_type.supports_anchor_zero_fee_commitments() {
eligible_features.clear_anchor_zero_fee_commitments();
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
eligible_features.clear_anchor_zero_fee_commitments();
eligible_features.clear_anchors_zero_fee_htlc_tx();
} else if channel_type.supports_scid_privacy() {
channel_type.clear_scid_privacy();
eligible_features.clear_scid_privacy();
eligible_features.clear_anchors_zero_fee_htlc_tx();
eligible_features.clear_anchor_zero_fee_commitments();
}

let next_channel_type = get_initial_channel_type(user_config, &eligible_features);

// Note that we can't get `anchor_zero_fee_commitments` type here, which requires zero
// fees, because we downgrade from this channel type first. If there were a superior
// channel type that downgrades to `anchor_zero_fee_commitments`, we'd need to handle
// fee setting differently here. If we proceeded to open a `anchor_zero_fee_commitments`
// channel with non-zero fees, we could produce a non-standard commitment transaction that
// puts us at risk of losing funds. We would expect our peer to reject such a channel
// open, but we don't want to rely on their validation.
assert!(!next_channel_type.supports_anchor_zero_fee_commitments());
Comment thread
carlaKC marked this conversation as resolved.
let conf_target = if next_channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::AnchorChannelFee
} else {
*channel_type = ChannelTypeFeatures::only_static_remote_key();
}
ConfirmationTarget::NonAnchorChannelFee
};
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(conf_target);
funding.channel_transaction_parameters.channel_type_features = next_channel_type;
Comment thread
tankyleo marked this conversation as resolved.

Ok(())
}

Expand DownExpand Up@@ -5262,6 +5296,15 @@ impl<SP: Deref> FundedChannel<SP> where
feerate_per_kw: u32, cur_feerate_per_kw: Option<u32>, logger: &L
) -> Result<(), ChannelError> where F::Target: FeeEstimator, L::Target: Logger,
{
if channel_type.supports_anchor_zero_fee_commitments() {
if feerate_per_kw != 0 {
let err = "Zero Fee Channels must never attempt to use a fee".to_owned();
return Err(ChannelError::close(err));
} else {
return Ok(());
}
}

let lower_limit_conf_target = if channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee
} else {
Expand DownExpand Up@@ -9893,13 +9936,16 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannel` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannel, ()>
where
F::Target: FeeEstimator,
L::Target: Logger,
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
self.get_open_channel(chain_hash, logger).ok_or(())
}

Expand DownExpand Up@@ -10078,8 +10124,9 @@ pub(super) fn channel_type_from_open_channel(

// We only support the channel types defined by the `ChannelManager` in
// `provided_channel_type_features`. The channel type must always support
// `static_remote_key`.
if !channel_type.requires_static_remote_key() {
// `static_remote_key`, either implicitly with `option_zero_fee_commitments`
// or explicitly.
if !channel_type.requires_static_remote_key() && !channel_type.requires_anchor_zero_fee_commitments() {
return Err(ChannelError::close("Channel Type was not understood - we require static remote key".to_owned()));
}
// Make sure we support all of the features behind the channel type.
Expand DownExpand Up@@ -10405,12 +10452,15 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannelV2` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannelV2, ()>
where
F::Target: FeeEstimator
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
Ok(self.get_open_channel_v2(chain_hash))
}

Expand DownExpand Up@@ -10663,10 +10713,21 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures)
ret.set_scid_privacy_required();
}

// Optionally, if the user would like to negotiate the `anchors_zero_fee_htlc_tx` option, we
// set it now. If they don't understand it, we'll fall back to our default of
// `only_static_remotekey`.
if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
// Optionally, if the user would like to negotiate `option_zero_fee_commitments` we set it now.
// If they don't understand it (or we don't want it), we check the same conditions for
// `option_anchors_zero_fee_htlc_tx`. The counterparty can still refuse the channel and we'll
// try to fall back (all the way to `only_static_remotekey`).
#[cfg(not(test))]
let negotiate_zero_fee_commitments = false;

#[cfg(test)]
let negotiate_zero_fee_commitments = config.channel_handshake_config.negotiate_anchor_zero_fee_commitments;

if negotiate_zero_fee_commitments && their_features.supports_anchor_zero_fee_commitments() {
ret.set_anchor_zero_fee_commitments_required();
// `option_static_remote_key` is assumed by `option_zero_fee_commitments`.
ret.clear_static_remote_key();
} else if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
their_features.supports_anchors_zero_fee_htlc_tx() {
ret.set_anchors_zero_fee_htlc_tx_required();
}
Expand DownExpand Up@@ -13267,6 +13328,45 @@ mod tests {
fn test_supports_anchors_zero_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_htlc_tx`, it is the
// resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments`, it is
// the resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;
Comment thread
tankyleo marked this conversation as resolved.

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments_and_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments` and
// `anchors_zero_fee_htlc_tx`, the resulting `channel_type` is
// `anchors_zero_fee_commitments`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

fn do_test_supports_channel_type(config: UserConfig, expected_channel_type: ChannelTypeFeatures) {
let secp_ctx = Secp256k1::new();
let fee_estimator = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
let network = Network::Testnet;
Expand All@@ -13276,21 +13376,13 @@ mod tests {
let node_id_a = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[1; 32]).unwrap());
let node_id_b = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[2; 32]).unwrap());

let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

// It is not enough for just the initiator to signal `option_anchors_zero_fee_htlc_tx`, both
// need to signal it.
// Assert that we get `static_remotekey` when no custom config is negotiated.
let channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
&channelmanager::provided_init_features(&UserConfig::default()), 10000000, 100000, 42,
&config, 0, 42, None, &logger
).unwrap();
assert!(!channel_a.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx());

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();
assert_eq!(channel_a.funding.get_channel_type(), &ChannelTypeFeatures::only_static_remote_key());

let mut channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
Expand All@@ -13307,6 +13399,14 @@ mod tests {

assert_eq!(channel_a.funding.get_channel_type(), &expected_channel_type);
assert_eq!(channel_b.funding.get_channel_type(), &expected_channel_type);

if expected_channel_type.supports_anchor_zero_fee_commitments() {
assert_eq!(channel_a.context.feerate_per_kw, 0);
assert_eq!(channel_b.context.feerate_per_kw, 0);
} else {
assert_ne!(channel_a.context.feerate_per_kw, 0);
assert_ne!(channel_b.context.feerate_per_kw, 0);
}
}

#[test]
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
9 changes: 9 additions & 0 deletions lightning-types/src/features.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1024,6 +1024,15 @@ impl ChannelTypeFeatures {
<sealed::ChannelTypeContext as sealed::AnchorsZeroFeeHtlcTx>::set_required_bit(&mut ret);
ret
}

/// Constructs a ChannelTypeFeatures with zero fee commitment anchors support.
pub fn anchors_zero_fee_commitments() -> Self {
let mut ret = Self::empty();
<sealed::ChannelTypeContext as sealed::AnchorZeroFeeCommitments>::set_required_bit(
&mut ret,
);
ret
}
}

impl<T: sealed::Context> Features<T> {
Expand Down
9 changes: 7 additions & 2 deletions lightning/src/ln/chan_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,8 +58,13 @@ use crate::prelude::*;
/// 483 for non-zero-fee-commitment channels and 114 for zero-fee-commitment channels.
///
/// Actual maximums can be set equal to or below this value by each channel participant.
pub fn max_htlcs(_channel_type: &ChannelTypeFeatures) -> u16 {
483
pub fn max_htlcs(channel_type: &ChannelTypeFeatures) -> u16 {
if channel_type.supports_anchor_zero_fee_commitments() {
// TRUC restricts the size of our commitment transactions to 10K vB rather than 100K vB
114
} else {
483
}
}
/// The weight of a BIP141 witnessScript for a BOLT3's "offered HTLC output" on a commitment transaction, non-anchor variant.
pub const OFFERED_HTLC_SCRIPT_WEIGHT: usize = 133;
Expand Down
188 changes: 144 additions & 44 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1557,6 +1557,7 @@ impl<SP: Deref> Channel<SP> where

pub fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<Option<OpenChannelMessage>, ()>
where
F::Target: FeeEstimator,
Expand All@@ -1567,13 +1568,17 @@ impl<SP: Deref> Channel<SP> where
ChannelPhase::Funded(_) => Ok(None),
ChannelPhase::UnfundedOutboundV1(chan) => {
let logger = WithChannelContext::from(logger, &chan.context, None);
chan.maybe_handle_error_without_close(chain_hash, fee_estimator, &&logger)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, &&logger, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V1(msg)))
},
ChannelPhase::UnfundedInboundV1(_) => Ok(None),
ChannelPhase::UnfundedV2(chan) => {
if chan.funding.is_outbound() {
chan.maybe_handle_error_without_close(chain_hash, fee_estimator)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V2(msg)))
} else {
Ok(None)
Expand DownExpand Up@@ -3072,12 +3077,18 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
debug_assert!(!channel_type.supports_any_optional_bits());
debug_assert!(!channel_type.requires_unknown_bits_from(&channelmanager::provided_channel_type_features(&config)));

let (commitment_conf_target, anchor_outputs_value_msat) = if channel_type.supports_anchors_zero_fee_htlc_tx() {
(ConfirmationTarget::AnchorChannelFee, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
(ConfirmationTarget::NonAnchorChannelFee, 0)
};
let commitment_feerate = fee_estimator.bounded_sat_per_1000_weight(commitment_conf_target);
let (commitment_feerate, anchor_outputs_value_msat) =
if channel_type.supports_anchor_zero_fee_commitments() {
(0, 0)
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee);
(feerate, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
(feerate, 0)
};
Comment thread
tankyleo marked this conversation as resolved.

let value_to_self_msat = channel_value_satoshis * 1000 - push_msat;
let commitment_tx_fee = commit_tx_fee_sat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type) * 1000;
Expand DownExpand Up@@ -4868,7 +4879,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
/// of the channel type we tried, not of our ability to open any channel at all. We can see if a
/// downgrade of channel features would be possible so that we can still open the channel.
pub(crate) fn maybe_downgrade_channel_features<F: Deref>(
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<(), ()>
where
F::Target: FeeEstimator
Expand All@@ -4885,25 +4897,47 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
// We've exhausted our options
return Err(());
}

// We should never have negotiated `anchors_nonzero_fee_htlc_tx` because it can result in a
// loss of funds.
let channel_type = &funding.channel_transaction_parameters.channel_type_features;
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this assert up because we should never negotiate a non-zero-htlc-anchor channel, and it's a risk of lost funds if we do.

Couldn't see a historical reason why we only checked it if the channel type was supports_anchors_zero_fee_htlc_tx - went back a few commits and couldn't find reasoning. cc @TheBlueMatt


// We support opening a few different types of channels. Try removing our additional
// features one by one until we've either arrived at our default or the counterparty has
// accepted one.
//
// Due to the order below, we may not negotiate `option_anchors_zero_fee_htlc_tx` if the
// counterparty doesn't support `option_scid_privacy`. Since `get_initial_channel_type`
// checks whether the counterparty supports every feature, this would only happen if the
// counterparty is advertising the feature, but rejecting channels proposing the feature for
// whatever reason.
let channel_type = &mut funding.channel_transaction_parameters.channel_type_features;
if channel_type.supports_anchors_zero_fee_htlc_tx() {
channel_type.clear_anchors_zero_fee_htlc_tx();
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());
// accepted one. Features are un-set for the current channel type or any that come before
// it in our order of preference, allowing us to negotiate the "next best" based on the
// counterparty's remaining features per our ranking in `get_initial_channel_type`.
let mut eligible_features = their_features.clone();
if channel_type.supports_anchor_zero_fee_commitments() {
eligible_features.clear_anchor_zero_fee_commitments();
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
eligible_features.clear_anchor_zero_fee_commitments();
eligible_features.clear_anchors_zero_fee_htlc_tx();
} else if channel_type.supports_scid_privacy() {
channel_type.clear_scid_privacy();
eligible_features.clear_scid_privacy();
eligible_features.clear_anchors_zero_fee_htlc_tx();
eligible_features.clear_anchor_zero_fee_commitments();
}

let next_channel_type = get_initial_channel_type(user_config, &eligible_features);

// Note that we can't get `anchor_zero_fee_commitments` type here, which requires zero
// fees, because we downgrade from this channel type first. If there were a superior
// channel type that downgrades to `anchor_zero_fee_commitments`, we'd need to handle
// fee setting differently here. If we proceeded to open a `anchor_zero_fee_commitments`
// channel with non-zero fees, we could produce a non-standard commitment transaction that
// puts us at risk of losing funds. We would expect our peer to reject such a channel
// open, but we don't want to rely on their validation.
assert!(!next_channel_type.supports_anchor_zero_fee_commitments());
Comment thread
carlaKC marked this conversation as resolved.
let conf_target = if next_channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::AnchorChannelFee
} else {
*channel_type = ChannelTypeFeatures::only_static_remote_key();
}
ConfirmationTarget::NonAnchorChannelFee
};
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(conf_target);
funding.channel_transaction_parameters.channel_type_features = next_channel_type;
Comment thread
tankyleo marked this conversation as resolved.

Ok(())
}

Expand DownExpand Up@@ -5262,6 +5296,15 @@ impl<SP: Deref> FundedChannel<SP> where
feerate_per_kw: u32, cur_feerate_per_kw: Option<u32>, logger: &L
) -> Result<(), ChannelError> where F::Target: FeeEstimator, L::Target: Logger,
{
if channel_type.supports_anchor_zero_fee_commitments() {
if feerate_per_kw != 0 {
let err = "Zero Fee Channels must never attempt to use a fee".to_owned();
return Err(ChannelError::close(err));
} else {
return Ok(());
}
}

let lower_limit_conf_target = if channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee
} else {
Expand DownExpand Up@@ -9893,13 +9936,16 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannel` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannel, ()>
where
F::Target: FeeEstimator,
L::Target: Logger,
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
self.get_open_channel(chain_hash, logger).ok_or(())
}

Expand DownExpand Up@@ -10078,8 +10124,9 @@ pub(super) fn channel_type_from_open_channel(

// We only support the channel types defined by the `ChannelManager` in
// `provided_channel_type_features`. The channel type must always support
// `static_remote_key`.
if !channel_type.requires_static_remote_key() {
// `static_remote_key`, either implicitly with `option_zero_fee_commitments`
// or explicitly.
if !channel_type.requires_static_remote_key() && !channel_type.requires_anchor_zero_fee_commitments() {
return Err(ChannelError::close("Channel Type was not understood - we require static remote key".to_owned()));
}
// Make sure we support all of the features behind the channel type.
Expand DownExpand Up@@ -10405,12 +10452,15 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannelV2` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannelV2, ()>
where
F::Target: FeeEstimator
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
Ok(self.get_open_channel_v2(chain_hash))
}

Expand DownExpand Up@@ -10663,10 +10713,21 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures)
ret.set_scid_privacy_required();
}

// Optionally, if the user would like to negotiate the `anchors_zero_fee_htlc_tx` option, we
// set it now. If they don't understand it, we'll fall back to our default of
// `only_static_remotekey`.
if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
// Optionally, if the user would like to negotiate `option_zero_fee_commitments` we set it now.
// If they don't understand it (or we don't want it), we check the same conditions for
// `option_anchors_zero_fee_htlc_tx`. The counterparty can still refuse the channel and we'll
// try to fall back (all the way to `only_static_remotekey`).
#[cfg(not(test))]
let negotiate_zero_fee_commitments = false;

#[cfg(test)]
let negotiate_zero_fee_commitments = config.channel_handshake_config.negotiate_anchor_zero_fee_commitments;

if negotiate_zero_fee_commitments && their_features.supports_anchor_zero_fee_commitments() {
ret.set_anchor_zero_fee_commitments_required();
// `option_static_remote_key` is assumed by `option_zero_fee_commitments`.
ret.clear_static_remote_key();
} else if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
their_features.supports_anchors_zero_fee_htlc_tx() {
ret.set_anchors_zero_fee_htlc_tx_required();
}
Expand DownExpand Up@@ -13267,6 +13328,45 @@ mod tests {
fn test_supports_anchors_zero_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_htlc_tx`, it is the
// resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments`, it is
// the resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;
Comment thread
tankyleo marked this conversation as resolved.

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments_and_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments` and
// `anchors_zero_fee_htlc_tx`, the resulting `channel_type` is
// `anchors_zero_fee_commitments`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

fn do_test_supports_channel_type(config: UserConfig, expected_channel_type: ChannelTypeFeatures) {
let secp_ctx = Secp256k1::new();
let fee_estimator = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
let network = Network::Testnet;
Expand All@@ -13276,21 +13376,13 @@ mod tests {
let node_id_a = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[1; 32]).unwrap());
let node_id_b = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[2; 32]).unwrap());

let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

// It is not enough for just the initiator to signal `option_anchors_zero_fee_htlc_tx`, both
// need to signal it.
// Assert that we get `static_remotekey` when no custom config is negotiated.
let channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
&channelmanager::provided_init_features(&UserConfig::default()), 10000000, 100000, 42,
&config, 0, 42, None, &logger
).unwrap();
assert!(!channel_a.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx());

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();
assert_eq!(channel_a.funding.get_channel_type(), &ChannelTypeFeatures::only_static_remote_key());

let mut channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
Expand All@@ -13307,6 +13399,14 @@ mod tests {

assert_eq!(channel_a.funding.get_channel_type(), &expected_channel_type);
assert_eq!(channel_b.funding.get_channel_type(), &expected_channel_type);

if expected_channel_type.supports_anchor_zero_fee_commitments() {
assert_eq!(channel_a.context.feerate_per_kw, 0);
assert_eq!(channel_b.context.feerate_per_kw, 0);
} else {
assert_ne!(channel_a.context.feerate_per_kw, 0);
assert_ne!(channel_b.context.feerate_per_kw, 0);
}
}

#[test]
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
9 changes: 9 additions & 0 deletions lightning-types/src/features.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1024,6 +1024,15 @@ impl ChannelTypeFeatures {
<sealed::ChannelTypeContext as sealed::AnchorsZeroFeeHtlcTx>::set_required_bit(&mut ret);
ret
}

/// Constructs a ChannelTypeFeatures with zero fee commitment anchors support.
pub fn anchors_zero_fee_commitments() -> Self {
let mut ret = Self::empty();
<sealed::ChannelTypeContext as sealed::AnchorZeroFeeCommitments>::set_required_bit(
&mut ret,
);
ret
}
}

impl<T: sealed::Context> Features<T> {
Expand Down
9 changes: 7 additions & 2 deletions lightning/src/ln/chan_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,8 +58,13 @@ use crate::prelude::*;
/// 483 for non-zero-fee-commitment channels and 114 for zero-fee-commitment channels.
///
/// Actual maximums can be set equal to or below this value by each channel participant.
pub fn max_htlcs(_channel_type: &ChannelTypeFeatures) -> u16 {
483
pub fn max_htlcs(channel_type: &ChannelTypeFeatures) -> u16 {
if channel_type.supports_anchor_zero_fee_commitments() {
// TRUC restricts the size of our commitment transactions to 10K vB rather than 100K vB
114
} else {
483
}
}
/// The weight of a BIP141 witnessScript for a BOLT3's "offered HTLC output" on a commitment transaction, non-anchor variant.
pub const OFFERED_HTLC_SCRIPT_WEIGHT: usize = 133;
Expand Down
188 changes: 144 additions & 44 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1557,6 +1557,7 @@ impl<SP: Deref> Channel<SP> where

pub fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<Option<OpenChannelMessage>, ()>
where
F::Target: FeeEstimator,
Expand All@@ -1567,13 +1568,17 @@ impl<SP: Deref> Channel<SP> where
ChannelPhase::Funded(_) => Ok(None),
ChannelPhase::UnfundedOutboundV1(chan) => {
let logger = WithChannelContext::from(logger, &chan.context, None);
chan.maybe_handle_error_without_close(chain_hash, fee_estimator, &&logger)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, &&logger, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V1(msg)))
},
ChannelPhase::UnfundedInboundV1(_) => Ok(None),
ChannelPhase::UnfundedV2(chan) => {
if chan.funding.is_outbound() {
chan.maybe_handle_error_without_close(chain_hash, fee_estimator)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V2(msg)))
} else {
Ok(None)
Expand DownExpand Up@@ -3072,12 +3077,18 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
debug_assert!(!channel_type.supports_any_optional_bits());
debug_assert!(!channel_type.requires_unknown_bits_from(&channelmanager::provided_channel_type_features(&config)));

let (commitment_conf_target, anchor_outputs_value_msat) = if channel_type.supports_anchors_zero_fee_htlc_tx() {
(ConfirmationTarget::AnchorChannelFee, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
(ConfirmationTarget::NonAnchorChannelFee, 0)
};
let commitment_feerate = fee_estimator.bounded_sat_per_1000_weight(commitment_conf_target);
let (commitment_feerate, anchor_outputs_value_msat) =
if channel_type.supports_anchor_zero_fee_commitments() {
(0, 0)
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee);
(feerate, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
(feerate, 0)
};
Comment thread
tankyleo marked this conversation as resolved.

let value_to_self_msat = channel_value_satoshis * 1000 - push_msat;
let commitment_tx_fee = commit_tx_fee_sat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type) * 1000;
Expand DownExpand Up@@ -4868,7 +4879,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
/// of the channel type we tried, not of our ability to open any channel at all. We can see if a
/// downgrade of channel features would be possible so that we can still open the channel.
pub(crate) fn maybe_downgrade_channel_features<F: Deref>(
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<(), ()>
where
F::Target: FeeEstimator
Expand All@@ -4885,25 +4897,47 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
// We've exhausted our options
return Err(());
}

// We should never have negotiated `anchors_nonzero_fee_htlc_tx` because it can result in a
// loss of funds.
let channel_type = &funding.channel_transaction_parameters.channel_type_features;
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this assert up because we should never negotiate a non-zero-htlc-anchor channel, and it's a risk of lost funds if we do.

Couldn't see a historical reason why we only checked it if the channel type was supports_anchors_zero_fee_htlc_tx - went back a few commits and couldn't find reasoning. cc @TheBlueMatt


// We support opening a few different types of channels. Try removing our additional
// features one by one until we've either arrived at our default or the counterparty has
// accepted one.
//
// Due to the order below, we may not negotiate `option_anchors_zero_fee_htlc_tx` if the
// counterparty doesn't support `option_scid_privacy`. Since `get_initial_channel_type`
// checks whether the counterparty supports every feature, this would only happen if the
// counterparty is advertising the feature, but rejecting channels proposing the feature for
// whatever reason.
let channel_type = &mut funding.channel_transaction_parameters.channel_type_features;
if channel_type.supports_anchors_zero_fee_htlc_tx() {
channel_type.clear_anchors_zero_fee_htlc_tx();
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());
// accepted one. Features are un-set for the current channel type or any that come before
// it in our order of preference, allowing us to negotiate the "next best" based on the
// counterparty's remaining features per our ranking in `get_initial_channel_type`.
let mut eligible_features = their_features.clone();
if channel_type.supports_anchor_zero_fee_commitments() {
eligible_features.clear_anchor_zero_fee_commitments();
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
eligible_features.clear_anchor_zero_fee_commitments();
eligible_features.clear_anchors_zero_fee_htlc_tx();
} else if channel_type.supports_scid_privacy() {
channel_type.clear_scid_privacy();
eligible_features.clear_scid_privacy();
eligible_features.clear_anchors_zero_fee_htlc_tx();
eligible_features.clear_anchor_zero_fee_commitments();
}

let next_channel_type = get_initial_channel_type(user_config, &eligible_features);

// Note that we can't get `anchor_zero_fee_commitments` type here, which requires zero
// fees, because we downgrade from this channel type first. If there were a superior
// channel type that downgrades to `anchor_zero_fee_commitments`, we'd need to handle
// fee setting differently here. If we proceeded to open a `anchor_zero_fee_commitments`
// channel with non-zero fees, we could produce a non-standard commitment transaction that
// puts us at risk of losing funds. We would expect our peer to reject such a channel
// open, but we don't want to rely on their validation.
assert!(!next_channel_type.supports_anchor_zero_fee_commitments());
Comment thread
carlaKC marked this conversation as resolved.
let conf_target = if next_channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::AnchorChannelFee
} else {
*channel_type = ChannelTypeFeatures::only_static_remote_key();
}
ConfirmationTarget::NonAnchorChannelFee
};
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(conf_target);
funding.channel_transaction_parameters.channel_type_features = next_channel_type;
Comment thread
tankyleo marked this conversation as resolved.

Ok(())
}

Expand DownExpand Up@@ -5262,6 +5296,15 @@ impl<SP: Deref> FundedChannel<SP> where
feerate_per_kw: u32, cur_feerate_per_kw: Option<u32>, logger: &L
) -> Result<(), ChannelError> where F::Target: FeeEstimator, L::Target: Logger,
{
if channel_type.supports_anchor_zero_fee_commitments() {
if feerate_per_kw != 0 {
let err = "Zero Fee Channels must never attempt to use a fee".to_owned();
return Err(ChannelError::close(err));
} else {
return Ok(());
}
}

let lower_limit_conf_target = if channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee
} else {
Expand DownExpand Up@@ -9893,13 +9936,16 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannel` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannel, ()>
where
F::Target: FeeEstimator,
L::Target: Logger,
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
self.get_open_channel(chain_hash, logger).ok_or(())
}

Expand DownExpand Up@@ -10078,8 +10124,9 @@ pub(super) fn channel_type_from_open_channel(

// We only support the channel types defined by the `ChannelManager` in
// `provided_channel_type_features`. The channel type must always support
// `static_remote_key`.
if !channel_type.requires_static_remote_key() {
// `static_remote_key`, either implicitly with `option_zero_fee_commitments`
// or explicitly.
if !channel_type.requires_static_remote_key() && !channel_type.requires_anchor_zero_fee_commitments() {
return Err(ChannelError::close("Channel Type was not understood - we require static remote key".to_owned()));
}
// Make sure we support all of the features behind the channel type.
Expand DownExpand Up@@ -10405,12 +10452,15 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannelV2` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannelV2, ()>
where
F::Target: FeeEstimator
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
Ok(self.get_open_channel_v2(chain_hash))
}

Expand DownExpand Up@@ -10663,10 +10713,21 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures)
ret.set_scid_privacy_required();
}

// Optionally, if the user would like to negotiate the `anchors_zero_fee_htlc_tx` option, we
// set it now. If they don't understand it, we'll fall back to our default of
// `only_static_remotekey`.
if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
// Optionally, if the user would like to negotiate `option_zero_fee_commitments` we set it now.
// If they don't understand it (or we don't want it), we check the same conditions for
// `option_anchors_zero_fee_htlc_tx`. The counterparty can still refuse the channel and we'll
// try to fall back (all the way to `only_static_remotekey`).
#[cfg(not(test))]
let negotiate_zero_fee_commitments = false;

#[cfg(test)]
let negotiate_zero_fee_commitments = config.channel_handshake_config.negotiate_anchor_zero_fee_commitments;

if negotiate_zero_fee_commitments && their_features.supports_anchor_zero_fee_commitments() {
ret.set_anchor_zero_fee_commitments_required();
// `option_static_remote_key` is assumed by `option_zero_fee_commitments`.
ret.clear_static_remote_key();
} else if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
their_features.supports_anchors_zero_fee_htlc_tx() {
ret.set_anchors_zero_fee_htlc_tx_required();
}
Expand DownExpand Up@@ -13267,6 +13328,45 @@ mod tests {
fn test_supports_anchors_zero_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_htlc_tx`, it is the
// resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments`, it is
// the resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;
Comment thread
tankyleo marked this conversation as resolved.

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments_and_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments` and
// `anchors_zero_fee_htlc_tx`, the resulting `channel_type` is
// `anchors_zero_fee_commitments`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

fn do_test_supports_channel_type(config: UserConfig, expected_channel_type: ChannelTypeFeatures) {
let secp_ctx = Secp256k1::new();
let fee_estimator = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
let network = Network::Testnet;
Expand All@@ -13276,21 +13376,13 @@ mod tests {
let node_id_a = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[1; 32]).unwrap());
let node_id_b = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[2; 32]).unwrap());

let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

// It is not enough for just the initiator to signal `option_anchors_zero_fee_htlc_tx`, both
// need to signal it.
// Assert that we get `static_remotekey` when no custom config is negotiated.
let channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
&channelmanager::provided_init_features(&UserConfig::default()), 10000000, 100000, 42,
&config, 0, 42, None, &logger
).unwrap();
assert!(!channel_a.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx());

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();
assert_eq!(channel_a.funding.get_channel_type(), &ChannelTypeFeatures::only_static_remote_key());

let mut channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
Expand All@@ -13307,6 +13399,14 @@ mod tests {

assert_eq!(channel_a.funding.get_channel_type(), &expected_channel_type);
assert_eq!(channel_b.funding.get_channel_type(), &expected_channel_type);

if expected_channel_type.supports_anchor_zero_fee_commitments() {
assert_eq!(channel_a.context.feerate_per_kw, 0);
assert_eq!(channel_b.context.feerate_per_kw, 0);
} else {
assert_ne!(channel_a.context.feerate_per_kw, 0);
assert_ne!(channel_b.context.feerate_per_kw, 0);
}
}

#[test]
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
9 changes: 9 additions & 0 deletions lightning-types/src/features.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1024,6 +1024,15 @@ impl ChannelTypeFeatures {
<sealed::ChannelTypeContext as sealed::AnchorsZeroFeeHtlcTx>::set_required_bit(&mut ret);
ret
}

/// Constructs a ChannelTypeFeatures with zero fee commitment anchors support.
pub fn anchors_zero_fee_commitments() -> Self {
let mut ret = Self::empty();
<sealed::ChannelTypeContext as sealed::AnchorZeroFeeCommitments>::set_required_bit(
&mut ret,
);
ret
}
}

impl<T: sealed::Context> Features<T> {
Expand Down
9 changes: 7 additions & 2 deletions lightning/src/ln/chan_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,8 +58,13 @@ use crate::prelude::*;
/// 483 for non-zero-fee-commitment channels and 114 for zero-fee-commitment channels.
///
/// Actual maximums can be set equal to or below this value by each channel participant.
pub fn max_htlcs(_channel_type: &ChannelTypeFeatures) -> u16 {
483
pub fn max_htlcs(channel_type: &ChannelTypeFeatures) -> u16 {
if channel_type.supports_anchor_zero_fee_commitments() {
// TRUC restricts the size of our commitment transactions to 10K vB rather than 100K vB
114
} else {
483
}
}
/// The weight of a BIP141 witnessScript for a BOLT3's "offered HTLC output" on a commitment transaction, non-anchor variant.
pub const OFFERED_HTLC_SCRIPT_WEIGHT: usize = 133;
Expand Down
188 changes: 144 additions & 44 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1557,6 +1557,7 @@ impl<SP: Deref> Channel<SP> where

pub fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<Option<OpenChannelMessage>, ()>
where
F::Target: FeeEstimator,
Expand All@@ -1567,13 +1568,17 @@ impl<SP: Deref> Channel<SP> where
ChannelPhase::Funded(_) => Ok(None),
ChannelPhase::UnfundedOutboundV1(chan) => {
let logger = WithChannelContext::from(logger, &chan.context, None);
chan.maybe_handle_error_without_close(chain_hash, fee_estimator, &&logger)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, &&logger, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V1(msg)))
},
ChannelPhase::UnfundedInboundV1(_) => Ok(None),
ChannelPhase::UnfundedV2(chan) => {
if chan.funding.is_outbound() {
chan.maybe_handle_error_without_close(chain_hash, fee_estimator)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V2(msg)))
} else {
Ok(None)
Expand DownExpand Up@@ -3072,12 +3077,18 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
debug_assert!(!channel_type.supports_any_optional_bits());
debug_assert!(!channel_type.requires_unknown_bits_from(&channelmanager::provided_channel_type_features(&config)));

let (commitment_conf_target, anchor_outputs_value_msat) = if channel_type.supports_anchors_zero_fee_htlc_tx() {
(ConfirmationTarget::AnchorChannelFee, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
(ConfirmationTarget::NonAnchorChannelFee, 0)
};
let commitment_feerate = fee_estimator.bounded_sat_per_1000_weight(commitment_conf_target);
let (commitment_feerate, anchor_outputs_value_msat) =
if channel_type.supports_anchor_zero_fee_commitments() {
(0, 0)
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee);
(feerate, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
(feerate, 0)
};
Comment thread
tankyleo marked this conversation as resolved.

let value_to_self_msat = channel_value_satoshis * 1000 - push_msat;
let commitment_tx_fee = commit_tx_fee_sat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type) * 1000;
Expand DownExpand Up@@ -4868,7 +4879,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
/// of the channel type we tried, not of our ability to open any channel at all. We can see if a
/// downgrade of channel features would be possible so that we can still open the channel.
pub(crate) fn maybe_downgrade_channel_features<F: Deref>(
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<(), ()>
where
F::Target: FeeEstimator
Expand All@@ -4885,25 +4897,47 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
// We've exhausted our options
return Err(());
}

// We should never have negotiated `anchors_nonzero_fee_htlc_tx` because it can result in a
// loss of funds.
let channel_type = &funding.channel_transaction_parameters.channel_type_features;
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this assert up because we should never negotiate a non-zero-htlc-anchor channel, and it's a risk of lost funds if we do.

Couldn't see a historical reason why we only checked it if the channel type was supports_anchors_zero_fee_htlc_tx - went back a few commits and couldn't find reasoning. cc @TheBlueMatt


// We support opening a few different types of channels. Try removing our additional
// features one by one until we've either arrived at our default or the counterparty has
// accepted one.
//
// Due to the order below, we may not negotiate `option_anchors_zero_fee_htlc_tx` if the
// counterparty doesn't support `option_scid_privacy`. Since `get_initial_channel_type`
// checks whether the counterparty supports every feature, this would only happen if the
// counterparty is advertising the feature, but rejecting channels proposing the feature for
// whatever reason.
let channel_type = &mut funding.channel_transaction_parameters.channel_type_features;
if channel_type.supports_anchors_zero_fee_htlc_tx() {
channel_type.clear_anchors_zero_fee_htlc_tx();
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());
// accepted one. Features are un-set for the current channel type or any that come before
// it in our order of preference, allowing us to negotiate the "next best" based on the
// counterparty's remaining features per our ranking in `get_initial_channel_type`.
let mut eligible_features = their_features.clone();
if channel_type.supports_anchor_zero_fee_commitments() {
eligible_features.clear_anchor_zero_fee_commitments();
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
eligible_features.clear_anchor_zero_fee_commitments();
eligible_features.clear_anchors_zero_fee_htlc_tx();
} else if channel_type.supports_scid_privacy() {
channel_type.clear_scid_privacy();
eligible_features.clear_scid_privacy();
eligible_features.clear_anchors_zero_fee_htlc_tx();
eligible_features.clear_anchor_zero_fee_commitments();
}

let next_channel_type = get_initial_channel_type(user_config, &eligible_features);

// Note that we can't get `anchor_zero_fee_commitments` type here, which requires zero
// fees, because we downgrade from this channel type first. If there were a superior
// channel type that downgrades to `anchor_zero_fee_commitments`, we'd need to handle
// fee setting differently here. If we proceeded to open a `anchor_zero_fee_commitments`
// channel with non-zero fees, we could produce a non-standard commitment transaction that
// puts us at risk of losing funds. We would expect our peer to reject such a channel
// open, but we don't want to rely on their validation.
assert!(!next_channel_type.supports_anchor_zero_fee_commitments());
Comment thread
carlaKC marked this conversation as resolved.
let conf_target = if next_channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::AnchorChannelFee
} else {
*channel_type = ChannelTypeFeatures::only_static_remote_key();
}
ConfirmationTarget::NonAnchorChannelFee
};
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(conf_target);
funding.channel_transaction_parameters.channel_type_features = next_channel_type;
Comment thread
tankyleo marked this conversation as resolved.

Ok(())
}

Expand DownExpand Up@@ -5262,6 +5296,15 @@ impl<SP: Deref> FundedChannel<SP> where
feerate_per_kw: u32, cur_feerate_per_kw: Option<u32>, logger: &L
) -> Result<(), ChannelError> where F::Target: FeeEstimator, L::Target: Logger,
{
if channel_type.supports_anchor_zero_fee_commitments() {
if feerate_per_kw != 0 {
let err = "Zero Fee Channels must never attempt to use a fee".to_owned();
return Err(ChannelError::close(err));
} else {
return Ok(());
}
}

let lower_limit_conf_target = if channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee
} else {
Expand DownExpand Up@@ -9893,13 +9936,16 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannel` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannel, ()>
where
F::Target: FeeEstimator,
L::Target: Logger,
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
self.get_open_channel(chain_hash, logger).ok_or(())
}

Expand DownExpand Up@@ -10078,8 +10124,9 @@ pub(super) fn channel_type_from_open_channel(

// We only support the channel types defined by the `ChannelManager` in
// `provided_channel_type_features`. The channel type must always support
// `static_remote_key`.
if !channel_type.requires_static_remote_key() {
// `static_remote_key`, either implicitly with `option_zero_fee_commitments`
// or explicitly.
if !channel_type.requires_static_remote_key() && !channel_type.requires_anchor_zero_fee_commitments() {
return Err(ChannelError::close("Channel Type was not understood - we require static remote key".to_owned()));
}
// Make sure we support all of the features behind the channel type.
Expand DownExpand Up@@ -10405,12 +10452,15 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannelV2` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannelV2, ()>
where
F::Target: FeeEstimator
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
Ok(self.get_open_channel_v2(chain_hash))
}

Expand DownExpand Up@@ -10663,10 +10713,21 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures)
ret.set_scid_privacy_required();
}

// Optionally, if the user would like to negotiate the `anchors_zero_fee_htlc_tx` option, we
// set it now. If they don't understand it, we'll fall back to our default of
// `only_static_remotekey`.
if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
// Optionally, if the user would like to negotiate `option_zero_fee_commitments` we set it now.
// If they don't understand it (or we don't want it), we check the same conditions for
// `option_anchors_zero_fee_htlc_tx`. The counterparty can still refuse the channel and we'll
// try to fall back (all the way to `only_static_remotekey`).
#[cfg(not(test))]
let negotiate_zero_fee_commitments = false;

#[cfg(test)]
let negotiate_zero_fee_commitments = config.channel_handshake_config.negotiate_anchor_zero_fee_commitments;

if negotiate_zero_fee_commitments && their_features.supports_anchor_zero_fee_commitments() {
ret.set_anchor_zero_fee_commitments_required();
// `option_static_remote_key` is assumed by `option_zero_fee_commitments`.
ret.clear_static_remote_key();
} else if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
their_features.supports_anchors_zero_fee_htlc_tx() {
ret.set_anchors_zero_fee_htlc_tx_required();
}
Expand DownExpand Up@@ -13267,6 +13328,45 @@ mod tests {
fn test_supports_anchors_zero_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_htlc_tx`, it is the
// resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments`, it is
// the resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;
Comment thread
tankyleo marked this conversation as resolved.

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments_and_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments` and
// `anchors_zero_fee_htlc_tx`, the resulting `channel_type` is
// `anchors_zero_fee_commitments`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

fn do_test_supports_channel_type(config: UserConfig, expected_channel_type: ChannelTypeFeatures) {
let secp_ctx = Secp256k1::new();
let fee_estimator = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
let network = Network::Testnet;
Expand All@@ -13276,21 +13376,13 @@ mod tests {
let node_id_a = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[1; 32]).unwrap());
let node_id_b = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[2; 32]).unwrap());

let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

// It is not enough for just the initiator to signal `option_anchors_zero_fee_htlc_tx`, both
// need to signal it.
// Assert that we get `static_remotekey` when no custom config is negotiated.
let channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
&channelmanager::provided_init_features(&UserConfig::default()), 10000000, 100000, 42,
&config, 0, 42, None, &logger
).unwrap();
assert!(!channel_a.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx());

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();
assert_eq!(channel_a.funding.get_channel_type(), &ChannelTypeFeatures::only_static_remote_key());

let mut channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
Expand All@@ -13307,6 +13399,14 @@ mod tests {

assert_eq!(channel_a.funding.get_channel_type(), &expected_channel_type);
assert_eq!(channel_b.funding.get_channel_type(), &expected_channel_type);

if expected_channel_type.supports_anchor_zero_fee_commitments() {
assert_eq!(channel_a.context.feerate_per_kw, 0);
assert_eq!(channel_b.context.feerate_per_kw, 0);
} else {
assert_ne!(channel_a.context.feerate_per_kw, 0);
assert_ne!(channel_b.context.feerate_per_kw, 0);
}
}

#[test]
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
9 changes: 9 additions & 0 deletions lightning-types/src/features.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1024,6 +1024,15 @@ impl ChannelTypeFeatures {
<sealed::ChannelTypeContext as sealed::AnchorsZeroFeeHtlcTx>::set_required_bit(&mut ret);
ret
}

/// Constructs a ChannelTypeFeatures with zero fee commitment anchors support.
pub fn anchors_zero_fee_commitments() -> Self {
let mut ret = Self::empty();
<sealed::ChannelTypeContext as sealed::AnchorZeroFeeCommitments>::set_required_bit(
&mut ret,
);
ret
}
}

impl<T: sealed::Context> Features<T> {
Expand Down
9 changes: 7 additions & 2 deletions lightning/src/ln/chan_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,8 +58,13 @@ use crate::prelude::*;
/// 483 for non-zero-fee-commitment channels and 114 for zero-fee-commitment channels.
///
/// Actual maximums can be set equal to or below this value by each channel participant.
pub fn max_htlcs(_channel_type: &ChannelTypeFeatures) -> u16 {
483
pub fn max_htlcs(channel_type: &ChannelTypeFeatures) -> u16 {
if channel_type.supports_anchor_zero_fee_commitments() {
// TRUC restricts the size of our commitment transactions to 10K vB rather than 100K vB
114
} else {
483
}
}
/// The weight of a BIP141 witnessScript for a BOLT3's "offered HTLC output" on a commitment transaction, non-anchor variant.
pub const OFFERED_HTLC_SCRIPT_WEIGHT: usize = 133;
Expand Down
188 changes: 144 additions & 44 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1557,6 +1557,7 @@ impl<SP: Deref> Channel<SP> where

pub fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<Option<OpenChannelMessage>, ()>
where
F::Target: FeeEstimator,
Expand All@@ -1567,13 +1568,17 @@ impl<SP: Deref> Channel<SP> where
ChannelPhase::Funded(_) => Ok(None),
ChannelPhase::UnfundedOutboundV1(chan) => {
let logger = WithChannelContext::from(logger, &chan.context, None);
chan.maybe_handle_error_without_close(chain_hash, fee_estimator, &&logger)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, &&logger, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V1(msg)))
},
ChannelPhase::UnfundedInboundV1(_) => Ok(None),
ChannelPhase::UnfundedV2(chan) => {
if chan.funding.is_outbound() {
chan.maybe_handle_error_without_close(chain_hash, fee_estimator)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V2(msg)))
} else {
Ok(None)
Expand DownExpand Up@@ -3072,12 +3077,18 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
debug_assert!(!channel_type.supports_any_optional_bits());
debug_assert!(!channel_type.requires_unknown_bits_from(&channelmanager::provided_channel_type_features(&config)));

let (commitment_conf_target, anchor_outputs_value_msat) = if channel_type.supports_anchors_zero_fee_htlc_tx() {
(ConfirmationTarget::AnchorChannelFee, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
(ConfirmationTarget::NonAnchorChannelFee, 0)
};
let commitment_feerate = fee_estimator.bounded_sat_per_1000_weight(commitment_conf_target);
let (commitment_feerate, anchor_outputs_value_msat) =
if channel_type.supports_anchor_zero_fee_commitments() {
(0, 0)
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee);
(feerate, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
(feerate, 0)
};
Comment thread
tankyleo marked this conversation as resolved.

let value_to_self_msat = channel_value_satoshis * 1000 - push_msat;
let commitment_tx_fee = commit_tx_fee_sat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type) * 1000;
Expand DownExpand Up@@ -4868,7 +4879,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
/// of the channel type we tried, not of our ability to open any channel at all. We can see if a
/// downgrade of channel features would be possible so that we can still open the channel.
pub(crate) fn maybe_downgrade_channel_features<F: Deref>(
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<(), ()>
where
F::Target: FeeEstimator
Expand All@@ -4885,25 +4897,47 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
// We've exhausted our options
return Err(());
}

// We should never have negotiated `anchors_nonzero_fee_htlc_tx` because it can result in a
// loss of funds.
let channel_type = &funding.channel_transaction_parameters.channel_type_features;
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this assert up because we should never negotiate a non-zero-htlc-anchor channel, and it's a risk of lost funds if we do.

Couldn't see a historical reason why we only checked it if the channel type was supports_anchors_zero_fee_htlc_tx - went back a few commits and couldn't find reasoning. cc @TheBlueMatt


// We support opening a few different types of channels. Try removing our additional
// features one by one until we've either arrived at our default or the counterparty has
// accepted one.
//
// Due to the order below, we may not negotiate `option_anchors_zero_fee_htlc_tx` if the
// counterparty doesn't support `option_scid_privacy`. Since `get_initial_channel_type`
// checks whether the counterparty supports every feature, this would only happen if the
// counterparty is advertising the feature, but rejecting channels proposing the feature for
// whatever reason.
let channel_type = &mut funding.channel_transaction_parameters.channel_type_features;
if channel_type.supports_anchors_zero_fee_htlc_tx() {
channel_type.clear_anchors_zero_fee_htlc_tx();
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());
// accepted one. Features are un-set for the current channel type or any that come before
// it in our order of preference, allowing us to negotiate the "next best" based on the
// counterparty's remaining features per our ranking in `get_initial_channel_type`.
let mut eligible_features = their_features.clone();
if channel_type.supports_anchor_zero_fee_commitments() {
eligible_features.clear_anchor_zero_fee_commitments();
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
eligible_features.clear_anchor_zero_fee_commitments();
eligible_features.clear_anchors_zero_fee_htlc_tx();
} else if channel_type.supports_scid_privacy() {
channel_type.clear_scid_privacy();
eligible_features.clear_scid_privacy();
eligible_features.clear_anchors_zero_fee_htlc_tx();
eligible_features.clear_anchor_zero_fee_commitments();
}

let next_channel_type = get_initial_channel_type(user_config, &eligible_features);

// Note that we can't get `anchor_zero_fee_commitments` type here, which requires zero
// fees, because we downgrade from this channel type first. If there were a superior
// channel type that downgrades to `anchor_zero_fee_commitments`, we'd need to handle
// fee setting differently here. If we proceeded to open a `anchor_zero_fee_commitments`
// channel with non-zero fees, we could produce a non-standard commitment transaction that
// puts us at risk of losing funds. We would expect our peer to reject such a channel
// open, but we don't want to rely on their validation.
assert!(!next_channel_type.supports_anchor_zero_fee_commitments());
Comment thread
carlaKC marked this conversation as resolved.
let conf_target = if next_channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::AnchorChannelFee
} else {
*channel_type = ChannelTypeFeatures::only_static_remote_key();
}
ConfirmationTarget::NonAnchorChannelFee
};
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(conf_target);
funding.channel_transaction_parameters.channel_type_features = next_channel_type;
Comment thread
tankyleo marked this conversation as resolved.

Ok(())
}

Expand DownExpand Up@@ -5262,6 +5296,15 @@ impl<SP: Deref> FundedChannel<SP> where
feerate_per_kw: u32, cur_feerate_per_kw: Option<u32>, logger: &L
) -> Result<(), ChannelError> where F::Target: FeeEstimator, L::Target: Logger,
{
if channel_type.supports_anchor_zero_fee_commitments() {
if feerate_per_kw != 0 {
let err = "Zero Fee Channels must never attempt to use a fee".to_owned();
return Err(ChannelError::close(err));
} else {
return Ok(());
}
}

let lower_limit_conf_target = if channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee
} else {
Expand DownExpand Up@@ -9893,13 +9936,16 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannel` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannel, ()>
where
F::Target: FeeEstimator,
L::Target: Logger,
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
self.get_open_channel(chain_hash, logger).ok_or(())
}

Expand DownExpand Up@@ -10078,8 +10124,9 @@ pub(super) fn channel_type_from_open_channel(

// We only support the channel types defined by the `ChannelManager` in
// `provided_channel_type_features`. The channel type must always support
// `static_remote_key`.
if !channel_type.requires_static_remote_key() {
// `static_remote_key`, either implicitly with `option_zero_fee_commitments`
// or explicitly.
if !channel_type.requires_static_remote_key() && !channel_type.requires_anchor_zero_fee_commitments() {
return Err(ChannelError::close("Channel Type was not understood - we require static remote key".to_owned()));
}
// Make sure we support all of the features behind the channel type.
Expand DownExpand Up@@ -10405,12 +10452,15 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannelV2` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannelV2, ()>
where
F::Target: FeeEstimator
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
Ok(self.get_open_channel_v2(chain_hash))
}

Expand DownExpand Up@@ -10663,10 +10713,21 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures)
ret.set_scid_privacy_required();
}

// Optionally, if the user would like to negotiate the `anchors_zero_fee_htlc_tx` option, we
// set it now. If they don't understand it, we'll fall back to our default of
// `only_static_remotekey`.
if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
// Optionally, if the user would like to negotiate `option_zero_fee_commitments` we set it now.
// If they don't understand it (or we don't want it), we check the same conditions for
// `option_anchors_zero_fee_htlc_tx`. The counterparty can still refuse the channel and we'll
// try to fall back (all the way to `only_static_remotekey`).
#[cfg(not(test))]
let negotiate_zero_fee_commitments = false;

#[cfg(test)]
let negotiate_zero_fee_commitments = config.channel_handshake_config.negotiate_anchor_zero_fee_commitments;

if negotiate_zero_fee_commitments && their_features.supports_anchor_zero_fee_commitments() {
ret.set_anchor_zero_fee_commitments_required();
// `option_static_remote_key` is assumed by `option_zero_fee_commitments`.
ret.clear_static_remote_key();
} else if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
their_features.supports_anchors_zero_fee_htlc_tx() {
ret.set_anchors_zero_fee_htlc_tx_required();
}
Expand DownExpand Up@@ -13267,6 +13328,45 @@ mod tests {
fn test_supports_anchors_zero_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_htlc_tx`, it is the
// resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments`, it is
// the resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;
Comment thread
tankyleo marked this conversation as resolved.

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments_and_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments` and
// `anchors_zero_fee_htlc_tx`, the resulting `channel_type` is
// `anchors_zero_fee_commitments`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

fn do_test_supports_channel_type(config: UserConfig, expected_channel_type: ChannelTypeFeatures) {
let secp_ctx = Secp256k1::new();
let fee_estimator = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
let network = Network::Testnet;
Expand All@@ -13276,21 +13376,13 @@ mod tests {
let node_id_a = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[1; 32]).unwrap());
let node_id_b = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[2; 32]).unwrap());

let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

// It is not enough for just the initiator to signal `option_anchors_zero_fee_htlc_tx`, both
// need to signal it.
// Assert that we get `static_remotekey` when no custom config is negotiated.
let channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
&channelmanager::provided_init_features(&UserConfig::default()), 10000000, 100000, 42,
&config, 0, 42, None, &logger
).unwrap();
assert!(!channel_a.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx());

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();
assert_eq!(channel_a.funding.get_channel_type(), &ChannelTypeFeatures::only_static_remote_key());

let mut channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
Expand All@@ -13307,6 +13399,14 @@ mod tests {

assert_eq!(channel_a.funding.get_channel_type(), &expected_channel_type);
assert_eq!(channel_b.funding.get_channel_type(), &expected_channel_type);

if expected_channel_type.supports_anchor_zero_fee_commitments() {
assert_eq!(channel_a.context.feerate_per_kw, 0);
assert_eq!(channel_b.context.feerate_per_kw, 0);
} else {
assert_ne!(channel_a.context.feerate_per_kw, 0);
assert_ne!(channel_b.context.feerate_per_kw, 0);
}
}

#[test]
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
9 changes: 9 additions & 0 deletions lightning-types/src/features.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1024,6 +1024,15 @@ impl ChannelTypeFeatures {
<sealed::ChannelTypeContext as sealed::AnchorsZeroFeeHtlcTx>::set_required_bit(&mut ret);
ret
}

/// Constructs a ChannelTypeFeatures with zero fee commitment anchors support.
pub fn anchors_zero_fee_commitments() -> Self {
let mut ret = Self::empty();
<sealed::ChannelTypeContext as sealed::AnchorZeroFeeCommitments>::set_required_bit(
&mut ret,
);
ret
}
}

impl<T: sealed::Context> Features<T> {
Expand Down
9 changes: 7 additions & 2 deletions lightning/src/ln/chan_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,8 +58,13 @@ use crate::prelude::*;
/// 483 for non-zero-fee-commitment channels and 114 for zero-fee-commitment channels.
///
/// Actual maximums can be set equal to or below this value by each channel participant.
pub fn max_htlcs(_channel_type: &ChannelTypeFeatures) -> u16 {
483
pub fn max_htlcs(channel_type: &ChannelTypeFeatures) -> u16 {
if channel_type.supports_anchor_zero_fee_commitments() {
// TRUC restricts the size of our commitment transactions to 10K vB rather than 100K vB
114
} else {
483
}
}
/// The weight of a BIP141 witnessScript for a BOLT3's "offered HTLC output" on a commitment transaction, non-anchor variant.
pub const OFFERED_HTLC_SCRIPT_WEIGHT: usize = 133;
Expand Down
188 changes: 144 additions & 44 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1557,6 +1557,7 @@ impl<SP: Deref> Channel<SP> where

pub fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<Option<OpenChannelMessage>, ()>
where
F::Target: FeeEstimator,
Expand All@@ -1567,13 +1568,17 @@ impl<SP: Deref> Channel<SP> where
ChannelPhase::Funded(_) => Ok(None),
ChannelPhase::UnfundedOutboundV1(chan) => {
let logger = WithChannelContext::from(logger, &chan.context, None);
chan.maybe_handle_error_without_close(chain_hash, fee_estimator, &&logger)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, &&logger, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V1(msg)))
},
ChannelPhase::UnfundedInboundV1(_) => Ok(None),
ChannelPhase::UnfundedV2(chan) => {
if chan.funding.is_outbound() {
chan.maybe_handle_error_without_close(chain_hash, fee_estimator)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V2(msg)))
} else {
Ok(None)
Expand DownExpand Up@@ -3072,12 +3077,18 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
debug_assert!(!channel_type.supports_any_optional_bits());
debug_assert!(!channel_type.requires_unknown_bits_from(&channelmanager::provided_channel_type_features(&config)));

let (commitment_conf_target, anchor_outputs_value_msat) = if channel_type.supports_anchors_zero_fee_htlc_tx() {
(ConfirmationTarget::AnchorChannelFee, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
(ConfirmationTarget::NonAnchorChannelFee, 0)
};
let commitment_feerate = fee_estimator.bounded_sat_per_1000_weight(commitment_conf_target);
let (commitment_feerate, anchor_outputs_value_msat) =
if channel_type.supports_anchor_zero_fee_commitments() {
(0, 0)
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee);
(feerate, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
(feerate, 0)
};
Comment thread
tankyleo marked this conversation as resolved.

let value_to_self_msat = channel_value_satoshis * 1000 - push_msat;
let commitment_tx_fee = commit_tx_fee_sat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type) * 1000;
Expand DownExpand Up@@ -4868,7 +4879,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
/// of the channel type we tried, not of our ability to open any channel at all. We can see if a
/// downgrade of channel features would be possible so that we can still open the channel.
pub(crate) fn maybe_downgrade_channel_features<F: Deref>(
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<(), ()>
where
F::Target: FeeEstimator
Expand All@@ -4885,25 +4897,47 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
// We've exhausted our options
return Err(());
}

// We should never have negotiated `anchors_nonzero_fee_htlc_tx` because it can result in a
// loss of funds.
let channel_type = &funding.channel_transaction_parameters.channel_type_features;
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this assert up because we should never negotiate a non-zero-htlc-anchor channel, and it's a risk of lost funds if we do.

Couldn't see a historical reason why we only checked it if the channel type was supports_anchors_zero_fee_htlc_tx - went back a few commits and couldn't find reasoning. cc @TheBlueMatt


// We support opening a few different types of channels. Try removing our additional
// features one by one until we've either arrived at our default or the counterparty has
// accepted one.
//
// Due to the order below, we may not negotiate `option_anchors_zero_fee_htlc_tx` if the
// counterparty doesn't support `option_scid_privacy`. Since `get_initial_channel_type`
// checks whether the counterparty supports every feature, this would only happen if the
// counterparty is advertising the feature, but rejecting channels proposing the feature for
// whatever reason.
let channel_type = &mut funding.channel_transaction_parameters.channel_type_features;
if channel_type.supports_anchors_zero_fee_htlc_tx() {
channel_type.clear_anchors_zero_fee_htlc_tx();
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());
// accepted one. Features are un-set for the current channel type or any that come before
// it in our order of preference, allowing us to negotiate the "next best" based on the
// counterparty's remaining features per our ranking in `get_initial_channel_type`.
let mut eligible_features = their_features.clone();
if channel_type.supports_anchor_zero_fee_commitments() {
eligible_features.clear_anchor_zero_fee_commitments();
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
eligible_features.clear_anchor_zero_fee_commitments();
eligible_features.clear_anchors_zero_fee_htlc_tx();
} else if channel_type.supports_scid_privacy() {
channel_type.clear_scid_privacy();
eligible_features.clear_scid_privacy();
eligible_features.clear_anchors_zero_fee_htlc_tx();
eligible_features.clear_anchor_zero_fee_commitments();
}

let next_channel_type = get_initial_channel_type(user_config, &eligible_features);

// Note that we can't get `anchor_zero_fee_commitments` type here, which requires zero
// fees, because we downgrade from this channel type first. If there were a superior
// channel type that downgrades to `anchor_zero_fee_commitments`, we'd need to handle
// fee setting differently here. If we proceeded to open a `anchor_zero_fee_commitments`
// channel with non-zero fees, we could produce a non-standard commitment transaction that
// puts us at risk of losing funds. We would expect our peer to reject such a channel
// open, but we don't want to rely on their validation.
assert!(!next_channel_type.supports_anchor_zero_fee_commitments());
Comment thread
carlaKC marked this conversation as resolved.
let conf_target = if next_channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::AnchorChannelFee
} else {
*channel_type = ChannelTypeFeatures::only_static_remote_key();
}
ConfirmationTarget::NonAnchorChannelFee
};
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(conf_target);
funding.channel_transaction_parameters.channel_type_features = next_channel_type;
Comment thread
tankyleo marked this conversation as resolved.

Ok(())
}

Expand DownExpand Up@@ -5262,6 +5296,15 @@ impl<SP: Deref> FundedChannel<SP> where
feerate_per_kw: u32, cur_feerate_per_kw: Option<u32>, logger: &L
) -> Result<(), ChannelError> where F::Target: FeeEstimator, L::Target: Logger,
{
if channel_type.supports_anchor_zero_fee_commitments() {
if feerate_per_kw != 0 {
let err = "Zero Fee Channels must never attempt to use a fee".to_owned();
return Err(ChannelError::close(err));
} else {
return Ok(());
}
}

let lower_limit_conf_target = if channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee
} else {
Expand DownExpand Up@@ -9893,13 +9936,16 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannel` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannel, ()>
where
F::Target: FeeEstimator,
L::Target: Logger,
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
self.get_open_channel(chain_hash, logger).ok_or(())
}

Expand DownExpand Up@@ -10078,8 +10124,9 @@ pub(super) fn channel_type_from_open_channel(

// We only support the channel types defined by the `ChannelManager` in
// `provided_channel_type_features`. The channel type must always support
// `static_remote_key`.
if !channel_type.requires_static_remote_key() {
// `static_remote_key`, either implicitly with `option_zero_fee_commitments`
// or explicitly.
if !channel_type.requires_static_remote_key() && !channel_type.requires_anchor_zero_fee_commitments() {
return Err(ChannelError::close("Channel Type was not understood - we require static remote key".to_owned()));
}
// Make sure we support all of the features behind the channel type.
Expand DownExpand Up@@ -10405,12 +10452,15 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannelV2` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannelV2, ()>
where
F::Target: FeeEstimator
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
Ok(self.get_open_channel_v2(chain_hash))
}

Expand DownExpand Up@@ -10663,10 +10713,21 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures)
ret.set_scid_privacy_required();
}

// Optionally, if the user would like to negotiate the `anchors_zero_fee_htlc_tx` option, we
// set it now. If they don't understand it, we'll fall back to our default of
// `only_static_remotekey`.
if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
// Optionally, if the user would like to negotiate `option_zero_fee_commitments` we set it now.
// If they don't understand it (or we don't want it), we check the same conditions for
// `option_anchors_zero_fee_htlc_tx`. The counterparty can still refuse the channel and we'll
// try to fall back (all the way to `only_static_remotekey`).
#[cfg(not(test))]
let negotiate_zero_fee_commitments = false;

#[cfg(test)]
let negotiate_zero_fee_commitments = config.channel_handshake_config.negotiate_anchor_zero_fee_commitments;

if negotiate_zero_fee_commitments && their_features.supports_anchor_zero_fee_commitments() {
ret.set_anchor_zero_fee_commitments_required();
// `option_static_remote_key` is assumed by `option_zero_fee_commitments`.
ret.clear_static_remote_key();
} else if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
their_features.supports_anchors_zero_fee_htlc_tx() {
ret.set_anchors_zero_fee_htlc_tx_required();
}
Expand DownExpand Up@@ -13267,6 +13328,45 @@ mod tests {
fn test_supports_anchors_zero_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_htlc_tx`, it is the
// resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments`, it is
// the resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;
Comment thread
tankyleo marked this conversation as resolved.

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments_and_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments` and
// `anchors_zero_fee_htlc_tx`, the resulting `channel_type` is
// `anchors_zero_fee_commitments`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

fn do_test_supports_channel_type(config: UserConfig, expected_channel_type: ChannelTypeFeatures) {
let secp_ctx = Secp256k1::new();
let fee_estimator = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
let network = Network::Testnet;
Expand All@@ -13276,21 +13376,13 @@ mod tests {
let node_id_a = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[1; 32]).unwrap());
let node_id_b = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[2; 32]).unwrap());

let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

// It is not enough for just the initiator to signal `option_anchors_zero_fee_htlc_tx`, both
// need to signal it.
// Assert that we get `static_remotekey` when no custom config is negotiated.
let channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
&channelmanager::provided_init_features(&UserConfig::default()), 10000000, 100000, 42,
&config, 0, 42, None, &logger
).unwrap();
assert!(!channel_a.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx());

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();
assert_eq!(channel_a.funding.get_channel_type(), &ChannelTypeFeatures::only_static_remote_key());

let mut channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
Expand All@@ -13307,6 +13399,14 @@ mod tests {

assert_eq!(channel_a.funding.get_channel_type(), &expected_channel_type);
assert_eq!(channel_b.funding.get_channel_type(), &expected_channel_type);

if expected_channel_type.supports_anchor_zero_fee_commitments() {
assert_eq!(channel_a.context.feerate_per_kw, 0);
assert_eq!(channel_b.context.feerate_per_kw, 0);
} else {
assert_ne!(channel_a.context.feerate_per_kw, 0);
assert_ne!(channel_b.context.feerate_per_kw, 0);
}
}

#[test]
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
9 changes: 9 additions & 0 deletions lightning-types/src/features.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1024,6 +1024,15 @@ impl ChannelTypeFeatures {
<sealed::ChannelTypeContext as sealed::AnchorsZeroFeeHtlcTx>::set_required_bit(&mut ret);
ret
}

/// Constructs a ChannelTypeFeatures with zero fee commitment anchors support.
pub fn anchors_zero_fee_commitments() -> Self {
let mut ret = Self::empty();
<sealed::ChannelTypeContext as sealed::AnchorZeroFeeCommitments>::set_required_bit(
&mut ret,
);
ret
}
}

impl<T: sealed::Context> Features<T> {
Expand Down
9 changes: 7 additions & 2 deletions lightning/src/ln/chan_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,8 +58,13 @@ use crate::prelude::*;
/// 483 for non-zero-fee-commitment channels and 114 for zero-fee-commitment channels.
///
/// Actual maximums can be set equal to or below this value by each channel participant.
pub fn max_htlcs(_channel_type: &ChannelTypeFeatures) -> u16 {
483
pub fn max_htlcs(channel_type: &ChannelTypeFeatures) -> u16 {
if channel_type.supports_anchor_zero_fee_commitments() {
// TRUC restricts the size of our commitment transactions to 10K vB rather than 100K vB
114
} else {
483
}
}
/// The weight of a BIP141 witnessScript for a BOLT3's "offered HTLC output" on a commitment transaction, non-anchor variant.
pub const OFFERED_HTLC_SCRIPT_WEIGHT: usize = 133;
Expand Down
188 changes: 144 additions & 44 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1557,6 +1557,7 @@ impl<SP: Deref> Channel<SP> where

pub fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<Option<OpenChannelMessage>, ()>
where
F::Target: FeeEstimator,
Expand All@@ -1567,13 +1568,17 @@ impl<SP: Deref> Channel<SP> where
ChannelPhase::Funded(_) => Ok(None),
ChannelPhase::UnfundedOutboundV1(chan) => {
let logger = WithChannelContext::from(logger, &chan.context, None);
chan.maybe_handle_error_without_close(chain_hash, fee_estimator, &&logger)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, &&logger, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V1(msg)))
},
ChannelPhase::UnfundedInboundV1(_) => Ok(None),
ChannelPhase::UnfundedV2(chan) => {
if chan.funding.is_outbound() {
chan.maybe_handle_error_without_close(chain_hash, fee_estimator)
chan.maybe_handle_error_without_close(
chain_hash, fee_estimator, user_config, their_features,
)
.map(|msg| Some(OpenChannelMessage::V2(msg)))
} else {
Ok(None)
Expand DownExpand Up@@ -3072,12 +3077,18 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
debug_assert!(!channel_type.supports_any_optional_bits());
debug_assert!(!channel_type.requires_unknown_bits_from(&channelmanager::provided_channel_type_features(&config)));

let (commitment_conf_target, anchor_outputs_value_msat) = if channel_type.supports_anchors_zero_fee_htlc_tx() {
(ConfirmationTarget::AnchorChannelFee, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
(ConfirmationTarget::NonAnchorChannelFee, 0)
};
let commitment_feerate = fee_estimator.bounded_sat_per_1000_weight(commitment_conf_target);
let (commitment_feerate, anchor_outputs_value_msat) =
if channel_type.supports_anchor_zero_fee_commitments() {
(0, 0)
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::AnchorChannelFee);
(feerate, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000)
} else {
let feerate = fee_estimator
.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
(feerate, 0)
};
Comment thread
tankyleo marked this conversation as resolved.

let value_to_self_msat = channel_value_satoshis * 1000 - push_msat;
let commitment_tx_fee = commit_tx_fee_sat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type) * 1000;
Expand DownExpand Up@@ -4868,7 +4879,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
/// of the channel type we tried, not of our ability to open any channel at all. We can see if a
/// downgrade of channel features would be possible so that we can still open the channel.
pub(crate) fn maybe_downgrade_channel_features<F: Deref>(
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, funding: &mut FundingScope, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<(), ()>
where
F::Target: FeeEstimator
Expand All@@ -4885,25 +4897,47 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
// We've exhausted our options
return Err(());
}

// We should never have negotiated `anchors_nonzero_fee_htlc_tx` because it can result in a
// loss of funds.
let channel_type = &funding.channel_transaction_parameters.channel_type_features;
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this assert up because we should never negotiate a non-zero-htlc-anchor channel, and it's a risk of lost funds if we do.

Couldn't see a historical reason why we only checked it if the channel type was supports_anchors_zero_fee_htlc_tx - went back a few commits and couldn't find reasoning. cc @TheBlueMatt


// We support opening a few different types of channels. Try removing our additional
// features one by one until we've either arrived at our default or the counterparty has
// accepted one.
//
// Due to the order below, we may not negotiate `option_anchors_zero_fee_htlc_tx` if the
// counterparty doesn't support `option_scid_privacy`. Since `get_initial_channel_type`
// checks whether the counterparty supports every feature, this would only happen if the
// counterparty is advertising the feature, but rejecting channels proposing the feature for
// whatever reason.
let channel_type = &mut funding.channel_transaction_parameters.channel_type_features;
if channel_type.supports_anchors_zero_fee_htlc_tx() {
channel_type.clear_anchors_zero_fee_htlc_tx();
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
assert!(!channel_type.supports_anchors_nonzero_fee_htlc_tx());
// accepted one. Features are un-set for the current channel type or any that come before
// it in our order of preference, allowing us to negotiate the "next best" based on the
// counterparty's remaining features per our ranking in `get_initial_channel_type`.
let mut eligible_features = their_features.clone();
if channel_type.supports_anchor_zero_fee_commitments() {
eligible_features.clear_anchor_zero_fee_commitments();
} else if channel_type.supports_anchors_zero_fee_htlc_tx() {
eligible_features.clear_anchor_zero_fee_commitments();
eligible_features.clear_anchors_zero_fee_htlc_tx();
} else if channel_type.supports_scid_privacy() {
channel_type.clear_scid_privacy();
eligible_features.clear_scid_privacy();
eligible_features.clear_anchors_zero_fee_htlc_tx();
eligible_features.clear_anchor_zero_fee_commitments();
}

let next_channel_type = get_initial_channel_type(user_config, &eligible_features);

// Note that we can't get `anchor_zero_fee_commitments` type here, which requires zero
// fees, because we downgrade from this channel type first. If there were a superior
// channel type that downgrades to `anchor_zero_fee_commitments`, we'd need to handle
// fee setting differently here. If we proceeded to open a `anchor_zero_fee_commitments`
// channel with non-zero fees, we could produce a non-standard commitment transaction that
// puts us at risk of losing funds. We would expect our peer to reject such a channel
// open, but we don't want to rely on their validation.
assert!(!next_channel_type.supports_anchor_zero_fee_commitments());
Comment thread
carlaKC marked this conversation as resolved.
let conf_target = if next_channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::AnchorChannelFee
} else {
*channel_type = ChannelTypeFeatures::only_static_remote_key();
}
ConfirmationTarget::NonAnchorChannelFee
};
self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(conf_target);
funding.channel_transaction_parameters.channel_type_features = next_channel_type;
Comment thread
tankyleo marked this conversation as resolved.

Ok(())
}

Expand DownExpand Up@@ -5262,6 +5296,15 @@ impl<SP: Deref> FundedChannel<SP> where
feerate_per_kw: u32, cur_feerate_per_kw: Option<u32>, logger: &L
) -> Result<(), ChannelError> where F::Target: FeeEstimator, L::Target: Logger,
{
if channel_type.supports_anchor_zero_fee_commitments() {
if feerate_per_kw != 0 {
let err = "Zero Fee Channels must never attempt to use a fee".to_owned();
return Err(ChannelError::close(err));
} else {
return Ok(());
}
}

let lower_limit_conf_target = if channel_type.supports_anchors_zero_fee_htlc_tx() {
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee
} else {
Expand DownExpand Up@@ -9893,13 +9936,16 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannel` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref, L: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannel, ()>
where
F::Target: FeeEstimator,
L::Target: Logger,
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
self.get_open_channel(chain_hash, logger).ok_or(())
}

Expand DownExpand Up@@ -10078,8 +10124,9 @@ pub(super) fn channel_type_from_open_channel(

// We only support the channel types defined by the `ChannelManager` in
// `provided_channel_type_features`. The channel type must always support
// `static_remote_key`.
if !channel_type.requires_static_remote_key() {
// `static_remote_key`, either implicitly with `option_zero_fee_commitments`
// or explicitly.
if !channel_type.requires_static_remote_key() && !channel_type.requires_anchor_zero_fee_commitments() {
return Err(ChannelError::close("Channel Type was not understood - we require static remote key".to_owned()));
}
// Make sure we support all of the features behind the channel type.
Expand DownExpand Up@@ -10405,12 +10452,15 @@ impl<SP: Deref> PendingV2Channel<SP> where SP::Target: SignerProvider {
/// not of our ability to open any channel at all. Thus, on error, we should first call this
/// and see if we get a new `OpenChannelV2` message, otherwise the channel is failed.
pub(crate) fn maybe_handle_error_without_close<F: Deref>(
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>
&mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator<F>,
user_config: &UserConfig, their_features: &InitFeatures,
) -> Result<msgs::OpenChannelV2, ()>
where
F::Target: FeeEstimator
{
self.context.maybe_downgrade_channel_features(&mut self.funding, fee_estimator)?;
self.context.maybe_downgrade_channel_features(
&mut self.funding, fee_estimator, user_config, their_features,
)?;
Ok(self.get_open_channel_v2(chain_hash))
}

Expand DownExpand Up@@ -10663,10 +10713,21 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures)
ret.set_scid_privacy_required();
}

// Optionally, if the user would like to negotiate the `anchors_zero_fee_htlc_tx` option, we
// set it now. If they don't understand it, we'll fall back to our default of
// `only_static_remotekey`.
if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
// Optionally, if the user would like to negotiate `option_zero_fee_commitments` we set it now.
// If they don't understand it (or we don't want it), we check the same conditions for
// `option_anchors_zero_fee_htlc_tx`. The counterparty can still refuse the channel and we'll
// try to fall back (all the way to `only_static_remotekey`).
#[cfg(not(test))]
let negotiate_zero_fee_commitments = false;

#[cfg(test)]
let negotiate_zero_fee_commitments = config.channel_handshake_config.negotiate_anchor_zero_fee_commitments;

if negotiate_zero_fee_commitments && their_features.supports_anchor_zero_fee_commitments() {
ret.set_anchor_zero_fee_commitments_required();
// `option_static_remote_key` is assumed by `option_zero_fee_commitments`.
ret.clear_static_remote_key();
} else if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
their_features.supports_anchors_zero_fee_htlc_tx() {
ret.set_anchors_zero_fee_htlc_tx_required();
}
Expand DownExpand Up@@ -13267,6 +13328,45 @@ mod tests {
fn test_supports_anchors_zero_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_htlc_tx`, it is the
// resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments`, it is
// the resulting `channel_type`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;
Comment thread
tankyleo marked this conversation as resolved.

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

#[test]
fn test_supports_zero_fee_commitments_and_htlc_tx_fee() {
// Tests that if both sides support and negotiate `anchors_zero_fee_commitments` and
// `anchors_zero_fee_htlc_tx`, the resulting `channel_type` is
// `anchors_zero_fee_commitments`.
let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_anchor_zero_fee_commitments_required();

do_test_supports_channel_type(config, expected_channel_type)
}

fn do_test_supports_channel_type(config: UserConfig, expected_channel_type: ChannelTypeFeatures) {
let secp_ctx = Secp256k1::new();
let fee_estimator = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
let network = Network::Testnet;
Expand All@@ -13276,21 +13376,13 @@ mod tests {
let node_id_a = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[1; 32]).unwrap());
let node_id_b = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[2; 32]).unwrap());

let mut config = UserConfig::default();
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;

// It is not enough for just the initiator to signal `option_anchors_zero_fee_htlc_tx`, both
// need to signal it.
// Assert that we get `static_remotekey` when no custom config is negotiated.
let channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
&channelmanager::provided_init_features(&UserConfig::default()), 10000000, 100000, 42,
&config, 0, 42, None, &logger
).unwrap();
assert!(!channel_a.funding.get_channel_type().supports_anchors_zero_fee_htlc_tx());

let mut expected_channel_type = ChannelTypeFeatures::empty();
expected_channel_type.set_static_remote_key_required();
expected_channel_type.set_anchors_zero_fee_htlc_tx_required();
assert_eq!(channel_a.funding.get_channel_type(), &ChannelTypeFeatures::only_static_remote_key());

let mut channel_a = OutboundV1Channel::<&TestKeysInterface>::new(
&fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
Expand All@@ -13307,6 +13399,14 @@ mod tests {

assert_eq!(channel_a.funding.get_channel_type(), &expected_channel_type);
assert_eq!(channel_b.funding.get_channel_type(), &expected_channel_type);

if expected_channel_type.supports_anchor_zero_fee_commitments() {
assert_eq!(channel_a.context.feerate_per_kw, 0);
assert_eq!(channel_b.context.feerate_per_kw, 0);
} else {
assert_ne!(channel_a.context.feerate_per_kw, 0);
assert_ne!(channel_b.context.feerate_per_kw, 0);
}
}

#[test]
Expand Down
Loading