Skip to content

Commit 660f3f0

Browse files
committed
f Expose overflow errors
1 parent adc2700 commit 660f3f0

File tree

1 file changed

+39
-18
lines changed

1 file changed

+39
-18
lines changed

lightning/src/util/anchor_channel_reserves.rs

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use crate::chain::chainmonitor::ChainMonitor;
2525
use crate::chain::chainmonitor::Persist;
2626
use crate::chain::Filter;
2727
use crate::events::bump_transaction::Utxo;
28+
use crate::ln::chan_utils::MAX_HTLCS;
2829
use crate::ln::channelmanager::AChannelManager;
2930
use crate::prelude::new_hash_set;
3031
use crate::sign::ecdsa::EcdsaChannelSigner;
@@ -33,6 +34,7 @@ use bitcoin::constants::WITNESS_SCALE_FACTOR;
3334
use bitcoin::Amount;
3435
use bitcoin::FeeRate;
3536
use bitcoin::Weight;
37+
use core::cmp::min;
3638
use core::ops::Deref;
3739

3840
// Transaction weights based on:
@@ -150,6 +152,15 @@ pub struct AnchorChannelReserveContext {
150152
pub taproot_wallet: bool,
151153
}
152154

155+
/// Errors around anchor channel reserve calculations.
156+
#[derive(Debug, Clone, PartialEq, Eq)]
157+
pub enum AnchorChannelReserveError {
158+
/// An overflow occurred in a fee calculation, caused by an excessive fee rate or weight.
159+
FeeOverflow,
160+
/// An overflow occurred calculating a total amount of reserves.
161+
AmountOverflow,
162+
}
163+
153164
/// A default for the [AnchorChannelReserveContext] parameters is provided as follows:
154165
/// - The upper bound fee rate is set to the 99th percentile of the median block fee rate since 2019:
155166
/// ~50 sats/vbyte.
@@ -171,28 +182,31 @@ impl Default for AnchorChannelReserveContext {
171182
///
172183
/// This reserve currently needs to be allocated as a disjoint set of UTXOs per channel,
173184
/// as claims are not yet aggregated across channels.
174-
pub fn get_reserve_per_channel(context: &AnchorChannelReserveContext) -> Amount {
185+
pub fn get_reserve_per_channel(
186+
context: &AnchorChannelReserveContext,
187+
) -> Result<Amount, AnchorChannelReserveError> {
188+
let num_htlcs = min(context.expected_accepted_htlcs, MAX_HTLCS) as u64;
175189
let weight = Weight::from_wu(
176190
COMMITMENT_TRANSACTION_BASE_WEIGHT +
177191
// Reserves are calculated in terms of accepted HTLCs, as their timeout defines the urgency of
178192
// on-chain resolution. Each accepted HTLC is assumed to be forwarded to calculate an upper
179193
// bound for the reserve, resulting in `expected_accepted_htlcs` inbound HTLCs and
180194
// `expected_accepted_htlcs` outbound HTLCs per channel in aggregate.
181-
2 * (context.expected_accepted_htlcs as u64) * COMMITMENT_TRANSACTION_PER_HTLC_WEIGHT +
195+
2 * num_htlcs * COMMITMENT_TRANSACTION_PER_HTLC_WEIGHT +
182196
anchor_output_spend_transaction_weight(context) +
183197
// As an upper bound, it is assumed that each HTLC is resolved in a separate transaction.
184198
// However, they might be aggregated when possible depending on timelocks and expiries.
185-
htlc_success_transaction_weight(context) * (context.expected_accepted_htlcs as u64) +
186-
htlc_timeout_transaction_weight(context) * (context.expected_accepted_htlcs as u64),
199+
htlc_success_transaction_weight(context) * num_htlcs +
200+
htlc_timeout_transaction_weight(context) * num_htlcs,
187201
);
188-
context.upper_bound_fee_rate.fee_wu(weight).unwrap_or(Amount::MAX)
202+
context.upper_bound_fee_rate.fee_wu(weight).ok_or(AnchorChannelReserveError::FeeOverflow)
189203
}
190204

191205
/// Calculates the number of anchor channels that can be supported by the reserve provided
192206
/// by `utxos`.
193207
pub fn get_supportable_anchor_channels(
194208
context: &AnchorChannelReserveContext, utxos: &[Utxo],
195-
) -> u64 {
209+
) -> Result<u64, AnchorChannelReserveError> {
196210
// Get the reserve needed per channel, replacing the fee for an initial spend with the actual value
197211
// below.
198212
let default_satisfaction_fee = context
@@ -202,30 +216,36 @@ pub fn get_supportable_anchor_channels(
202216
} else {
203217
P2WPKH_INPUT_WEIGHT
204218
}))
205-
.unwrap_or(Amount::MAX);
206-
let reserve_per_channel = get_reserve_per_channel(context) - default_satisfaction_fee;
219+
.ok_or(AnchorChannelReserveError::FeeOverflow)?;
220+
let reserve_per_channel = get_reserve_per_channel(context)? - default_satisfaction_fee;
207221

208222
let mut total_fractional_amount = Amount::from_sat(0);
209223
let mut num_whole_utxos = 0;
210224
for utxo in utxos {
211225
let satisfaction_fee = context
212226
.upper_bound_fee_rate
213227
.fee_wu(Weight::from_wu(utxo.satisfaction_weight))
214-
.unwrap_or(Amount::MAX);
215-
let amount = utxo.output.value.checked_sub(satisfaction_fee).unwrap_or(Amount::MIN);
228+
.ok_or(AnchorChannelReserveError::FeeOverflow)?;
229+
let amount = if let Some(amount) = utxo.output.value.checked_sub(satisfaction_fee) {
230+
amount
231+
} else {
232+
// The UTXO is considered dust at the given fee rate.
233+
continue;
234+
};
216235
if amount >= reserve_per_channel {
217236
num_whole_utxos += 1;
218237
} else {
219-
total_fractional_amount =
220-
total_fractional_amount.checked_add(amount).unwrap_or(Amount::MAX);
238+
total_fractional_amount = total_fractional_amount
239+
.checked_add(amount)
240+
.ok_or(AnchorChannelReserveError::AmountOverflow)?;
221241
}
222242
}
223243
// We require disjoint sets of UTXOs for the reserve of each channel,
224244
// as claims are currently only aggregated per channel.
225245
//
226246
// A worst-case coin selection is assumed for fractional UTXOs, selecting up to double the
227247
// required amount.
228-
num_whole_utxos + total_fractional_amount.to_sat() / reserve_per_channel.to_sat() / 2
248+
Ok(num_whole_utxos + total_fractional_amount.to_sat() / reserve_per_channel.to_sat() / 2)
229249
}
230250

231251
/// Verifies whether the anchor channel reserve provided by `utxos` is sufficient to support
@@ -258,7 +278,7 @@ pub fn can_support_additional_anchor_channel<
258278
>(
259279
context: &AnchorChannelReserveContext, utxos: &[Utxo], a_channel_manager: &AChannelManagerRef,
260280
chain_monitor: &ChainMonitorRef,
261-
) -> bool
281+
) -> Result<bool, AnchorChannelReserveError>
262282
where
263283
AChannelManagerRef::Target: AChannelManager,
264284
FilterRef::Target: Filter,
@@ -289,7 +309,8 @@ where
289309
anchor_channels.insert(channel.channel_id);
290310
}
291311
}
292-
get_supportable_anchor_channels(context, utxos) > anchor_channels.len() as u64
312+
get_supportable_anchor_channels(context, utxos)
313+
.map(|num_channels| num_channels > anchor_channels.len() as u64)
293314
}
294315

295316
#[cfg(test)]
@@ -308,7 +329,7 @@ mod test {
308329
expected_accepted_htlcs: 1,
309330
taproot_wallet: false,
310331
}),
311-
Amount::from_sat(4349)
332+
Ok(Amount::from_sat(4349))
312333
);
313334
}
314335

@@ -329,7 +350,7 @@ mod test {
329350
#[test]
330351
fn test_get_supportable_anchor_channels() {
331352
let context = AnchorChannelReserveContext::default();
332-
let reserve_per_channel = get_reserve_per_channel(&context);
353+
let reserve_per_channel = get_reserve_per_channel(&context).unwrap();
333354
// Only 3 disjoint sets with a value greater than the required reserve can be created.
334355
let utxos = vec![
335356
make_p2wpkh_utxo(reserve_per_channel * 3 / 2),
@@ -338,7 +359,7 @@ mod test {
338359
make_p2wpkh_utxo(reserve_per_channel * 99 / 100),
339360
make_p2wpkh_utxo(reserve_per_channel * 20 / 100),
340361
];
341-
assert_eq!(get_supportable_anchor_channels(&context, utxos.as_slice()), 3);
362+
assert_eq!(get_supportable_anchor_channels(&context, utxos.as_slice()), Ok(3));
342363
}
343364

344365
#[test]

0 commit comments

Comments
 (0)