@@ -11147,6 +11147,11 @@ where
11147
11147
their_funding_contribution.to_sat(),
11148
11148
);
11149
11149
11150
+ // While we check that the remote can afford the HTLCs, anchors, and the reserve
11151
+ // after creating the new `FundingScope` below, we MUST do a basic check here to
11152
+ // make sure that their funding contribution doesn't completely exhaust their
11153
+ // balance because an invariant of `FundingScope` is that `value_to_self_msat`
11154
+ // MUST be smaller than or equal to `channel_value_satoshis * 1000`.
11150
11155
if post_channel_balance.is_none() {
11151
11156
return Err(ChannelError::WarnAndDisconnect(format!(
11152
11157
"Channel {} cannot be spliced out; their {} contribution exhausts their channel balance: {}",
@@ -11164,15 +11169,7 @@ where
11164
11169
counterparty_funding_pubkey,
11165
11170
);
11166
11171
11167
- // TODO(splicing): Check that channel balance does not go below the channel reserve
11168
-
11169
- // Note on channel reserve requirement pre-check: as the splice acceptor does not contribute,
11170
- // it can't go below reserve, therefore no pre-check is done here.
11171
-
11172
- // TODO(splicing): Early check for reserve requirement
11173
-
11174
- // TODO(splicing): Pre-check for reserve requirement
11175
- // (Note: It should also be checked later at tx_complete)
11172
+ self.validate_their_funding_contribution_reserve(&splice_funding)?;
11176
11173
11177
11174
Ok(splice_funding)
11178
11175
}
@@ -11336,6 +11333,74 @@ where
11336
11333
)
11337
11334
}
11338
11335
11336
+ /// Used to validate a negative `funding_contribution_satoshis` in `splice_init` and `splice_ack` messages.
11337
+ #[cfg(splicing)]
11338
+ fn validate_their_funding_contribution_reserve(
11339
+ &self, splice_funding: &FundingScope,
11340
+ ) -> Result<(), ChannelError> {
11341
+ // We don't care about the exact value of `dust_exposure_limiting_feerate` here as
11342
+ // we do not validate dust exposure below, but we want to avoid triggering a debug
11343
+ // assert.
11344
+ //
11345
+ // TODO: clean this up here and elsewhere.
11346
+ let dust_exposure_limiting_feerate =
11347
+ if splice_funding.get_channel_type().supports_anchor_zero_fee_commitments() {
11348
+ None
11349
+ } else {
11350
+ Some(self.context.feerate_per_kw)
11351
+ };
11352
+ // This *should* have no effect because no HTLC updates should be pending, but even if it does,
11353
+ // the result may be a failed negotiation (and not a force-close), so we choose to include them.
11354
+ let include_remote_unknown_htlcs = true;
11355
+ // Make sure that that the funder of the channel can pay the transaction fees for an additional
11356
+ // nondust HTLC on the channel.
11357
+ let addl_nondust_htlc_count = 1;
11358
+
11359
+ let validate_stats = |stats: NextCommitmentStats| {
11360
+ let (_, remote_balance_incl_fee_msat) = stats.get_balances_including_fee_msat();
11361
+ let splice_remote_balance_msat = remote_balance_incl_fee_msat
11362
+ .ok_or(ChannelError::WarnAndDisconnect(format!("Remote balance does not cover the sum of HTLCs, anchors, and commitment transaction fee")))?;
11363
+
11364
+ // Check if the remote's new balance is under the specified reserve
11365
+ if splice_remote_balance_msat
11366
+ < splice_funding.holder_selected_channel_reserve_satoshis * 1000
11367
+ {
11368
+ return Err(ChannelError::WarnAndDisconnect(format!(
11369
+ "Remote balance below reserve mandated by holder: {} vs {}",
11370
+ splice_remote_balance_msat,
11371
+ splice_funding.holder_selected_channel_reserve_satoshis * 1000,
11372
+ )));
11373
+ }
11374
+ Ok(())
11375
+ };
11376
+
11377
+ // Reserve check on local commitment transaction
11378
+
11379
+ let splice_local_commitment_stats = self.context.get_next_local_commitment_stats(
11380
+ splice_funding,
11381
+ None, // htlc_candidate
11382
+ include_remote_unknown_htlcs,
11383
+ addl_nondust_htlc_count,
11384
+ self.context.feerate_per_kw,
11385
+ dust_exposure_limiting_feerate,
11386
+ );
11387
+
11388
+ validate_stats(splice_local_commitment_stats)?;
11389
+
11390
+ // Reserve check on remote commitment transaction
11391
+
11392
+ let splice_remote_commitment_stats = self.context.get_next_remote_commitment_stats(
11393
+ splice_funding,
11394
+ None, // htlc_candidate
11395
+ include_remote_unknown_htlcs,
11396
+ addl_nondust_htlc_count,
11397
+ self.context.feerate_per_kw,
11398
+ dust_exposure_limiting_feerate,
11399
+ );
11400
+
11401
+ validate_stats(splice_remote_commitment_stats)
11402
+ }
11403
+
11339
11404
#[cfg(splicing)]
11340
11405
pub fn splice_locked<NS: Deref, L: Deref>(
11341
11406
&mut self, msg: &msgs::SpliceLocked, node_signer: &NS, chain_hash: ChainHash,
0 commit comments