Skip to content

Commit 187a8b7

Browse files
authored
Merge pull request #3422 from TheBlueMatt/2024-11-probing-diversity
Add `probing_diversity_penalty_msat` to the scorer parameters
2 parents c4d23bc + a245ca7 commit 187a8b7

File tree

1 file changed

+121
-17
lines changed

1 file changed

+121
-17
lines changed

lightning/src/routing/scoring.rs

Lines changed: 121 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,9 @@ where L::Target: Logger {
476476
network_graph: G,
477477
logger: L,
478478
channel_liquidities: ChannelLiquidities,
479+
/// The last time we were given via a [`ScoreUpdate`] method. This does not imply that we've
480+
/// decayed every liquidity bound up to that time.
481+
last_update_time: Duration,
479482
}
480483
/// Container for live and historical liquidity bounds for each channel.
481484
#[derive(Clone)]
@@ -745,6 +748,29 @@ pub struct ProbabilisticScoringFeeParameters {
745748
///
746749
/// Default value: false
747750
pub linear_success_probability: bool,
751+
752+
/// In order to ensure we have knowledge for as many paths as possible, when probing it makes
753+
/// sense to bias away from channels for which we have very recent data.
754+
///
755+
/// This value is a penalty that is applied based on the last time that we updated the bounds
756+
/// on the available liquidity in a channel. The specified value is the maximum penalty that
757+
/// will be applied.
758+
///
759+
/// It obviously does not make sense to assign a non-0 value here unless you are using the
760+
/// pathfinding result for background probing.
761+
///
762+
/// Specifically, the following penalty is applied
763+
/// `probing_diversity_penalty_msat * max(0, (86400 - current time + last update))^2 / 86400^2` is
764+
///
765+
/// As this is a maximum value, when setting this you should consider it in relation to the
766+
/// other values set to ensure that, at maximum, we strongly avoid paths which we recently
767+
/// tried (similar to if they have a low success probability). For example, you might set this
768+
/// to be the sum of [`Self::base_penalty_msat`] and
769+
/// [`Self::historical_liquidity_penalty_multiplier_msat`] (plus some multiple of their
770+
/// corresponding `amount_multiplier`s).
771+
///
772+
/// Default value: 0
773+
pub probing_diversity_penalty_msat: u64,
748774
}
749775

750776
impl Default for ProbabilisticScoringFeeParameters {
@@ -760,6 +786,7 @@ impl Default for ProbabilisticScoringFeeParameters {
760786
historical_liquidity_penalty_multiplier_msat: 10_000,
761787
historical_liquidity_penalty_amount_multiplier_msat: 1_250,
762788
linear_success_probability: false,
789+
probing_diversity_penalty_msat: 0,
763790
}
764791
}
765792
}
@@ -814,6 +841,7 @@ impl ProbabilisticScoringFeeParameters {
814841
anti_probing_penalty_msat: 0,
815842
considered_impossible_penalty_msat: 0,
816843
linear_success_probability: true,
844+
probing_diversity_penalty_msat: 0,
817845
}
818846
}
819847
}
@@ -901,17 +929,11 @@ struct ChannelLiquidity {
901929
/// Time when the historical liquidity bounds were last modified as an offset against the unix
902930
/// epoch.
903931
offset_history_last_updated: Duration,
904-
}
905932

906-
// Check that the liquidity HashMap's entries sit on round cache lines.
907-
//
908-
// Specifically, the first cache line will have the key, the liquidity offsets, and the total
909-
// points tracked in the historical tracker.
910-
//
911-
// The next two cache lines will have the historical points, which we only access last during
912-
// scoring, followed by the last_updated `Duration`s (which we do not need during scoring).
913-
const _LIQUIDITY_MAP_SIZING_CHECK: usize = 192 - ::core::mem::size_of::<(u64, ChannelLiquidity)>();
914-
const _LIQUIDITY_MAP_SIZING_CHECK_2: usize = ::core::mem::size_of::<(u64, ChannelLiquidity)>() - 192;
933+
/// The last time when the liquidity bounds were updated with new payment information (i.e.
934+
/// ignoring decays).
935+
last_datapoint_time: Duration,
936+
}
915937

916938
/// A snapshot of [`ChannelLiquidity`] in one direction assuming a certain channel capacity.
917939
struct DirectedChannelLiquidity<L: Deref<Target = u64>, HT: Deref<Target = HistoricalLiquidityTracker>, T: Deref<Target = Duration>> {
@@ -921,6 +943,7 @@ struct DirectedChannelLiquidity<L: Deref<Target = u64>, HT: Deref<Target = Histo
921943
capacity_msat: u64,
922944
last_updated: T,
923945
offset_history_last_updated: T,
946+
last_datapoint_time: T,
924947
}
925948

926949
impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ProbabilisticScorer<G, L> where L::Target: Logger {
@@ -932,6 +955,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ProbabilisticScorer<G, L> whe
932955
network_graph,
933956
logger,
934957
channel_liquidities: ChannelLiquidities::new(),
958+
last_update_time: Duration::from_secs(0),
935959
}
936960
}
937961

@@ -1163,6 +1187,7 @@ impl ChannelLiquidity {
11631187
liquidity_history: HistoricalLiquidityTracker::new(),
11641188
last_updated,
11651189
offset_history_last_updated: last_updated,
1190+
last_datapoint_time: last_updated,
11661191
}
11671192
}
11681193

@@ -1195,6 +1220,7 @@ impl ChannelLiquidity {
11951220
capacity_msat,
11961221
last_updated: &self.last_updated,
11971222
offset_history_last_updated: &self.offset_history_last_updated,
1223+
last_datapoint_time: &self.last_datapoint_time,
11981224
}
11991225
}
12001226

@@ -1218,6 +1244,7 @@ impl ChannelLiquidity {
12181244
capacity_msat,
12191245
last_updated: &mut self.last_updated,
12201246
offset_history_last_updated: &mut self.offset_history_last_updated,
1247+
last_datapoint_time: &mut self.last_datapoint_time,
12211248
}
12221249
}
12231250

@@ -1385,7 +1412,7 @@ DirectedChannelLiquidity< L, HT, T> {
13851412
/// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
13861413
/// this direction.
13871414
fn penalty_msat(
1388-
&self, amount_msat: u64, inflight_htlc_msat: u64,
1415+
&self, amount_msat: u64, inflight_htlc_msat: u64, last_update_time: Duration,
13891416
score_params: &ProbabilisticScoringFeeParameters,
13901417
) -> u64 {
13911418
let total_inflight_amount_msat = amount_msat.saturating_add(inflight_htlc_msat);
@@ -1467,6 +1494,18 @@ DirectedChannelLiquidity< L, HT, T> {
14671494
}
14681495
}
14691496

1497+
if score_params.probing_diversity_penalty_msat != 0 {
1498+
// We use `last_update_time` as a stand-in for the current time as we don't want to
1499+
// fetch the current time in every score call (slowing things down substantially on
1500+
// some platforms where a syscall is required), don't want to add an unnecessary `std`
1501+
// requirement. Assuming we're probing somewhat regularly, it should reliably be close
1502+
// to the current time, (and using the last the last time we probed is also fine here).
1503+
let time_since_update = last_update_time.saturating_sub(*self.last_datapoint_time);
1504+
let mul = Duration::from_secs(60 * 60 * 24).saturating_sub(time_since_update).as_secs();
1505+
let penalty = score_params.probing_diversity_penalty_msat.saturating_mul(mul * mul);
1506+
res = res.saturating_add(penalty / ((60 * 60 * 24) * (60 * 60 * 24)));
1507+
}
1508+
14701509
res
14711510
}
14721511

@@ -1564,6 +1603,7 @@ DirectedChannelLiquidity<L, HT, T> {
15641603
*self.max_liquidity_offset_msat = 0;
15651604
}
15661605
*self.last_updated = duration_since_epoch;
1606+
*self.last_datapoint_time = duration_since_epoch;
15671607
}
15681608

15691609
/// Adjusts the upper bound of the channel liquidity balance in this direction.
@@ -1573,6 +1613,7 @@ DirectedChannelLiquidity<L, HT, T> {
15731613
*self.min_liquidity_offset_msat = 0;
15741614
}
15751615
*self.last_updated = duration_since_epoch;
1616+
*self.last_datapoint_time = duration_since_epoch;
15761617
}
15771618
}
15781619

@@ -1616,11 +1657,12 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreLookUp for Probabilistic
16161657
}
16171658

16181659
let capacity_msat = usage.effective_capacity.as_msat();
1660+
let time = self.last_update_time;
16191661
self.channel_liquidities
16201662
.get(scid)
16211663
.unwrap_or(&ChannelLiquidity::new(Duration::ZERO))
16221664
.as_directed(&source, &target, capacity_msat)
1623-
.penalty_msat(usage.amount_msat, usage.inflight_htlc_msat, score_params)
1665+
.penalty_msat(usage.amount_msat, usage.inflight_htlc_msat, time, score_params)
16241666
.saturating_add(anti_probing_penalty_msat)
16251667
.saturating_add(base_penalty_msat)
16261668
}
@@ -1666,6 +1708,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
16661708
}
16671709
if at_failed_channel { break; }
16681710
}
1711+
self.last_update_time = duration_since_epoch;
16691712
}
16701713

16711714
fn payment_path_successful(&mut self, path: &Path, duration_since_epoch: Duration) {
@@ -1693,6 +1736,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
16931736
hop.short_channel_id);
16941737
}
16951738
}
1739+
self.last_update_time = duration_since_epoch;
16961740
}
16971741

16981742
fn probe_failed(&mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration) {
@@ -1705,6 +1749,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
17051749

17061750
fn time_passed(&mut self, duration_since_epoch: Duration) {
17071751
self.channel_liquidities.time_passed(duration_since_epoch, self.decay_params);
1752+
self.last_update_time = duration_since_epoch;
17081753
}
17091754
}
17101755

@@ -2383,11 +2428,16 @@ ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScore
23832428
) -> Result<Self, DecodeError> {
23842429
let (decay_params, network_graph, logger) = args;
23852430
let channel_liquidities = ChannelLiquidities::read(r)?;
2431+
let mut last_update_time = Duration::from_secs(0);
2432+
for (_, liq) in channel_liquidities.0.iter() {
2433+
last_update_time = cmp::max(last_update_time, liq.last_updated);
2434+
}
23862435
Ok(Self {
23872436
decay_params,
23882437
network_graph,
23892438
logger,
23902439
channel_liquidities,
2440+
last_update_time,
23912441
})
23922442
}
23932443
}
@@ -2404,6 +2454,7 @@ impl Writeable for ChannelLiquidity {
24042454
(5, self.liquidity_history.writeable_min_offset_history(), required),
24052455
(7, self.liquidity_history.writeable_max_offset_history(), required),
24062456
(9, self.offset_history_last_updated, required),
2457+
(11, self.last_datapoint_time, required),
24072458
});
24082459
Ok(())
24092460
}
@@ -2420,6 +2471,7 @@ impl Readable for ChannelLiquidity {
24202471
let mut max_liquidity_offset_history: Option<HistoricalBucketRangeTracker> = None;
24212472
let mut last_updated = Duration::from_secs(0);
24222473
let mut offset_history_last_updated = None;
2474+
let mut last_datapoint_time = None;
24232475
read_tlv_fields!(r, {
24242476
(0, min_liquidity_offset_msat, required),
24252477
(1, legacy_min_liq_offset_history, option),
@@ -2429,6 +2481,7 @@ impl Readable for ChannelLiquidity {
24292481
(5, min_liquidity_offset_history, option),
24302482
(7, max_liquidity_offset_history, option),
24312483
(9, offset_history_last_updated, option),
2484+
(11, last_datapoint_time, option),
24322485
});
24332486

24342487
if min_liquidity_offset_history.is_none() {
@@ -2453,6 +2506,7 @@ impl Readable for ChannelLiquidity {
24532506
),
24542507
last_updated,
24552508
offset_history_last_updated: offset_history_last_updated.unwrap_or(last_updated),
2509+
last_datapoint_time: last_datapoint_time.unwrap_or(last_updated),
24562510
})
24572511
}
24582512
}
@@ -2626,19 +2680,20 @@ mod tests {
26262680
let logger = TestLogger::new();
26272681
let last_updated = Duration::ZERO;
26282682
let offset_history_last_updated = Duration::ZERO;
2683+
let last_datapoint_time = Duration::ZERO;
26292684
let network_graph = network_graph(&logger);
26302685
let decay_params = ProbabilisticScoringDecayParameters::default();
26312686
let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger)
26322687
.with_channel(42,
26332688
ChannelLiquidity {
26342689
min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100,
2635-
last_updated, offset_history_last_updated,
2690+
last_updated, offset_history_last_updated, last_datapoint_time,
26362691
liquidity_history: HistoricalLiquidityTracker::new(),
26372692
})
26382693
.with_channel(43,
26392694
ChannelLiquidity {
26402695
min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100,
2641-
last_updated, offset_history_last_updated,
2696+
last_updated, offset_history_last_updated, last_datapoint_time,
26422697
liquidity_history: HistoricalLiquidityTracker::new(),
26432698
});
26442699
let source = source_node_id();
@@ -2705,13 +2760,14 @@ mod tests {
27052760
let logger = TestLogger::new();
27062761
let last_updated = Duration::ZERO;
27072762
let offset_history_last_updated = Duration::ZERO;
2763+
let last_datapoint_time = Duration::ZERO;
27082764
let network_graph = network_graph(&logger);
27092765
let decay_params = ProbabilisticScoringDecayParameters::default();
27102766
let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger)
27112767
.with_channel(42,
27122768
ChannelLiquidity {
27132769
min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400,
2714-
last_updated, offset_history_last_updated,
2770+
last_updated, offset_history_last_updated, last_datapoint_time,
27152771
liquidity_history: HistoricalLiquidityTracker::new(),
27162772
});
27172773
let source = source_node_id();
@@ -2765,13 +2821,14 @@ mod tests {
27652821
let logger = TestLogger::new();
27662822
let last_updated = Duration::ZERO;
27672823
let offset_history_last_updated = Duration::ZERO;
2824+
let last_datapoint_time = Duration::ZERO;
27682825
let network_graph = network_graph(&logger);
27692826
let decay_params = ProbabilisticScoringDecayParameters::default();
27702827
let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger)
27712828
.with_channel(42,
27722829
ChannelLiquidity {
27732830
min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400,
2774-
last_updated, offset_history_last_updated,
2831+
last_updated, offset_history_last_updated, last_datapoint_time,
27752832
liquidity_history: HistoricalLiquidityTracker::new(),
27762833
});
27772834
let source = source_node_id();
@@ -2877,6 +2934,7 @@ mod tests {
28772934
let logger = TestLogger::new();
28782935
let last_updated = Duration::ZERO;
28792936
let offset_history_last_updated = Duration::ZERO;
2937+
let last_datapoint_time = Duration::ZERO;
28802938
let network_graph = network_graph(&logger);
28812939
let params = ProbabilisticScoringFeeParameters {
28822940
liquidity_penalty_multiplier_msat: 1_000,
@@ -2890,7 +2948,7 @@ mod tests {
28902948
.with_channel(42,
28912949
ChannelLiquidity {
28922950
min_liquidity_offset_msat: 40, max_liquidity_offset_msat: 40,
2893-
last_updated, offset_history_last_updated,
2951+
last_updated, offset_history_last_updated, last_datapoint_time,
28942952
liquidity_history: HistoricalLiquidityTracker::new(),
28952953
});
28962954
let source = source_node_id();
@@ -4074,6 +4132,52 @@ mod tests {
40744132
combined_scorer.scorer.estimated_channel_liquidity_range(42, &target_node_id());
40754133
assert_eq!(liquidity_range.unwrap(), (0, 0));
40764134
}
4135+
4136+
#[test]
4137+
fn probes_for_diversity() {
4138+
// Tests the probing_diversity_penalty_msat is applied
4139+
let logger = TestLogger::new();
4140+
let network_graph = network_graph(&logger);
4141+
let params = ProbabilisticScoringFeeParameters {
4142+
probing_diversity_penalty_msat: 1_000_000,
4143+
..ProbabilisticScoringFeeParameters::zero_penalty()
4144+
};
4145+
let decay_params = ProbabilisticScoringDecayParameters {
4146+
liquidity_offset_half_life: Duration::from_secs(10),
4147+
..ProbabilisticScoringDecayParameters::zero_penalty()
4148+
};
4149+
let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger);
4150+
let source = source_node_id();
4151+
4152+
let usage = ChannelUsage {
4153+
amount_msat: 512,
4154+
inflight_htlc_msat: 0,
4155+
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_024 },
4156+
};
4157+
let channel = network_graph.read_only().channel(42).unwrap().to_owned();
4158+
let (info, _) = channel.as_directed_from(&source).unwrap();
4159+
let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate {
4160+
info,
4161+
short_channel_id: 42,
4162+
});
4163+
4164+
// Apply some update to set the last-update time to now
4165+
scorer.payment_path_failed(&payment_path_for_amount(1000), 42, Duration::ZERO);
4166+
4167+
// If no time has passed, we get the full probing_diversity_penalty_msat
4168+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 1_000_000);
4169+
4170+
// As time passes the penalty decreases.
4171+
scorer.time_passed(Duration::from_secs(1));
4172+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 999_976);
4173+
4174+
scorer.time_passed(Duration::from_secs(2));
4175+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 999_953);
4176+
4177+
// Once we've gotten halfway through the day our penalty is 1/4 the configured value.
4178+
scorer.time_passed(Duration::from_secs(86400/2));
4179+
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 250_000);
4180+
}
40774181
}
40784182

40794183
#[cfg(ldk_bench)]

0 commit comments

Comments
 (0)