Skip to content

Commit ea8b426

Browse files
committed
Track history of where channel liquidities have been in the past
This introduces two new fields to the `ChannelLiquidity` struct - `min_liquidity_offset_history` and `max_liquidity_offset_history`, both an array of 8 `u16`s. Each entry represents the proportion of time that we spent with the min or max liquidity offset in the given 1/8th of the channel's liquidity range. ie the first bucket in `min_liquidity_offset_history` represents the proportion of time we've thought the channel's minimum liquidity is lower than 1/8th's the channel's capacity. Each bucket is stored, effectively, as a fixed-point number with 5 bits for the fractional part, which is incremented by one (ie 32) each time we update our liquidity estimates and decide our estimates are in that bucket. We then decay each bucket by 2047/2048. Thus, memory of a payment sticks around for more than 8,000 data points, though the majority of that memory decays after 1,387 data points.
1 parent 09e0ad5 commit ea8b426

File tree

2 files changed

+113
-20
lines changed

2 files changed

+113
-20
lines changed

lightning/src/routing/scoring.rs

+90-20
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ use util::time::Time;
6464
use prelude::*;
6565
use core::fmt;
6666
use core::cell::{RefCell, RefMut};
67+
use core::convert::TryInto;
6768
use core::ops::{Deref, DerefMut};
6869
use core::time::Duration;
6970
use io::{self, Read};
@@ -446,13 +447,18 @@ struct ChannelLiquidity<T: Time> {
446447

447448
/// Time when the liquidity bounds were last modified.
448449
last_updated: T,
450+
451+
min_liquidity_offset_history: [u16; 8],
452+
max_liquidity_offset_history: [u16; 8],
449453
}
450454

451455
/// A snapshot of [`ChannelLiquidity`] in one direction assuming a certain channel capacity and
452456
/// decayed with a given half life.
453-
struct DirectedChannelLiquidity<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> {
457+
struct DirectedChannelLiquidity<L: Deref<Target = u64>, LA: Deref<Target = [u16; 8]>, T: Time, U: Deref<Target = T>> {
454458
min_liquidity_offset_msat: L,
455459
max_liquidity_offset_msat: L,
460+
min_liquidity_offset_history: LA,
461+
max_liquidity_offset_history: LA,
456462
capacity_msat: u64,
457463
last_updated: U,
458464
now: T,
@@ -593,6 +599,8 @@ impl<T: Time> ChannelLiquidity<T> {
593599
Self {
594600
min_liquidity_offset_msat: 0,
595601
max_liquidity_offset_msat: 0,
602+
min_liquidity_offset_history: [0; 8],
603+
max_liquidity_offset_history: [0; 8],
596604
last_updated: T::now(),
597605
}
598606
}
@@ -601,16 +609,21 @@ impl<T: Time> ChannelLiquidity<T> {
601609
/// `capacity_msat`.
602610
fn as_directed(
603611
&self, source: &NodeId, target: &NodeId, capacity_msat: u64, half_life: Duration
604-
) -> DirectedChannelLiquidity<&u64, T, &T> {
605-
let (min_liquidity_offset_msat, max_liquidity_offset_msat) = if source < target {
606-
(&self.min_liquidity_offset_msat, &self.max_liquidity_offset_msat)
607-
} else {
608-
(&self.max_liquidity_offset_msat, &self.min_liquidity_offset_msat)
609-
};
612+
) -> DirectedChannelLiquidity<&u64, &[u16; 8], T, &T> {
613+
let (min_liquidity_offset_msat, max_liquidity_offset_msat, min_liquidity_offset_history, max_liquidity_offset_history) =
614+
if source < target {
615+
(&self.min_liquidity_offset_msat, &self.max_liquidity_offset_msat,
616+
&self.min_liquidity_offset_history, &self.max_liquidity_offset_history)
617+
} else {
618+
(&self.max_liquidity_offset_msat, &self.min_liquidity_offset_msat,
619+
&self.max_liquidity_offset_history, &self.min_liquidity_offset_history)
620+
};
610621

611622
DirectedChannelLiquidity {
612623
min_liquidity_offset_msat,
613624
max_liquidity_offset_msat,
625+
min_liquidity_offset_history,
626+
max_liquidity_offset_history,
614627
capacity_msat,
615628
last_updated: &self.last_updated,
616629
now: T::now(),
@@ -622,16 +635,21 @@ impl<T: Time> ChannelLiquidity<T> {
622635
/// `capacity_msat`.
623636
fn as_directed_mut(
624637
&mut self, source: &NodeId, target: &NodeId, capacity_msat: u64, half_life: Duration
625-
) -> DirectedChannelLiquidity<&mut u64, T, &mut T> {
626-
let (min_liquidity_offset_msat, max_liquidity_offset_msat) = if source < target {
627-
(&mut self.min_liquidity_offset_msat, &mut self.max_liquidity_offset_msat)
628-
} else {
629-
(&mut self.max_liquidity_offset_msat, &mut self.min_liquidity_offset_msat)
630-
};
638+
) -> DirectedChannelLiquidity<&mut u64, &mut [u16; 8], T, &mut T> {
639+
let (min_liquidity_offset_msat, max_liquidity_offset_msat, min_liquidity_offset_history, max_liquidity_offset_history) =
640+
if source < target {
641+
(&mut self.min_liquidity_offset_msat, &mut self.max_liquidity_offset_msat,
642+
&mut self.min_liquidity_offset_history, &mut self.max_liquidity_offset_history)
643+
} else {
644+
(&mut self.max_liquidity_offset_msat, &mut self.min_liquidity_offset_msat,
645+
&mut self.max_liquidity_offset_history, &mut self.min_liquidity_offset_history)
646+
};
631647

632648
DirectedChannelLiquidity {
633649
min_liquidity_offset_msat,
634650
max_liquidity_offset_msat,
651+
min_liquidity_offset_history,
652+
max_liquidity_offset_history,
635653
capacity_msat,
636654
last_updated: &mut self.last_updated,
637655
now: T::now(),
@@ -652,7 +670,7 @@ const PRECISION_LOWER_BOUND_DENOMINATOR: u64 = approx::LOWER_BITS_BOUND;
652670
const AMOUNT_PENALTY_DIVISOR: u64 = 1 << 20;
653671
const BASE_AMOUNT_PENALTY_DIVISOR: u64 = 1 << 30;
654672

655-
impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiquidity<L, T, U> {
673+
impl<L: Deref<Target = u64>, LA: Deref<Target = [u16; 8]>, T: Time, U: Deref<Target = T>> DirectedChannelLiquidity<L, LA, T, U> {
656674
/// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
657675
/// this direction.
658676
fn penalty_msat(&self, amount_msat: u64, params: &ProbabilisticScoringParameters) -> u64 {
@@ -722,7 +740,7 @@ impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiqui
722740
}
723741
}
724742

725-
impl<L: DerefMut<Target = u64>, T: Time, U: DerefMut<Target = T>> DirectedChannelLiquidity<L, T, U> {
743+
impl<L: DerefMut<Target = u64>, LA: DerefMut<Target = [u16; 8]>, T: Time, U: DerefMut<Target = T>> DirectedChannelLiquidity<L, LA, T, U> {
726744
/// Adjusts the channel liquidity balance bounds when failing to route `amount_msat`.
727745
fn failed_at_channel<Log: Deref>(&mut self, amount_msat: u64, chan_descr: fmt::Arguments, logger: &Log) where Log::Target: Logger {
728746
if amount_msat < self.max_liquidity_msat() {
@@ -750,6 +768,43 @@ impl<L: DerefMut<Target = u64>, T: Time, U: DerefMut<Target = T>> DirectedChanne
750768
self.set_max_liquidity_msat(max_liquidity_msat);
751769
}
752770

771+
fn update_history_buckets(&mut self) {
772+
// We have 8 leaky buckets for min and max liquidity. Each bucket tracks the amount of time
773+
// we spend in each bucket as a fixed-point number with 5 bits for the fractional part.
774+
//
775+
// Each time we update our liquidity estimate, we add 32 to the buckets for the current
776+
// min and max liquidity offset positions.
777+
//
778+
// We then decay each bucket by multiplying by 2047/2048. This ensures we can't actually
779+
// reach 65,535 - when we get to 63,457 adding 32 and decaying by 2047/2048 leaves us back
780+
// at 63,457.
781+
//
782+
// In total, this allows us to track data for the last 8,000 or so payments across a given
783+
// channel.
784+
debug_assert!(*self.min_liquidity_offset_msat <= self.capacity_msat);
785+
if let Some(v) = self.min_liquidity_offset_history
786+
.get_mut((self.min_liquidity_offset_msat.saturating_sub(1) * 8 / self.capacity_msat)
787+
.try_into().unwrap_or(32))
788+
{
789+
*v = v.saturating_add(8);
790+
core::mem::drop(v);
791+
for e in self.min_liquidity_offset_history.iter_mut() {
792+
*e = ((*e as u64) * 2047 / 2048) as u16;
793+
}
794+
}
795+
debug_assert!(*self.max_liquidity_offset_msat <= self.capacity_msat);
796+
if let Some(v) = self.max_liquidity_offset_history
797+
.get_mut((self.max_liquidity_offset_msat.saturating_sub(1) * 8 / self.capacity_msat)
798+
.try_into().unwrap_or(32))
799+
{
800+
*v = v.saturating_add(8);
801+
core::mem::drop(v);
802+
for e in self.max_liquidity_offset_history.iter_mut() {
803+
*e = ((*e as u64) * 2047 / 2048) as u16;
804+
}
805+
}
806+
}
807+
753808
/// Adjusts the lower bound of the channel liquidity balance in this direction.
754809
fn set_min_liquidity_msat(&mut self, amount_msat: u64) {
755810
*self.min_liquidity_offset_msat = amount_msat;
@@ -759,6 +814,7 @@ impl<L: DerefMut<Target = u64>, T: Time, U: DerefMut<Target = T>> DirectedChanne
759814
self.decayed_offset_msat(*self.max_liquidity_offset_msat)
760815
};
761816
*self.last_updated = self.now;
817+
self.update_history_buckets();
762818
}
763819

764820
/// Adjusts the upper bound of the channel liquidity balance in this direction.
@@ -770,6 +826,7 @@ impl<L: DerefMut<Target = u64>, T: Time, U: DerefMut<Target = T>> DirectedChanne
770826
self.decayed_offset_msat(*self.min_liquidity_offset_msat)
771827
};
772828
*self.last_updated = self.now;
829+
self.update_history_buckets();
773830
}
774831
}
775832

@@ -1236,7 +1293,9 @@ impl<T: Time> Writeable for ChannelLiquidity<T> {
12361293
let duration_since_epoch = T::duration_since_epoch() - self.last_updated.elapsed();
12371294
write_tlv_fields!(w, {
12381295
(0, self.min_liquidity_offset_msat, required),
1296+
(1, Some(self.min_liquidity_offset_history), option),
12391297
(2, self.max_liquidity_offset_msat, required),
1298+
(3, Some(self.max_liquidity_offset_history), option),
12401299
(4, duration_since_epoch, required),
12411300
});
12421301
Ok(())
@@ -1248,10 +1307,14 @@ impl<T: Time> Readable for ChannelLiquidity<T> {
12481307
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
12491308
let mut min_liquidity_offset_msat = 0;
12501309
let mut max_liquidity_offset_msat = 0;
1310+
let mut min_liquidity_offset_history = Some([0; 8]);
1311+
let mut max_liquidity_offset_history = Some([0; 8]);
12511312
let mut duration_since_epoch = Duration::from_secs(0);
12521313
read_tlv_fields!(r, {
12531314
(0, min_liquidity_offset_msat, required),
1315+
(1, min_liquidity_offset_history, option),
12541316
(2, max_liquidity_offset_msat, required),
1317+
(3, max_liquidity_offset_history, option),
12551318
(4, duration_since_epoch, required),
12561319
});
12571320
// On rust prior to 1.60 `Instant::duration_since` will panic if time goes backwards.
@@ -1269,6 +1332,8 @@ impl<T: Time> Readable for ChannelLiquidity<T> {
12691332
Ok(Self {
12701333
min_liquidity_offset_msat,
12711334
max_liquidity_offset_msat,
1335+
min_liquidity_offset_history: min_liquidity_offset_history.unwrap(),
1336+
max_liquidity_offset_history: max_liquidity_offset_history.unwrap(),
12721337
last_updated,
12731338
})
12741339
}
@@ -1459,11 +1524,13 @@ mod tests {
14591524
let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger)
14601525
.with_channel(42,
14611526
ChannelLiquidity {
1462-
min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, last_updated
1527+
min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, last_updated,
1528+
min_liquidity_offset_history: [0; 8], max_liquidity_offset_history: [0; 8],
14631529
})
14641530
.with_channel(43,
14651531
ChannelLiquidity {
1466-
min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, last_updated
1532+
min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, last_updated,
1533+
min_liquidity_offset_history: [0; 8], max_liquidity_offset_history: [0; 8],
14671534
});
14681535
let source = source_node_id();
14691536
let target = target_node_id();
@@ -1534,7 +1601,8 @@ mod tests {
15341601
let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger)
15351602
.with_channel(42,
15361603
ChannelLiquidity {
1537-
min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, last_updated
1604+
min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, last_updated,
1605+
min_liquidity_offset_history: [0; 8], max_liquidity_offset_history: [0; 8],
15381606
});
15391607
let source = source_node_id();
15401608
let target = target_node_id();
@@ -1592,7 +1660,8 @@ mod tests {
15921660
let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger)
15931661
.with_channel(42,
15941662
ChannelLiquidity {
1595-
min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, last_updated
1663+
min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, last_updated,
1664+
min_liquidity_offset_history: [0; 8], max_liquidity_offset_history: [0; 8],
15961665
});
15971666
let source = source_node_id();
15981667
let target = target_node_id();
@@ -1699,7 +1768,8 @@ mod tests {
16991768
let scorer = ProbabilisticScorer::new(params, &network_graph, &logger)
17001769
.with_channel(42,
17011770
ChannelLiquidity {
1702-
min_liquidity_offset_msat: 40, max_liquidity_offset_msat: 40, last_updated
1771+
min_liquidity_offset_msat: 40, max_liquidity_offset_msat: 40, last_updated,
1772+
min_liquidity_offset_history: [0; 8], max_liquidity_offset_history: [0; 8],
17031773
});
17041774
let source = source_node_id();
17051775
let target = target_node_id();

lightning/src/util/ser.rs

+23
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,29 @@ impl_array!(PUBLIC_KEY_SIZE); // for PublicKey
515515
impl_array!(COMPACT_SIGNATURE_SIZE); // for Signature
516516
impl_array!(1300); // for OnionPacket.hop_data
517517

518+
impl Writeable for [u16; 8] {
519+
#[inline]
520+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
521+
for v in self.iter() {
522+
w.write_all(&v.to_be_bytes())?
523+
}
524+
Ok(())
525+
}
526+
}
527+
528+
impl Readable for [u16; 8] {
529+
#[inline]
530+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
531+
let mut buf = [0u8; 16];
532+
r.read_exact(&mut buf)?;
533+
let mut res = [0u16; 8];
534+
for (idx, v) in res.iter_mut().enumerate() {
535+
*v = (buf[idx] as u16) << 8 | (buf[idx + 1] as u16)
536+
}
537+
Ok(res)
538+
}
539+
}
540+
518541
// HashMap
519542
impl<K, V> Writeable for HashMap<K, V>
520543
where K: Writeable + Eq + Hash,

0 commit comments

Comments
 (0)