]> git.bitcoin.ninja Git - ln-routing-replay/commitdiff
Demonstrate testing the LDK scorer
authorMatt Corallo <git@bluematt.me>
Thu, 21 Nov 2024 22:38:57 +0000 (22:38 +0000)
committerMatt Corallo <git@bluematt.me>
Fri, 22 Nov 2024 14:14:30 +0000 (14:14 +0000)
Note that this requires LDK PR #3420

Cargo.toml
src/main.rs

index 50abd8cb8605196d2d1facdec97bbfc1ee5cff57..1c29d8eaece5aa8d0c948c2aa862372772dc3fcc 100644 (file)
@@ -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" }
index 0d82fc1632e08079eee26b85406064ed843bcfb4..2a415f1fe8cc589dd1ab156a0a05a2c1bada2661 100644 (file)
@@ -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>, 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<DevNullLogger>) -> 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<DevNullLogger>) -> 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