Skip to content

Commit 9798f7a

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 38f8630 commit 9798f7a

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
@@ -11147,6 +11147,11 @@ where
1114711147
their_funding_contribution.to_sat(),
1114811148
);
1114911149

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`.
1115011155
if post_channel_balance.is_none() {
1115111156
return Err(ChannelError::WarnAndDisconnect(format!(
1115211157
"Channel {} cannot be spliced out; their {} contribution exhausts their channel balance: {}",
@@ -11164,15 +11169,7 @@ where
1116411169
counterparty_funding_pubkey,
1116511170
);
1116611171

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)?;
1117611173

1117711174
Ok(splice_funding)
1117811175
}
@@ -11336,6 +11333,74 @@ where
1133611333
)
1133711334
}
1133811335

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+
1133911404
#[cfg(splicing)]
1134011405
pub fn splice_locked<NS: Deref, L: Deref>(
1134111406
&mut self, msg: &msgs::SpliceLocked, node_signer: &NS, chain_hash: ChainHash,

0 commit comments

Comments
 (0)