Skip to content

Commit b43a7e3

Browse files
committed
Fix feerate calculation on closing transactions
This resolves a number of bugs around how we calculate feerates on closing transactions: * We previously calculated the weight wrong both by always counting two outputs instead of counting the number of outputs that actually exist in the closing transaction and by not counting the witness redeemscript. * We use assertions to check the calculated weight matches what we actually build (with debug_assertions for variable-length sigs). * As an additional sanity check, we really should check that the transaction had at least min-relay-fee when we were the channel initator.
1 parent 6366fc7 commit b43a7e3

File tree

1 file changed

+45
-14
lines changed

1 file changed

+45
-14
lines changed

lightning/src/ln/channel.rs

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,8 +1054,30 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
10541054
}
10551055

10561056
#[inline]
1057-
fn get_closing_transaction_weight(a_scriptpubkey: &Script, b_scriptpubkey: &Script) -> u64 {
1058-
(4 + 1 + 36 + 4 + 1 + 1 + 2*(8+1) + 4 + a_scriptpubkey.len() as u64 + b_scriptpubkey.len() as u64)*4 + 2 + 1 + 1 + 2*(1 + 72)
1057+
fn get_closing_transaction_weight(&self, a_scriptpubkey: Option<&Script>, b_scriptpubkey: Option<&Script>) -> u64 {
1058+
let mut ret =
1059+
(4 + // version
1060+
1 + // input count
1061+
36 + // prevout
1062+
1 + // script length (0)
1063+
4 + // sequence
1064+
1 + // output count
1065+
4 // lock time
1066+
)*4 + // * 4 for non-witness parts
1067+
2 + // witness marker and flag
1068+
1 + // witness element count
1069+
4 + // 4 element lengths (2 sigs, multisig dummy, and witness script)
1070+
self.get_funding_redeemscript().len() as u64 + // funding witness script
1071+
2*(1 + 71); // two signatures + sighash type flags
1072+
if let Some(spk) = a_scriptpubkey {
1073+
ret += ((8+1) + // output values and script length
1074+
spk.len() as u64) * 4; // scriptpubkey and witness multiplier
1075+
}
1076+
if let Some(spk) = b_scriptpubkey {
1077+
ret += ((8+1) + // output values and script length
1078+
spk.len() as u64) * 4; // scriptpubkey and witness multiplier
1079+
}
1080+
ret
10591081
}
10601082

10611083
#[inline]
@@ -2880,13 +2902,14 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
28802902
if self.feerate_per_kw > proposed_feerate {
28812903
proposed_feerate = self.feerate_per_kw;
28822904
}
2883-
let tx_weight = Self::get_closing_transaction_weight(&self.get_closing_scriptpubkey(), self.counterparty_shutdown_scriptpubkey.as_ref().unwrap());
2905+
let tx_weight = self.get_closing_transaction_weight(Some(&self.get_closing_scriptpubkey()), Some(self.counterparty_shutdown_scriptpubkey.as_ref().unwrap()));
28842906
let proposed_total_fee_satoshis = proposed_feerate as u64 * tx_weight / 1000;
28852907

28862908
let (closing_tx, total_fee_satoshis) = self.build_closing_transaction(proposed_total_fee_satoshis, false);
28872909
let sig = self.holder_keys
28882910
.sign_closing_transaction(&closing_tx, &self.secp_ctx)
28892911
.ok();
2912+
assert!(closing_tx.get_weight() as u64 <= tx_weight);
28902913
if sig.is_none() { return None; }
28912914

28922915
self.last_sent_closing_fee = Some((proposed_feerate, total_fee_satoshis, sig.clone().unwrap()));
@@ -3031,9 +3054,14 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
30313054
},
30323055
};
30333056

3057+
let closing_tx_max_weight = self.get_closing_transaction_weight(
3058+
if let Some(oup) = closing_tx.output.get(0) { Some(&oup.script_pubkey) } else { None },
3059+
if let Some(oup) = closing_tx.output.get(1) { Some(&oup.script_pubkey) } else { None });
30343060
if let Some((_, last_fee, sig)) = self.last_sent_closing_fee {
30353061
if last_fee == msg.fee_satoshis {
30363062
self.build_signed_closing_transaction(&mut closing_tx, &msg.signature, &sig);
3063+
assert!(closing_tx.get_weight() as u64 <= closing_tx_max_weight);
3064+
debug_assert!(closing_tx.get_weight() as u64 >= closing_tx_max_weight - 2);
30373065
self.channel_state = ChannelState::ShutdownComplete as u32;
30383066
self.update_time_counter += 1;
30393067
return Ok((None, Some(closing_tx)));
@@ -3042,11 +3070,12 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
30423070

30433071
macro_rules! propose_new_feerate {
30443072
($new_feerate: expr) => {
3045-
let closing_tx_max_weight = Self::get_closing_transaction_weight(&self.get_closing_scriptpubkey(), self.counterparty_shutdown_scriptpubkey.as_ref().unwrap());
3046-
let (closing_tx, used_total_fee) = self.build_closing_transaction($new_feerate as u64 * closing_tx_max_weight / 1000, false);
3073+
let tx_weight = self.get_closing_transaction_weight(Some(&self.get_closing_scriptpubkey()), Some(self.counterparty_shutdown_scriptpubkey.as_ref().unwrap()));
3074+
let (closing_tx, used_total_fee) = self.build_closing_transaction($new_feerate as u64 * tx_weight / 1000, false);
30473075
let sig = self.holder_keys
30483076
.sign_closing_transaction(&closing_tx, &self.secp_ctx)
30493077
.map_err(|_| ChannelError::Close("External signer refused to sign closing transaction".to_owned()))?;
3078+
assert!(closing_tx.get_weight() as u64 <= tx_weight);
30503079
self.last_sent_closing_fee = Some(($new_feerate, used_total_fee, sig.clone()));
30513080
return Ok((Some(msgs::ClosingSigned {
30523081
channel_id: self.channel_id,
@@ -3056,10 +3085,10 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
30563085
}
30573086
}
30583087

3059-
let proposed_sat_per_kw = msg.fee_satoshis * 1000 / closing_tx.get_weight() as u64;
3088+
let mut min_feerate = 253;
30603089
if self.channel_outbound {
30613090
let max_feerate = fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Normal);
3062-
if (proposed_sat_per_kw as u32) > max_feerate {
3091+
if (msg.fee_satoshis as u64) > max_feerate as u64 * closing_tx_max_weight / 1000 {
30633092
if let Some((last_feerate, _, _)) = self.last_sent_closing_fee {
30643093
if max_feerate <= last_feerate {
30653094
return Err(ChannelError::Close(format!("Unable to come to consensus about closing feerate, remote wanted something higher ({}) than our Normal feerate ({})", last_feerate, max_feerate)));
@@ -3068,21 +3097,23 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
30683097
propose_new_feerate!(max_feerate);
30693098
}
30703099
} else {
3071-
let min_feerate = fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Background);
3072-
if (proposed_sat_per_kw as u32) < min_feerate {
3073-
if let Some((last_feerate, _, _)) = self.last_sent_closing_fee {
3074-
if min_feerate >= last_feerate {
3075-
return Err(ChannelError::Close(format!("Unable to come to consensus about closing feerate, remote wanted something lower ({}) than our Background feerate ({}).", last_feerate, min_feerate)));
3076-
}
3100+
min_feerate = fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Background);
3101+
}
3102+
if (msg.fee_satoshis as u64) < min_feerate as u64 * closing_tx_max_weight / 1000 {
3103+
if let Some((last_feerate, _, _)) = self.last_sent_closing_fee {
3104+
if min_feerate >= last_feerate {
3105+
return Err(ChannelError::Close(format!("Unable to come to consensus about closing feerate, remote wanted something lower ({}) than our Background feerate ({}).", last_feerate, min_feerate)));
30773106
}
3078-
propose_new_feerate!(min_feerate);
30793107
}
3108+
propose_new_feerate!(min_feerate);
30803109
}
30813110

30823111
let sig = self.holder_keys
30833112
.sign_closing_transaction(&closing_tx, &self.secp_ctx)
30843113
.map_err(|_| ChannelError::Close("External signer refused to sign closing transaction".to_owned()))?;
30853114
self.build_signed_closing_transaction(&mut closing_tx, &msg.signature, &sig);
3115+
assert!(closing_tx.get_weight() as u64 <= closing_tx_max_weight);
3116+
debug_assert!(closing_tx.get_weight() as u64 >= closing_tx_max_weight - 2);
30863117

30873118
self.channel_state = ChannelState::ShutdownComplete as u32;
30883119
self.update_time_counter += 1;

0 commit comments

Comments
 (0)