Skip to content

Commit 6f053e4

Browse files
authored
Merge pull request #1158 from jkczyz/2021-11-scorer-tests
Scorer unit tests
2 parents d2f401a + b57ed79 commit 6f053e4

File tree

1 file changed

+230
-0
lines changed

1 file changed

+230
-0
lines changed

lightning/src/routing/scorer.rs

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ impl Time for std::time::Instant {
247247
}
248248

249249
/// A state in which time has no meaning.
250+
#[derive(Debug, PartialEq, Eq)]
250251
pub struct Eternity;
251252

252253
impl Time for Eternity {
@@ -320,3 +321,232 @@ impl<T: Time> Readable for ChannelFailure<T> {
320321
})
321322
}
322323
}
324+
325+
#[cfg(test)]
326+
mod tests {
327+
use super::{Eternity, ScoringParameters, ScorerUsingTime, Time};
328+
329+
use routing::Score;
330+
use routing::network_graph::NodeId;
331+
use util::ser::{Readable, Writeable};
332+
333+
use bitcoin::secp256k1::PublicKey;
334+
use core::cell::Cell;
335+
use core::ops::Sub;
336+
use core::time::Duration;
337+
use io;
338+
339+
/// Time that can be advanced manually in tests.
340+
#[derive(Debug, PartialEq, Eq)]
341+
struct SinceEpoch(Duration);
342+
343+
impl SinceEpoch {
344+
thread_local! {
345+
static ELAPSED: Cell<Duration> = core::cell::Cell::new(Duration::from_secs(0));
346+
}
347+
348+
fn advance(duration: Duration) {
349+
Self::ELAPSED.with(|elapsed| elapsed.set(elapsed.get() + duration))
350+
}
351+
}
352+
353+
impl Time for SinceEpoch {
354+
fn now() -> Self {
355+
Self(Self::duration_since_epoch())
356+
}
357+
358+
fn duration_since_epoch() -> Duration {
359+
Self::ELAPSED.with(|elapsed| elapsed.get())
360+
}
361+
362+
fn elapsed(&self) -> Duration {
363+
Self::duration_since_epoch() - self.0
364+
}
365+
}
366+
367+
impl Sub<Duration> for SinceEpoch {
368+
type Output = Self;
369+
370+
fn sub(self, other: Duration) -> Self {
371+
Self(self.0 - other)
372+
}
373+
}
374+
375+
#[test]
376+
fn time_passes_when_advanced() {
377+
let now = SinceEpoch::now();
378+
assert_eq!(now.elapsed(), Duration::from_secs(0));
379+
380+
SinceEpoch::advance(Duration::from_secs(1));
381+
SinceEpoch::advance(Duration::from_secs(1));
382+
383+
let elapsed = now.elapsed();
384+
let later = SinceEpoch::now();
385+
386+
assert_eq!(elapsed, Duration::from_secs(2));
387+
assert_eq!(later - elapsed, now);
388+
}
389+
390+
#[test]
391+
fn time_never_passes_in_an_eternity() {
392+
let now = Eternity::now();
393+
let elapsed = now.elapsed();
394+
let later = Eternity::now();
395+
396+
assert_eq!(now.elapsed(), Duration::from_secs(0));
397+
assert_eq!(later - elapsed, now);
398+
}
399+
400+
/// A scorer for testing with time that can be manually advanced.
401+
type Scorer = ScorerUsingTime::<SinceEpoch>;
402+
403+
fn source_node_id() -> NodeId {
404+
NodeId::from_pubkey(&PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap())
405+
}
406+
407+
fn target_node_id() -> NodeId {
408+
NodeId::from_pubkey(&PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap())
409+
}
410+
411+
#[test]
412+
fn penalizes_without_channel_failures() {
413+
let scorer = Scorer::new(ScoringParameters {
414+
base_penalty_msat: 1_000,
415+
failure_penalty_msat: 512,
416+
failure_penalty_half_life: Duration::from_secs(1),
417+
});
418+
let source = source_node_id();
419+
let target = target_node_id();
420+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000);
421+
422+
SinceEpoch::advance(Duration::from_secs(1));
423+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000);
424+
}
425+
426+
#[test]
427+
fn accumulates_channel_failure_penalties() {
428+
let mut scorer = Scorer::new(ScoringParameters {
429+
base_penalty_msat: 1_000,
430+
failure_penalty_msat: 64,
431+
failure_penalty_half_life: Duration::from_secs(10),
432+
});
433+
let source = source_node_id();
434+
let target = target_node_id();
435+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000);
436+
437+
scorer.payment_path_failed(&[], 42);
438+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_064);
439+
440+
scorer.payment_path_failed(&[], 42);
441+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_128);
442+
443+
scorer.payment_path_failed(&[], 42);
444+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_192);
445+
}
446+
447+
#[test]
448+
fn decays_channel_failure_penalties_over_time() {
449+
let mut scorer = Scorer::new(ScoringParameters {
450+
base_penalty_msat: 1_000,
451+
failure_penalty_msat: 512,
452+
failure_penalty_half_life: Duration::from_secs(10),
453+
});
454+
let source = source_node_id();
455+
let target = target_node_id();
456+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000);
457+
458+
scorer.payment_path_failed(&[], 42);
459+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_512);
460+
461+
SinceEpoch::advance(Duration::from_secs(9));
462+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_512);
463+
464+
SinceEpoch::advance(Duration::from_secs(1));
465+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_256);
466+
467+
SinceEpoch::advance(Duration::from_secs(10 * 8));
468+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_001);
469+
470+
SinceEpoch::advance(Duration::from_secs(10));
471+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000);
472+
473+
SinceEpoch::advance(Duration::from_secs(10));
474+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000);
475+
}
476+
477+
#[test]
478+
fn accumulates_channel_failure_penalties_after_decay() {
479+
let mut scorer = Scorer::new(ScoringParameters {
480+
base_penalty_msat: 1_000,
481+
failure_penalty_msat: 512,
482+
failure_penalty_half_life: Duration::from_secs(10),
483+
});
484+
let source = source_node_id();
485+
let target = target_node_id();
486+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000);
487+
488+
scorer.payment_path_failed(&[], 42);
489+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_512);
490+
491+
SinceEpoch::advance(Duration::from_secs(10));
492+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_256);
493+
494+
scorer.payment_path_failed(&[], 42);
495+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_768);
496+
497+
SinceEpoch::advance(Duration::from_secs(10));
498+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_384);
499+
}
500+
501+
#[test]
502+
fn restores_persisted_channel_failure_penalties() {
503+
let mut scorer = Scorer::new(ScoringParameters {
504+
base_penalty_msat: 1_000,
505+
failure_penalty_msat: 512,
506+
failure_penalty_half_life: Duration::from_secs(10),
507+
});
508+
let source = source_node_id();
509+
let target = target_node_id();
510+
511+
scorer.payment_path_failed(&[], 42);
512+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_512);
513+
514+
SinceEpoch::advance(Duration::from_secs(10));
515+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_256);
516+
517+
scorer.payment_path_failed(&[], 43);
518+
assert_eq!(scorer.channel_penalty_msat(43, &source, &target), 1_512);
519+
520+
let mut serialized_scorer = Vec::new();
521+
scorer.write(&mut serialized_scorer).unwrap();
522+
523+
let deserialized_scorer = <Scorer>::read(&mut io::Cursor::new(&serialized_scorer)).unwrap();
524+
assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target), 1_256);
525+
assert_eq!(deserialized_scorer.channel_penalty_msat(43, &source, &target), 1_512);
526+
}
527+
528+
#[test]
529+
fn decays_persisted_channel_failure_penalties() {
530+
let mut scorer = Scorer::new(ScoringParameters {
531+
base_penalty_msat: 1_000,
532+
failure_penalty_msat: 512,
533+
failure_penalty_half_life: Duration::from_secs(10),
534+
});
535+
let source = source_node_id();
536+
let target = target_node_id();
537+
538+
scorer.payment_path_failed(&[], 42);
539+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_512);
540+
541+
let mut serialized_scorer = Vec::new();
542+
scorer.write(&mut serialized_scorer).unwrap();
543+
544+
SinceEpoch::advance(Duration::from_secs(10));
545+
546+
let deserialized_scorer = <Scorer>::read(&mut io::Cursor::new(&serialized_scorer)).unwrap();
547+
assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target), 1_256);
548+
549+
SinceEpoch::advance(Duration::from_secs(10));
550+
assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target), 1_128);
551+
}
552+
}

0 commit comments

Comments
 (0)