Skip to content

Commit 835b67b

Browse files
committed
Check reserves after funding_contribution_satoshis is applied
This applies to both `splice_init` and `splice_ack` messages. From BOLT 2: ``` - If `funding_contribution_satoshis` is negative and its absolute value is greater than the sending node's current channel balance: - MUST send a `warning` and close the connection or send an `error` and fail the channel. ``` Further down also: ``` If a side does not meet the reserve requirements, that's OK: but if they take funds out of the channel, they must ensure that they do meet them. If your peer adds a massive amount to the channel, then you only have to add more reserve if you want to contribute to the splice (and you can use `tx_remove_output` and/or `tx_remove_input` part-way through if this happens). ```
1 parent 345f5db commit 835b67b

File tree

1 file changed

+74
-9
lines changed

1 file changed

+74
-9
lines changed

lightning/src/ln/channel.rs

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11002,6 +11002,11 @@ where
1100211002
their_funding_contribution.to_sat(),
1100311003
);
1100411004

11005+
// While we check that the remote can afford the HTLCs, anchors, and the reserve
11006+
// after creating the new `FundingScope` below, we MUST do a basic check here to
11007+
// make sure that their funding contribution doesn't completely exhaust their
11008+
// balance because an invariant of `FundingScope` is that `value_to_self_msat`
11009+
// MUST be smaller than or equal to `channel_value_satoshis * 1000`.
1100511010
if post_channel_balance.is_none() {
1100611011
return Err(ChannelError::WarnAndDisconnect(format!(
1100711012
"Channel {} cannot be spliced out; their {} contribution exhausts their channel balance: {}",
@@ -11019,15 +11024,7 @@ where
1101911024
counterparty_funding_pubkey,
1102011025
);
1102111026

11022-
// TODO(splicing): Check that channel balance does not go below the channel reserve
11023-
11024-
// Note on channel reserve requirement pre-check: as the splice acceptor does not contribute,
11025-
// it can't go below reserve, therefore no pre-check is done here.
11026-
11027-
// TODO(splicing): Early check for reserve requirement
11028-
11029-
// TODO(splicing): Pre-check for reserve requirement
11030-
// (Note: It should also be checked later at tx_complete)
11027+
self.validate_their_funding_contribution_reserve(&splice_funding)?;
1103111028

1103211029
Ok(splice_funding)
1103311030
}
@@ -11191,6 +11188,74 @@ where
1119111188
)
1119211189
}
1119311190

11191+
/// Used to validate a negative `funding_contribution_satoshis` in `splice_init` and `splice_ack` messages.
11192+
#[cfg(splicing)]
11193+
fn validate_their_funding_contribution_reserve(
11194+
&self, splice_funding: &FundingScope,
11195+
) -> Result<(), ChannelError> {
11196+
// We don't care about the exact value of `dust_exposure_limiting_feerate` here as
11197+
// we do not validate dust exposure below, but we want to avoid triggering a debug
11198+
// assert.
11199+
//
11200+
// TODO: clean this up here and elsewhere.
11201+
let dust_exposure_limiting_feerate =
11202+
if splice_funding.get_channel_type().supports_anchor_zero_fee_commitments() {
11203+
None
11204+
} else {
11205+
Some(self.context.feerate_per_kw)
11206+
};
11207+
// This *should* have no effect because no HTLC updates should be pending, but even if it does,
11208+
// the result may be a failed negotiation (and not a force-close), so we choose to include them.
11209+
let include_remote_unknown_htlcs = true;
11210+
// Make sure that that the funder of the channel can pay the transaction fees for an additional
11211+
// nondust HTLC on the channel.
11212+
let addl_nondust_htlc_count = 1;
11213+
11214+
let validate_stats = |stats: NextCommitmentStats| {
11215+
let (_, remote_balance_incl_fee_msat) = stats.get_balances_including_fee_msat();
11216+
let splice_remote_balance_msat = remote_balance_incl_fee_msat
11217+
.ok_or(ChannelError::WarnAndDisconnect(format!("Remote balance does not cover the sum of HTLCs, anchors, and commitment transaction fee")))?;
11218+
11219+
// Check if the remote's new balance is under the specified reserve
11220+
if splice_remote_balance_msat
11221+
< splice_funding.holder_selected_channel_reserve_satoshis * 1000
11222+
{
11223+
return Err(ChannelError::WarnAndDisconnect(format!(
11224+
"Remote balance below reserve mandated by holder: {} vs {}",
11225+
splice_remote_balance_msat,
11226+
splice_funding.holder_selected_channel_reserve_satoshis * 1000,
11227+
)));
11228+
}
11229+
Ok(())
11230+
};
11231+
11232+
// Reserve check on local commitment transaction
11233+
11234+
let splice_local_commitment_stats = self.context.get_next_local_commitment_stats(
11235+
splice_funding,
11236+
None, // htlc_candidate
11237+
include_remote_unknown_htlcs,
11238+
addl_nondust_htlc_count,
11239+
self.context.feerate_per_kw,
11240+
dust_exposure_limiting_feerate,
11241+
);
11242+
11243+
validate_stats(splice_local_commitment_stats)?;
11244+
11245+
// Reserve check on remote commitment transaction
11246+
11247+
let splice_remote_commitment_stats = self.context.get_next_remote_commitment_stats(
11248+
splice_funding,
11249+
None, // htlc_candidate
11250+
include_remote_unknown_htlcs,
11251+
addl_nondust_htlc_count,
11252+
self.context.feerate_per_kw,
11253+
dust_exposure_limiting_feerate,
11254+
);
11255+
11256+
validate_stats(splice_remote_commitment_stats)
11257+
}
11258+
1119411259
#[cfg(splicing)]
1119511260
pub fn splice_locked<NS: Deref, L: Deref>(
1119611261
&mut self, msg: &msgs::SpliceLocked, node_signer: &NS, chain_hash: ChainHash,

0 commit comments

Comments
 (0)