Skip to content

Commit

Permalink
Merge pull request lightningdevkit#1158 from jkczyz/2021-11-scorer-tests
Browse files Browse the repository at this point in the history
Scorer unit tests
  • Loading branch information
TheBlueMatt authored Nov 8, 2021
2 parents d2f401a + b57ed79 commit 6f053e4
Showing 1 changed file with 230 additions and 0 deletions.
230 changes: 230 additions & 0 deletions lightning/src/routing/scorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ impl Time for std::time::Instant {
}

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

impl Time for Eternity {
Expand Down Expand Up @@ -320,3 +321,232 @@ impl<T: Time> Readable for ChannelFailure<T> {
})
}
}

#[cfg(test)]
mod tests {
use super::{Eternity, ScoringParameters, ScorerUsingTime, Time};

use routing::Score;
use routing::network_graph::NodeId;
use util::ser::{Readable, Writeable};

use bitcoin::secp256k1::PublicKey;
use core::cell::Cell;
use core::ops::Sub;
use core::time::Duration;
use io;

/// Time that can be advanced manually in tests.
#[derive(Debug, PartialEq, Eq)]
struct SinceEpoch(Duration);

impl SinceEpoch {
thread_local! {
static ELAPSED: Cell<Duration> = core::cell::Cell::new(Duration::from_secs(0));
}

fn advance(duration: Duration) {
Self::ELAPSED.with(|elapsed| elapsed.set(elapsed.get() + duration))
}
}

impl Time for SinceEpoch {
fn now() -> Self {
Self(Self::duration_since_epoch())
}

fn duration_since_epoch() -> Duration {
Self::ELAPSED.with(|elapsed| elapsed.get())
}

fn elapsed(&self) -> Duration {
Self::duration_since_epoch() - self.0
}
}

impl Sub<Duration> for SinceEpoch {
type Output = Self;

fn sub(self, other: Duration) -> Self {
Self(self.0 - other)
}
}

#[test]
fn time_passes_when_advanced() {
let now = SinceEpoch::now();
assert_eq!(now.elapsed(), Duration::from_secs(0));

SinceEpoch::advance(Duration::from_secs(1));
SinceEpoch::advance(Duration::from_secs(1));

let elapsed = now.elapsed();
let later = SinceEpoch::now();

assert_eq!(elapsed, Duration::from_secs(2));
assert_eq!(later - elapsed, now);
}

#[test]
fn time_never_passes_in_an_eternity() {
let now = Eternity::now();
let elapsed = now.elapsed();
let later = Eternity::now();

assert_eq!(now.elapsed(), Duration::from_secs(0));
assert_eq!(later - elapsed, now);
}

/// A scorer for testing with time that can be manually advanced.
type Scorer = ScorerUsingTime::<SinceEpoch>;

fn source_node_id() -> NodeId {
NodeId::from_pubkey(&PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap())
}

fn target_node_id() -> NodeId {
NodeId::from_pubkey(&PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap())
}

#[test]
fn penalizes_without_channel_failures() {
let scorer = Scorer::new(ScoringParameters {
base_penalty_msat: 1_000,
failure_penalty_msat: 512,
failure_penalty_half_life: Duration::from_secs(1),
});
let source = source_node_id();
let target = target_node_id();
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000);

SinceEpoch::advance(Duration::from_secs(1));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000);
}

#[test]
fn accumulates_channel_failure_penalties() {
let mut scorer = Scorer::new(ScoringParameters {
base_penalty_msat: 1_000,
failure_penalty_msat: 64,
failure_penalty_half_life: Duration::from_secs(10),
});
let source = source_node_id();
let target = target_node_id();
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000);

scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_064);

scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_128);

scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_192);
}

#[test]
fn decays_channel_failure_penalties_over_time() {
let mut scorer = Scorer::new(ScoringParameters {
base_penalty_msat: 1_000,
failure_penalty_msat: 512,
failure_penalty_half_life: Duration::from_secs(10),
});
let source = source_node_id();
let target = target_node_id();
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000);

scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_512);

SinceEpoch::advance(Duration::from_secs(9));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_512);

SinceEpoch::advance(Duration::from_secs(1));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_256);

SinceEpoch::advance(Duration::from_secs(10 * 8));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_001);

SinceEpoch::advance(Duration::from_secs(10));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000);

SinceEpoch::advance(Duration::from_secs(10));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000);
}

#[test]
fn accumulates_channel_failure_penalties_after_decay() {
let mut scorer = Scorer::new(ScoringParameters {
base_penalty_msat: 1_000,
failure_penalty_msat: 512,
failure_penalty_half_life: Duration::from_secs(10),
});
let source = source_node_id();
let target = target_node_id();
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_000);

scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_512);

SinceEpoch::advance(Duration::from_secs(10));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_256);

scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_768);

SinceEpoch::advance(Duration::from_secs(10));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_384);
}

#[test]
fn restores_persisted_channel_failure_penalties() {
let mut scorer = Scorer::new(ScoringParameters {
base_penalty_msat: 1_000,
failure_penalty_msat: 512,
failure_penalty_half_life: Duration::from_secs(10),
});
let source = source_node_id();
let target = target_node_id();

scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_512);

SinceEpoch::advance(Duration::from_secs(10));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_256);

scorer.payment_path_failed(&[], 43);
assert_eq!(scorer.channel_penalty_msat(43, &source, &target), 1_512);

let mut serialized_scorer = Vec::new();
scorer.write(&mut serialized_scorer).unwrap();

let deserialized_scorer = <Scorer>::read(&mut io::Cursor::new(&serialized_scorer)).unwrap();
assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target), 1_256);
assert_eq!(deserialized_scorer.channel_penalty_msat(43, &source, &target), 1_512);
}

#[test]
fn decays_persisted_channel_failure_penalties() {
let mut scorer = Scorer::new(ScoringParameters {
base_penalty_msat: 1_000,
failure_penalty_msat: 512,
failure_penalty_half_life: Duration::from_secs(10),
});
let source = source_node_id();
let target = target_node_id();

scorer.payment_path_failed(&[], 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target), 1_512);

let mut serialized_scorer = Vec::new();
scorer.write(&mut serialized_scorer).unwrap();

SinceEpoch::advance(Duration::from_secs(10));

let deserialized_scorer = <Scorer>::read(&mut io::Cursor::new(&serialized_scorer)).unwrap();
assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target), 1_256);

SinceEpoch::advance(Duration::from_secs(10));
assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target), 1_128);
}
}

0 comments on commit 6f053e4

Please sign in to comment.