From: Matt Corallo Date: Thu, 21 Nov 2024 22:38:57 +0000 (+0000) Subject: Demonstrate testing the LDK scorer X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=033866494209ee1aa6d15cd026b8810f059cc29d;p=ln-routing-replay Demonstrate testing the LDK scorer Note that this requires LDK PR #3420 --- diff --git a/Cargo.toml b/Cargo.toml index 50abd8c..1c29d8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,8 @@ edition = "2021" bitcoin = "0.32" # Note that we have to turn on no-std to disable the timestamp range verification on channel_update lightning = { version = "0.0.125", default-features = false, features = ["no-std"] } + +[patch.crates-io] +# In order to properly test the LDK model, we need a small patch which is working its way upstream. +# If you're testing your own model you can remove this patch. +lightning = { git = "https://git.bitcoin.ninja/rust-lightning", branch = "2024-11-live-success-prob-125" } diff --git a/src/main.rs b/src/main.rs index 0d82fc1..2a415f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,12 +6,22 @@ use lightning::routing::gossip::{NetworkGraph, NodeId, ReadOnlyNetworkGraph}; /// The simulation state. /// /// You're free to put whatever you want here. -pub struct State { +pub struct State<'a> { + /// As a demonstration, the default run calculates the probability the LDK historical model + /// assigns to results. + scorer: lightning::routing::scoring::ProbabilisticScorer<&'a NetworkGraph, DevNullLogger>, + // We demonstrate calculating log-loss of the LDK historical model + loss_sum: f64, + result_count: u64, } /// Creates a new [`State`] before any probe results are processed. pub fn do_setup<'a>(graph: &'a NetworkGraph) -> State { - State {} + State { + scorer: lightning::routing::scoring::ProbabilisticScorer::new(Default::default(), graph, internal::DevNullLogger), + loss_sum: 0.0, + result_count: 0, + } } /// Processes one probe result. @@ -19,13 +29,73 @@ pub fn do_setup<'a>(graph: &'a NetworkGraph) -> State { /// The network graph as it existed at `result.timestamp` is provided, as well as a reference to /// the current state. pub fn process_probe_result(network_graph: ReadOnlyNetworkGraph, result: ProbeResult, state: &mut State) { + // Update the model's time + use lightning::routing::scoring::ScoreUpdate; + let cur_time = std::time::Duration::from_secs(result.timestamp); + state.scorer.time_passed(cur_time); + // Evaluate the model + for hop in result.channels_with_sufficient_liquidity.iter() { + // You can get additional information about the channel from the network_graph: + let _chan = network_graph.channels().get(&hop.short_channel_id).unwrap(); + let mut model_probability = + state.scorer.historical_estimated_payment_success_probability(hop.short_channel_id, &hop.dst_node_id, hop.amount_msat, &Default::default()) + .unwrap_or_else(|| + // If LDK doesn't have sufficient historical state it will fall back to (roughly) the live model. + state.scorer + .live_estimated_payment_success_probability(hop.short_channel_id, &hop.dst_node_id, hop.amount_msat, &Default::default()) + .expect("We should have some estimated probability, even without history data") + ); + if model_probability < 0.01 { model_probability = 0.01; } + state.loss_sum -= model_probability.log2(); + state.result_count += 1; + } + if let Some(hop) = &result.channel_that_rejected_payment { + // You can get additional information about the channel from the network_graph: + let _chan = network_graph.channels().get(&hop.short_channel_id).unwrap(); + let mut model_probability = + state.scorer.historical_estimated_payment_success_probability(hop.short_channel_id, &hop.dst_node_id, hop.amount_msat, &Default::default()) + .unwrap_or_else(|| + // If LDK doesn't have sufficient historical state it will fall back to (roughly) the live model. + state.scorer + .live_estimated_payment_success_probability(hop.short_channel_id, &hop.dst_node_id, hop.amount_msat, &Default::default()) + .expect("We should have some estimated probability, even without history data") + ); + if model_probability > 0.99 { model_probability = 0.99; } + state.loss_sum -= (1.0 - model_probability).log2(); + state.result_count += 1; + } + + // Update the model with the information we learned + let mut path = lightning::routing::router::Path { + hops: Vec::new(), + blinded_tail: None, + }; + let mut hops = result.channels_with_sufficient_liquidity.iter().chain(result.channel_that_rejected_payment.iter()).peekable(); + while hops.peek().is_some() { + // Sadly, we have to munge the `DirectedChannel` into an LDK `RouteHop`. + let hop = hops.next().unwrap(); + path.hops.push(lightning::routing::router::RouteHop { + pubkey: hop.dst_node_id.try_into().unwrap(), + node_features: lightning::types::features::NodeFeatures::empty(), + short_channel_id: hop.short_channel_id, + channel_features: lightning::types::features::ChannelFeatures::empty(), + fee_msat: hop.amount_msat - hops.peek().map(|hop| hop.amount_msat).unwrap_or(0), + cltv_expiry_delta: 42, + maybe_announced_channel: true, + }); + } + if let Some(hop) = result.channel_that_rejected_payment { + state.scorer.payment_path_failed(&path, hop.short_channel_id, cur_time); + } else { + state.scorer.payment_path_successful(&path, cur_time); + } } /// This is run after all probe results have been processed, and should be used for printing /// results or any required teardown. pub fn results_complete(state: State) { - + println!("Avg log-loss {}", state.loss_sum / (state.result_count as f64)); } /// A hop in a route, consisting of a channel and the source public key, as well as the amount