@@ -11002,6 +11002,11 @@ where
11002
11002
their_funding_contribution.to_sat(),
11003
11003
);
11004
11004
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`.
11005
11010
if post_channel_balance.is_none() {
11006
11011
return Err(ChannelError::WarnAndDisconnect(format!(
11007
11012
"Channel {} cannot be spliced out; their {} contribution exhausts their channel balance: {}",
@@ -11019,15 +11024,7 @@ where
11019
11024
counterparty_funding_pubkey,
11020
11025
);
11021
11026
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)?;
11031
11028
11032
11029
Ok(splice_funding)
11033
11030
}
@@ -11191,6 +11188,74 @@ where
11191
11188
)
11192
11189
}
11193
11190
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
+
11194
11259
#[cfg(splicing)]
11195
11260
pub fn splice_locked<NS: Deref, L: Deref>(
11196
11261
&mut self, msg: &msgs::SpliceLocked, node_signer: &NS, chain_hash: ChainHash,
0 commit comments