Skip to content

Commit bfd1a57

Browse files
committed
Guard against division by zero in scorer
Since a node may announce that the htlc_maximum_msat of a channel is zero, adding one to the denominator in the bucket formulas will prevent the panic from ever happening. While the routing algorithm may never select such a channel to score, this precaution may still be useful in case the algorithm changes or if the scorer is used with a different routing algorithm.
1 parent e448686 commit bfd1a57

File tree

1 file changed

+27
-7
lines changed

1 file changed

+27
-7
lines changed

lightning/src/routing/scoring.rs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ impl HistoricalBucketRangeTracker {
575575
// Ensure the bucket index is in the range [0, 7], even if the liquidity offset is zero or
576576
// the channel's capacity, though the second should generally never happen.
577577
debug_assert!(liquidity_offset_msat <= capacity_msat);
578-
let bucket_idx: u8 = (liquidity_offset_msat.saturating_sub(1) * 8 / capacity_msat)
578+
let bucket_idx: u8 = (liquidity_offset_msat * 8 / capacity_msat.saturating_add(1))
579579
.try_into().unwrap_or(32); // 32 is bogus for 8 buckets, and will be ignored
580580
debug_assert!(bucket_idx < 8);
581581
if bucket_idx < 8 {
@@ -1034,12 +1034,12 @@ impl<L: Deref<Target = u64>, BRT: Deref<Target = HistoricalBucketRangeTracker>,
10341034
if params.historical_liquidity_penalty_multiplier_msat != 0 ||
10351035
params.historical_liquidity_penalty_amount_multiplier_msat != 0 {
10361036
let payment_amt_64th_bucket = if amount_msat < u64::max_value() / 64 {
1037-
amount_msat * 64 / self.capacity_msat
1037+
amount_msat * 64 / self.capacity_msat.saturating_add(1)
10381038
} else {
10391039
// Only use 128-bit arithmetic when multiplication will overflow to avoid 128-bit
10401040
// division. This branch should only be hit in fuzz testing since the amount would
10411041
// need to be over 2.88 million BTC in practice.
1042-
((amount_msat as u128) * 64 / (self.capacity_msat as u128))
1042+
((amount_msat as u128) * 64 / (self.capacity_msat as u128).saturating_add(1))
10431043
.try_into().unwrap_or(65)
10441044
};
10451045
#[cfg(not(fuzzing))]
@@ -1817,13 +1817,13 @@ mod tests {
18171817
let chain_source: Option<&crate::util::test_utils::TestChainSource> = None;
18181818
network_graph.update_channel_from_announcement(
18191819
&signed_announcement, &chain_source).unwrap();
1820-
update_channel(network_graph, short_channel_id, node_1_key, 0);
1821-
update_channel(network_graph, short_channel_id, node_2_key, 1);
1820+
update_channel(network_graph, short_channel_id, node_1_key, 0, 1_000);
1821+
update_channel(network_graph, short_channel_id, node_2_key, 1, 0);
18221822
}
18231823

18241824
fn update_channel(
18251825
network_graph: &mut NetworkGraph<&TestLogger>, short_channel_id: u64, node_key: SecretKey,
1826-
flags: u8
1826+
flags: u8, htlc_maximum_msat: u64
18271827
) {
18281828
let genesis_hash = genesis_block(Network::Testnet).header.block_hash();
18291829
let secp_ctx = Secp256k1::new();
@@ -1834,7 +1834,7 @@ mod tests {
18341834
flags,
18351835
cltv_expiry_delta: 18,
18361836
htlc_minimum_msat: 0,
1837-
htlc_maximum_msat: 1_000,
1837+
htlc_maximum_msat,
18381838
fee_base_msat: 1,
18391839
fee_proportional_millionths: 0,
18401840
excess_data: Vec::new(),
@@ -2754,6 +2754,7 @@ mod tests {
27542754
let logger = TestLogger::new();
27552755
let network_graph = network_graph(&logger);
27562756
let params = ProbabilisticScoringParameters {
2757+
liquidity_offset_half_life: Duration::from_secs(60 * 60),
27572758
historical_liquidity_penalty_multiplier_msat: 1024,
27582759
historical_liquidity_penalty_amount_multiplier_msat: 1024,
27592760
historical_no_updates_half_life: Duration::from_secs(10),
@@ -2804,6 +2805,25 @@ mod tests {
28042805
};
28052806
scorer.payment_path_failed(&payment_path_for_amount(1).iter().collect::<Vec<_>>(), 42);
28062807
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 409);
2808+
2809+
let usage = ChannelUsage {
2810+
amount_msat: 1,
2811+
inflight_htlc_msat: 0,
2812+
effective_capacity: EffectiveCapacity::MaximumHTLC { amount_msat: 0 },
2813+
};
2814+
assert_eq!(scorer.channel_penalty_msat(42, &target, &source, usage), 2048);
2815+
2816+
// Advance to decay all liquidity offsets to zero.
2817+
SinceEpoch::advance(Duration::from_secs(60 * 60 * 10));
2818+
2819+
// Use a path in the opposite direction, which have zero for htlc_maximum_msat. This will
2820+
// ensure that the effective capacity is zero to test division-by-zero edge cases.
2821+
let path = vec![
2822+
path_hop(target_pubkey(), 43, 2),
2823+
path_hop(source_pubkey(), 42, 1),
2824+
path_hop(sender_pubkey(), 41, 0),
2825+
];
2826+
scorer.payment_path_failed(&path.iter().collect::<Vec<_>>(), 42);
28072827
}
28082828

28092829
#[test]

0 commit comments

Comments
 (0)