Skip to content

Commit a652980

Browse files
committed
Force-close channels on underflow
Following up on the previous commit, where we added debug_asserts within `build_closing_transaction` to ensure neither `value_to_holder` nor `value_to_counterparty` underflow, we now also force-close the channel in the (presumably impossible) event that it did happen.
1 parent 13835c0 commit a652980

File tree

1 file changed

+33
-19
lines changed

1 file changed

+33
-19
lines changed

lightning/src/ln/channel.rs

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4351,7 +4351,7 @@ impl<SP: Deref> Channel<SP> where
43514351
}
43524352

43534353
#[inline]
4354-
fn build_closing_transaction(&self, proposed_total_fee_satoshis: u64, skip_remote_output: bool) -> (ClosingTransaction, u64) {
4354+
fn build_closing_transaction(&self, proposed_total_fee_satoshis: u64, skip_remote_output: bool) -> Result<(ClosingTransaction, u64), ChannelError> {
43554355
assert!(self.context.pending_inbound_htlcs.is_empty());
43564356
assert!(self.context.pending_outbound_htlcs.is_empty());
43574357
assert!(self.context.pending_update_fee.is_none());
@@ -4369,11 +4369,17 @@ impl<SP: Deref> Channel<SP> where
43694369
}
43704370

43714371
debug_assert!(value_to_counterparty >= 0);
4372+
if value_to_counterparty < 0 {
4373+
return Err(ChannelError::close(format!("Value to counterparty below 0: {}", value_to_counterparty)))
4374+
}
43724375
if skip_remote_output || value_to_counterparty as u64 <= self.context.holder_dust_limit_satoshis {
43734376
value_to_counterparty = 0;
43744377
}
43754378

43764379
debug_assert!(value_to_holder >= 0);
4380+
if value_to_holder < 0 {
4381+
return Err(ChannelError::close(format!("Value to holder below 0: {}", value_to_holder)))
4382+
}
43774383
if value_to_holder as u64 <= self.context.holder_dust_limit_satoshis {
43784384
value_to_holder = 0;
43794385
}
@@ -4384,7 +4390,7 @@ impl<SP: Deref> Channel<SP> where
43844390
let funding_outpoint = self.funding_outpoint().into_bitcoin_outpoint();
43854391

43864392
let closing_transaction = ClosingTransaction::new(value_to_holder as u64, value_to_counterparty as u64, holder_shutdown_script, counterparty_shutdown_script, funding_outpoint);
4387-
(closing_transaction, total_fee_satoshis)
4393+
Ok((closing_transaction, total_fee_satoshis))
43884394
}
43894395

43904396
fn funding_outpoint(&self) -> OutPoint {
@@ -6138,19 +6144,27 @@ impl<SP: Deref> Channel<SP> where
61386144
if let Some((fee, skip_remote_output, fee_range, holder_sig)) = self.context.last_sent_closing_fee.clone() {
61396145
debug_assert!(holder_sig.is_none());
61406146
log_trace!(logger, "Attempting to generate pending closing_signed...");
6141-
let (closing_tx, fee) = self.build_closing_transaction(fee, skip_remote_output);
6142-
let closing_signed = self.get_closing_signed_msg(&closing_tx, skip_remote_output,
6143-
fee, fee_range.min_fee_satoshis, fee_range.max_fee_satoshis, logger);
6144-
let signed_tx = if let (Some(ClosingSigned { signature, .. }), Some(counterparty_sig)) =
6145-
(closing_signed.as_ref(), self.context.last_received_closing_sig) {
6146-
let funding_redeemscript = self.context.get_funding_redeemscript();
6147-
let sighash = closing_tx.trust().get_sighash_all(&funding_redeemscript, self.context.channel_value_satoshis);
6148-
debug_assert!(self.context.secp_ctx.verify_ecdsa(&sighash, &counterparty_sig,
6149-
&self.context.get_counterparty_pubkeys().funding_pubkey).is_ok());
6150-
Some(self.build_signed_closing_transaction(&closing_tx, &counterparty_sig, signature))
6151-
} else { None };
6152-
let shutdown_result = signed_tx.as_ref().map(|_| self.shutdown_result_coop_close());
6153-
(closing_signed, signed_tx, shutdown_result)
6147+
let closing_transaction_result = self.build_closing_transaction(fee, skip_remote_output);
6148+
match closing_transaction_result {
6149+
Ok((closing_tx, fee)) => {
6150+
let closing_signed = self.get_closing_signed_msg(&closing_tx, skip_remote_output,
6151+
fee, fee_range.min_fee_satoshis, fee_range.max_fee_satoshis, logger);
6152+
let signed_tx = if let (Some(ClosingSigned { signature, .. }), Some(counterparty_sig)) =
6153+
(closing_signed.as_ref(), self.context.last_received_closing_sig) {
6154+
let funding_redeemscript = self.context.get_funding_redeemscript();
6155+
let sighash = closing_tx.trust().get_sighash_all(&funding_redeemscript, self.context.channel_value_satoshis);
6156+
debug_assert!(self.context.secp_ctx.verify_ecdsa(&sighash, &counterparty_sig,
6157+
&self.context.get_counterparty_pubkeys().funding_pubkey).is_ok());
6158+
Some(self.build_signed_closing_transaction(&closing_tx, &counterparty_sig, signature))
6159+
} else { None };
6160+
let shutdown_result = signed_tx.as_ref().map(|_| self.shutdown_result_coop_close());
6161+
(closing_signed, signed_tx, shutdown_result)
6162+
}
6163+
Err(err) => {
6164+
let shutdown = self.context.force_shutdown(true, ClosureReason::ProcessingError {err: err.to_string()});
6165+
(None, None, Some(shutdown))
6166+
}
6167+
}
61546168
} else { (None, None, None) }
61556169
} else { (None, None, None) };
61566170

@@ -6618,7 +6632,7 @@ impl<SP: Deref> Channel<SP> where
66186632
let (our_min_fee, our_max_fee) = self.calculate_closing_fee_limits(fee_estimator);
66196633

66206634
assert!(self.context.shutdown_scriptpubkey.is_some());
6621-
let (closing_tx, total_fee_satoshis) = self.build_closing_transaction(our_min_fee, false);
6635+
let (closing_tx, total_fee_satoshis) = self.build_closing_transaction(our_min_fee, false)?;
66226636
log_trace!(logger, "Proposing initial closing_signed for our counterparty with a fee range of {}-{} sat (with initial proposal {} sats)",
66236637
our_min_fee, our_max_fee, total_fee_satoshis);
66246638

@@ -6852,7 +6866,7 @@ impl<SP: Deref> Channel<SP> where
68526866

68536867
let funding_redeemscript = self.context.get_funding_redeemscript();
68546868
let mut skip_remote_output = false;
6855-
let (mut closing_tx, used_total_fee) = self.build_closing_transaction(msg.fee_satoshis, skip_remote_output);
6869+
let (mut closing_tx, used_total_fee) = self.build_closing_transaction(msg.fee_satoshis, skip_remote_output)?;
68566870
if used_total_fee != msg.fee_satoshis {
68576871
return Err(ChannelError::close(format!("Remote sent us a closing_signed with a fee other than the value they can claim. Fee in message: {}. Actual closing tx fee: {}", msg.fee_satoshis, used_total_fee)));
68586872
}
@@ -6864,7 +6878,7 @@ impl<SP: Deref> Channel<SP> where
68646878
// The remote end may have decided to revoke their output due to inconsistent dust
68656879
// limits, so check for that case by re-checking the signature here.
68666880
skip_remote_output = true;
6867-
closing_tx = self.build_closing_transaction(msg.fee_satoshis, skip_remote_output).0;
6881+
closing_tx = self.build_closing_transaction(msg.fee_satoshis, skip_remote_output)?.0;
68686882
let sighash = closing_tx.trust().get_sighash_all(&funding_redeemscript, self.context.channel_value_satoshis);
68696883
secp_check!(self.context.secp_ctx.verify_ecdsa(&sighash, &msg.signature, self.context.counterparty_funding_pubkey()), "Invalid closing tx signature from peer".to_owned());
68706884
},
@@ -6895,7 +6909,7 @@ impl<SP: Deref> Channel<SP> where
68956909
(closing_tx, $new_fee)
68966910
} else {
68976911
skip_remote_output = false;
6898-
self.build_closing_transaction($new_fee, skip_remote_output)
6912+
self.build_closing_transaction($new_fee, skip_remote_output)?
68996913
};
69006914

69016915
let closing_signed = self.get_closing_signed_msg(&closing_tx, skip_remote_output, used_fee, our_min_fee, our_max_fee, logger);

0 commit comments

Comments
 (0)