Skip to content

Commit 66f11aa

Browse files
committed
Notify scorer of failing payment path and channel
Upon receiving a PaymentPathFailed event, the failing payment may be retried on a different path. To avoid using the channel responsible for the failure, a scorer should be notified of the failure before being used to find a new route. Add a payment_path_failed method to routing::Score and call it in InvoicePayer's event handler.
1 parent 505ebed commit 66f11aa

File tree

4 files changed

+98
-9
lines changed

4 files changed

+98
-9
lines changed

lightning-invoice/src/payment.rs

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
//! # use lightning::ln::msgs::LightningError;
3333
//! # use lightning::routing;
3434
//! # use lightning::routing::network_graph::NodeId;
35-
//! # use lightning::routing::router::{Route, RouteParameters};
35+
//! # use lightning::routing::router::{Route, RouteHop, RouteParameters};
3636
//! # use lightning::util::events::{Event, EventHandler, EventsProvider};
3737
//! # use lightning::util::logger::{Logger, Record};
3838
//! # use lightning_invoice::Invoice;
@@ -70,6 +70,7 @@
7070
//! # fn channel_penalty_msat(
7171
//! # &self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId
7272
//! # ) -> u64 { 0 }
73+
//! # fn payment_path_failed(&mut self, _path: &Vec<RouteHop>, _short_channel_id: u64) {}
7374
//! # }
7475
//! #
7576
//! # struct FakeLogger {};
@@ -124,7 +125,7 @@ use secp256k1::key::PublicKey;
124125

125126
use std::collections::hash_map::{self, HashMap};
126127
use std::ops::Deref;
127-
use std::sync::Mutex;
128+
use std::sync::{Mutex, RwLock};
128129
use std::time::{Duration, SystemTime};
129130

130131
/// A utility for paying [`Invoice]`s.
@@ -138,7 +139,7 @@ where
138139
{
139140
payer: P,
140141
router: R,
141-
scorer: S,
142+
scorer: RwLock<S>,
142143
logger: L,
143144
event_handler: E,
144145
payment_cache: Mutex<HashMap<PaymentHash, usize>>,
@@ -204,14 +205,22 @@ where
204205
Self {
205206
payer,
206207
router,
207-
scorer,
208+
scorer: RwLock::new(scorer),
208209
logger,
209210
event_handler,
210211
payment_cache: Mutex::new(HashMap::new()),
211212
retry_attempts,
212213
}
213214
}
214215

216+
/// Returns a read-only reference to the parameterized [`routing::Score`].
217+
///
218+
/// Useful if the scorer needs to be persisted. Be sure to drop the returned guard immediately
219+
/// after use since retrying failed payment paths require write access.
220+
pub fn scorer(&self) -> std::sync::RwLockReadGuard<'_, S> {
221+
self.scorer.read().unwrap()
222+
}
223+
215224
/// Pays the given [`Invoice`], caching it for later use in case a retry is needed.
216225
pub fn pay_invoice(&self, invoice: &Invoice) -> Result<PaymentId, PaymentError> {
217226
if invoice.amount_milli_satoshis().is_none() {
@@ -258,7 +267,7 @@ where
258267
&payer,
259268
&params,
260269
Some(&first_hops.iter().collect::<Vec<_>>()),
261-
&self.scorer,
270+
&*self.scorer.read().unwrap(),
262271
).map_err(|e| PaymentError::Routing(e))?;
263272

264273
let payment_hash = PaymentHash(invoice.payment_hash().clone().into_inner());
@@ -278,7 +287,8 @@ where
278287
let payer = self.payer.node_id();
279288
let first_hops = self.payer.first_hops();
280289
let route = self.router.find_route(
281-
&payer, &params, Some(&first_hops.iter().collect::<Vec<_>>()), &self.scorer
290+
&payer, &params, Some(&first_hops.iter().collect::<Vec<_>>()),
291+
&*self.scorer.read().unwrap()
282292
).map_err(|e| PaymentError::Routing(e))?;
283293
self.payer.retry_payment(&route, payment_id).map_err(|e| PaymentError::Sending(e))
284294
}
@@ -311,7 +321,13 @@ where
311321
{
312322
fn handle_event(&self, event: &Event) {
313323
match event {
314-
Event::PaymentPathFailed { payment_id, payment_hash, rejected_by_dest, retry, .. } => {
324+
Event::PaymentPathFailed {
325+
payment_id, payment_hash, rejected_by_dest, path, short_channel_id, retry, ..
326+
} => {
327+
if let Some(short_channel_id) = short_channel_id {
328+
self.scorer.write().unwrap().payment_path_failed(path, *short_channel_id);
329+
}
330+
315331
let mut payment_cache = self.payment_cache.lock().unwrap();
316332
let entry = loop {
317333
let entry = payment_cache.entry(*payment_hash);
@@ -862,6 +878,39 @@ mod tests {
862878
}
863879
}
864880

881+
#[test]
882+
fn scores_failed_channel() {
883+
let event_handled = core::cell::RefCell::new(false);
884+
let event_handler = |_: &_| { *event_handled.borrow_mut() = true; };
885+
886+
let payment_preimage = PaymentPreimage([1; 32]);
887+
let invoice = invoice(payment_preimage);
888+
let payment_hash = PaymentHash(invoice.payment_hash().clone().into_inner());
889+
let final_value_msat = invoice.amount_milli_satoshis().unwrap();
890+
let path = TestRouter::path_for_value(final_value_msat);
891+
let short_channel_id = Some(path[0].short_channel_id);
892+
893+
let payer = TestPayer::new();
894+
let router = TestRouter {};
895+
let scorer = TestScorer::new().expect_channel_failure(short_channel_id.unwrap());
896+
let logger = TestLogger::new();
897+
let invoice_payer =
898+
InvoicePayer::new(&payer, router, scorer, &logger, event_handler, RetryAttempts(2));
899+
900+
let payment_id = Some(invoice_payer.pay_invoice(&invoice).unwrap());
901+
let event = Event::PaymentPathFailed {
902+
payment_id,
903+
payment_hash,
904+
network_update: None,
905+
rejected_by_dest: false,
906+
all_paths_failed: false,
907+
path,
908+
short_channel_id,
909+
retry: Some(TestRouter::retry_for_invoice(&invoice)),
910+
};
911+
invoice_payer.handle_event(&event);
912+
}
913+
865914
struct TestRouter;
866915

867916
impl TestRouter {
@@ -933,16 +982,45 @@ mod tests {
933982
}
934983
}
935984

936-
struct TestScorer;
985+
struct TestScorer {
986+
expectations: std::collections::VecDeque<u64>,
987+
}
937988

938989
impl TestScorer {
939-
fn new() -> Self { Self {} }
990+
fn new() -> Self {
991+
Self {
992+
expectations: std::collections::VecDeque::new(),
993+
}
994+
}
995+
996+
fn expect_channel_failure(mut self, short_channel_id: u64) -> Self {
997+
self.expectations.push_back(short_channel_id);
998+
self
999+
}
9401000
}
9411001

9421002
impl routing::Score for TestScorer {
9431003
fn channel_penalty_msat(
9441004
&self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId
9451005
) -> u64 { 0 }
1006+
1007+
fn payment_path_failed(&mut self, _path: &Vec<RouteHop>, short_channel_id: u64) {
1008+
if let Some(expected_short_channel_id) = self.expectations.pop_front() {
1009+
assert_eq!(short_channel_id, expected_short_channel_id);
1010+
}
1011+
}
1012+
}
1013+
1014+
impl Drop for TestScorer {
1015+
fn drop(&mut self) {
1016+
if std::thread::panicking() {
1017+
return;
1018+
}
1019+
1020+
if !self.expectations.is_empty() {
1021+
panic!("Unsatisfied channel failure expectations: {:?}", self.expectations);
1022+
}
1023+
}
9461024
}
9471025

9481026
struct TestPayer {

lightning/src/routing/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub mod router;
1414
pub mod scorer;
1515

1616
use routing::network_graph::NodeId;
17+
use routing::router::RouteHop;
1718

1819
/// An interface used to score payment channels for path finding.
1920
///
@@ -22,4 +23,7 @@ pub trait Score {
2223
/// Returns the fee in msats willing to be paid to avoid routing through the given channel
2324
/// in the direction from `source` to `target`.
2425
fn channel_penalty_msat(&self, short_channel_id: u64, source: &NodeId, target: &NodeId) -> u64;
26+
27+
/// Handles updating channel penalties after failing to route through a channel.
28+
fn payment_path_failed(&mut self, path: &Vec<RouteHop>, short_channel_id: u64);
2529
}

lightning/src/routing/router.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4537,6 +4537,8 @@ mod tests {
45374537
fn channel_penalty_msat(&self, short_channel_id: u64, _source: &NodeId, _target: &NodeId) -> u64 {
45384538
if short_channel_id == self.short_channel_id { u64::max_value() } else { 0 }
45394539
}
4540+
4541+
fn payment_path_failed(&mut self, _path: &Vec<RouteHop>, _short_channel_id: u64) {}
45404542
}
45414543

45424544
struct BadNodeScorer {
@@ -4547,6 +4549,8 @@ mod tests {
45474549
fn channel_penalty_msat(&self, _short_channel_id: u64, _source: &NodeId, target: &NodeId) -> u64 {
45484550
if *target == self.node_id { u64::max_value() } else { 0 }
45494551
}
4552+
4553+
fn payment_path_failed(&mut self, _path: &Vec<RouteHop>, _short_channel_id: u64) {}
45504554
}
45514555

45524556
#[test]

lightning/src/routing/scorer.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
use routing;
4646

4747
use routing::network_graph::NodeId;
48+
use routing::router::RouteHop;
4849

4950
/// [`routing::Score`] implementation that provides reasonable default behavior.
5051
///
@@ -78,4 +79,6 @@ impl routing::Score for Scorer {
7879
) -> u64 {
7980
self.base_penalty_msat
8081
}
82+
83+
fn payment_path_failed(&mut self, _path: &Vec<RouteHop>, _short_channel_id: u64) {}
8184
}

0 commit comments

Comments
 (0)