Skip to content

Commit

Permalink
Add probing_diversity_penalty_msat to the scorer parameters
Browse files Browse the repository at this point in the history
When doing background probing, its important to get a diverse view
of the network graph to ensure you have as many options when
pathfinding as possible.

Sadly, the naive probing we currently recommend users do does not
accomplish that - using the same success-optimized pathfinder when
sending as when probing results in always testing the same (good)
path over and over and over again.

Instead, here, we add a `probing_diversity_penalty_msat` parameter
to the scorer, allowing us to penalize channels for which we
already have recent data. It applies a penalty which scales with
the square of the inverse time since the channel was last updated,
up to one day.
  • Loading branch information
TheBlueMatt committed Nov 23, 2024
1 parent 983f3c5 commit 957f93f
Showing 1 changed file with 87 additions and 3 deletions.
90 changes: 87 additions & 3 deletions lightning/src/routing/scoring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,9 @@ where L::Target: Logger {
network_graph: G,
logger: L,
channel_liquidities: HashMap<u64, ChannelLiquidity>,
/// The last time we were given via a [`ScoreUpdate`] method. This does not imply that we've
/// decayed every liquidity bound up to that time.
last_duration_since_epoch: Duration,
}

/// Parameters for configuring [`ProbabilisticScorer`].
Expand Down Expand Up @@ -637,6 +640,22 @@ pub struct ProbabilisticScoringFeeParameters {
///
/// Default value: false
pub linear_success_probability: bool,

/// In order to ensure we have knowledge for as many paths as possible, when probing it makes
/// sense to bias away from channels for which we have very recent data.
///
/// This value is a penalty that is applied based on the last time that we updated the bounds
/// on the available liquidity in a channel. The specified value is the maximum penalty that
/// will be applied.
///
/// It obviously does not make sense to assign a non-0 value here unless you are using the
/// pathfinding result for background probing.
///
/// Specifically, the following penalty is applied
/// `probing_diversity_penalty_msat * max(0, (86400 - current time + last update))^2 / 86400^2` is
///
/// Default value: 0
pub probing_diversity_penalty_msat: u64,
}

impl Default for ProbabilisticScoringFeeParameters {
Expand All @@ -652,6 +671,7 @@ impl Default for ProbabilisticScoringFeeParameters {
historical_liquidity_penalty_multiplier_msat: 10_000,
historical_liquidity_penalty_amount_multiplier_msat: 64,
linear_success_probability: false,
probing_diversity_penalty_msat: 0,
}
}
}
Expand Down Expand Up @@ -706,6 +726,7 @@ impl ProbabilisticScoringFeeParameters {
anti_probing_penalty_msat: 0,
considered_impossible_penalty_msat: 0,
linear_success_probability: true,
probing_diversity_penalty_msat: 0,
}
}
}
Expand Down Expand Up @@ -850,6 +871,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ProbabilisticScorer<G, L> whe
network_graph,
logger,
channel_liquidities: new_hash_map(),
last_duration_since_epoch: Duration::from_secs(0),
}
}

Expand Down Expand Up @@ -1172,7 +1194,7 @@ DirectedChannelLiquidity< L, HT, T> {
/// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
/// this direction.
fn penalty_msat(
&self, amount_msat: u64, inflight_htlc_msat: u64,
&self, amount_msat: u64, inflight_htlc_msat: u64, last_duration_since_epoch: Duration,
score_params: &ProbabilisticScoringFeeParameters,
) -> u64 {
let total_inflight_amount_msat = amount_msat.saturating_add(inflight_htlc_msat);
Expand Down Expand Up @@ -1247,6 +1269,13 @@ DirectedChannelLiquidity< L, HT, T> {
}
}

if score_params.probing_diversity_penalty_msat != 0 {
let time_since_update = last_duration_since_epoch.saturating_sub(*self.last_datapoint);
let mul = Duration::from_secs(60 * 60 * 24).saturating_sub(time_since_update).as_secs();
let penalty = score_params.probing_diversity_penalty_msat.saturating_mul(mul * mul);
res = res.saturating_add(penalty / ((60 * 60 * 24) * (60 * 60 * 24)));
}

res
}

Expand Down Expand Up @@ -1398,11 +1427,12 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreLookUp for Probabilistic
}

let capacity_msat = usage.effective_capacity.as_msat();
let time = self.last_duration_since_epoch;
self.channel_liquidities
.get(scid)
.unwrap_or(&ChannelLiquidity::new(Duration::ZERO))
.as_directed(&source, &target, capacity_msat)
.penalty_msat(usage.amount_msat, usage.inflight_htlc_msat, score_params)
.penalty_msat(usage.amount_msat, usage.inflight_htlc_msat, time, score_params)
.saturating_add(anti_probing_penalty_msat)
.saturating_add(base_penalty_msat)
}
Expand Down Expand Up @@ -1448,6 +1478,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
}
if at_failed_channel { break; }
}
self.last_duration_since_epoch = duration_since_epoch;
}

fn payment_path_successful(&mut self, path: &Path, duration_since_epoch: Duration) {
Expand Down Expand Up @@ -1475,6 +1506,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
hop.short_channel_id);
}
}
self.last_duration_since_epoch = duration_since_epoch;
}

fn probe_failed(&mut self, path: &Path, short_channel_id: u64, duration_since_epoch: Duration) {
Expand Down Expand Up @@ -1506,6 +1538,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
liquidity.min_liquidity_offset_msat != 0 || liquidity.max_liquidity_offset_msat != 0 ||
liquidity.liquidity_history.has_datapoints()
});
self.last_duration_since_epoch = duration_since_epoch;
}
}

Expand Down Expand Up @@ -1939,15 +1972,20 @@ ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScore
r: &mut R, args: (ProbabilisticScoringDecayParameters, G, L)
) -> Result<Self, DecodeError> {
let (decay_params, network_graph, logger) = args;
let mut channel_liquidities = new_hash_map();
let mut channel_liquidities: HashMap<u64, ChannelLiquidity> = new_hash_map();
read_tlv_fields!(r, {
(0, channel_liquidities, required),
});
let mut last_duration_since_epoch = Duration::from_secs(0);
for (_, liq) in channel_liquidities.iter() {
last_duration_since_epoch = cmp::max(last_duration_since_epoch, liq.last_updated);
}
Ok(Self {
decay_params,
network_graph,
logger,
channel_liquidities,
last_duration_since_epoch,
})
}
}
Expand Down Expand Up @@ -3542,6 +3580,52 @@ mod tests {
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, amount_msat, &params),
Some(0.0));
}

#[test]
fn probes_for_diversity() {
// Tests the probing_diversity_penalty_msat is applied
let logger = TestLogger::new();
let network_graph = network_graph(&logger);
let params = ProbabilisticScoringFeeParameters {
probing_diversity_penalty_msat: 1_000_000,
..ProbabilisticScoringFeeParameters::zero_penalty()
};
let decay_params = ProbabilisticScoringDecayParameters {
liquidity_offset_half_life: Duration::from_secs(10),
..ProbabilisticScoringDecayParameters::zero_penalty()
};
let mut scorer = ProbabilisticScorer::new(decay_params, &network_graph, &logger);
let source = source_node_id();

let usage = ChannelUsage {
amount_msat: 512,
inflight_htlc_msat: 0,
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_024 },
};
let channel = network_graph.read_only().channel(42).unwrap().to_owned();
let (info, _) = channel.as_directed_from(&source).unwrap();
let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate {
info,
short_channel_id: 42,
});

// Apply some update to set the last-update time to now
scorer.payment_path_failed(&payment_path_for_amount(1000), 42, Duration::ZERO);

// If no time has passed, we get the full probing_diversity_penalty_msat
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 1_000_000);

// As time passes the penalty decreases.
scorer.time_passed(Duration::from_secs(1));
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 999_976);

scorer.time_passed(Duration::from_secs(2));
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 999_953);

// Once we've gotten halfway through the day our penalty is 1/4 the configured value.
scorer.time_passed(Duration::from_secs(86400/2));
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &params), 250_000);
}
}

#[cfg(ldk_bench)]
Expand Down

0 comments on commit 957f93f

Please sign in to comment.