@@ -64,6 +64,7 @@ use util::time::Time;
64
64
use prelude:: * ;
65
65
use core:: fmt;
66
66
use core:: cell:: { RefCell , RefMut } ;
67
+ use core:: convert:: TryInto ;
67
68
use core:: ops:: { Deref , DerefMut } ;
68
69
use core:: time:: Duration ;
69
70
use io:: { self , Read } ;
@@ -432,6 +433,48 @@ pub struct ProbabilisticScoringParameters {
432
433
pub considered_impossible_penalty_msat : u64 ,
433
434
}
434
435
436
+ /// Tracks the historical state of a distribution as a weighted average of how much time was spent
437
+ /// in each of 8 buckets.
438
+ #[ derive( Clone , Copy ) ]
439
+ struct HistoricalBucketRangeTracker {
440
+ buckets : [ u16 ; 8 ] ,
441
+ }
442
+
443
+ impl HistoricalBucketRangeTracker {
444
+ fn new ( ) -> Self { Self { buckets : [ 0 ; 8 ] } }
445
+ fn track_datapoint ( & mut self , bucket_idx : u8 ) {
446
+ // We have 8 leaky buckets for min and max liquidity. Each bucket tracks the amount of time
447
+ // we spend in each bucket as a 16-bit fixed-point number with a 5 bit fractional part.
448
+ //
449
+ // Each time we update our liquidity estimate, we add 32 (1.0 in our fixed-point system) to
450
+ // the buckets for the current min and max liquidity offset positions.
451
+ //
452
+ // We then decay each bucket by multiplying by 2047/2048 (avoiding dividing by a
453
+ // non-power-of-two). This ensures we can't actually overflow the u16 - when we get to
454
+ // 63,457 adding 32 and decaying by 2047/2048 leaves us back at 63,457.
455
+ //
456
+ // In total, this allows us to track data for the last 8,000 or so payments across a given
457
+ // channel.
458
+ //
459
+ // These constants are a balance - we try to fit in 2 bytes per bucket to reduce overhead,
460
+ // and need to balance having more bits in the decimal part (to ensure decay isn't too
461
+ // non-linear) with having too few bits in the mantissa, causing us to not store very many
462
+ // datapoints.
463
+ //
464
+ // The constants were picked experimentally, selecting a decay amount that restricts us
465
+ // from overflowing buckets without having to cap them manually.
466
+ debug_assert ! ( bucket_idx < 8 ) ;
467
+ if bucket_idx < 8 {
468
+ for e in self . buckets . iter_mut ( ) {
469
+ * e = ( ( * e as u32 ) * 2047 / 2048 ) as u16 ;
470
+ }
471
+ self . buckets [ bucket_idx as usize ] = self . buckets [ bucket_idx as usize ] . saturating_add ( 32 ) ;
472
+ }
473
+ }
474
+ }
475
+
476
+ impl_writeable_tlv_based ! ( HistoricalBucketRangeTracker , { ( 0 , buckets, required) } ) ;
477
+
435
478
/// Accounting for channel liquidity balance uncertainty.
436
479
///
437
480
/// Direction is defined in terms of [`NodeId`] partial ordering, where the source node is the
@@ -446,13 +489,18 @@ struct ChannelLiquidity<T: Time> {
446
489
447
490
/// Time when the liquidity bounds were last modified.
448
491
last_updated : T ,
492
+
493
+ min_liquidity_offset_history : HistoricalBucketRangeTracker ,
494
+ max_liquidity_offset_history : HistoricalBucketRangeTracker ,
449
495
}
450
496
451
497
/// A snapshot of [`ChannelLiquidity`] in one direction assuming a certain channel capacity and
452
498
/// decayed with a given half life.
453
- struct DirectedChannelLiquidity < L : Deref < Target = u64 > , T : Time , U : Deref < Target = T > > {
499
+ struct DirectedChannelLiquidity < L : Deref < Target = u64 > , BRT : Deref < Target = HistoricalBucketRangeTracker > , T : Time , U : Deref < Target = T > > {
454
500
min_liquidity_offset_msat : L ,
455
501
max_liquidity_offset_msat : L ,
502
+ min_liquidity_offset_history : BRT ,
503
+ max_liquidity_offset_history : BRT ,
456
504
capacity_msat : u64 ,
457
505
last_updated : U ,
458
506
now : T ,
@@ -593,6 +641,8 @@ impl<T: Time> ChannelLiquidity<T> {
593
641
Self {
594
642
min_liquidity_offset_msat : 0 ,
595
643
max_liquidity_offset_msat : 0 ,
644
+ min_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
645
+ max_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
596
646
last_updated : T :: now ( ) ,
597
647
}
598
648
}
@@ -601,16 +651,21 @@ impl<T: Time> ChannelLiquidity<T> {
601
651
/// `capacity_msat`.
602
652
fn as_directed (
603
653
& 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
- } ;
654
+ ) -> DirectedChannelLiquidity < & u64 , & HistoricalBucketRangeTracker , T , & T > {
655
+ let ( min_liquidity_offset_msat, max_liquidity_offset_msat, min_liquidity_offset_history, max_liquidity_offset_history) =
656
+ if source < target {
657
+ ( & self . min_liquidity_offset_msat , & self . max_liquidity_offset_msat ,
658
+ & self . min_liquidity_offset_history , & self . max_liquidity_offset_history )
659
+ } else {
660
+ ( & self . max_liquidity_offset_msat , & self . min_liquidity_offset_msat ,
661
+ & self . max_liquidity_offset_history , & self . min_liquidity_offset_history )
662
+ } ;
610
663
611
664
DirectedChannelLiquidity {
612
665
min_liquidity_offset_msat,
613
666
max_liquidity_offset_msat,
667
+ min_liquidity_offset_history,
668
+ max_liquidity_offset_history,
614
669
capacity_msat,
615
670
last_updated : & self . last_updated ,
616
671
now : T :: now ( ) ,
@@ -622,16 +677,21 @@ impl<T: Time> ChannelLiquidity<T> {
622
677
/// `capacity_msat`.
623
678
fn as_directed_mut (
624
679
& 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
- } ;
680
+ ) -> DirectedChannelLiquidity < & mut u64 , & mut HistoricalBucketRangeTracker , T , & mut T > {
681
+ let ( min_liquidity_offset_msat, max_liquidity_offset_msat, min_liquidity_offset_history, max_liquidity_offset_history) =
682
+ if source < target {
683
+ ( & mut self . min_liquidity_offset_msat , & mut self . max_liquidity_offset_msat ,
684
+ & mut self . min_liquidity_offset_history , & mut self . max_liquidity_offset_history )
685
+ } else {
686
+ ( & mut self . max_liquidity_offset_msat , & mut self . min_liquidity_offset_msat ,
687
+ & mut self . max_liquidity_offset_history , & mut self . min_liquidity_offset_history )
688
+ } ;
631
689
632
690
DirectedChannelLiquidity {
633
691
min_liquidity_offset_msat,
634
692
max_liquidity_offset_msat,
693
+ min_liquidity_offset_history,
694
+ max_liquidity_offset_history,
635
695
capacity_msat,
636
696
last_updated : & mut self . last_updated ,
637
697
now : T :: now ( ) ,
@@ -652,7 +712,7 @@ const PRECISION_LOWER_BOUND_DENOMINATOR: u64 = approx::LOWER_BITS_BOUND;
652
712
const AMOUNT_PENALTY_DIVISOR : u64 = 1 << 20 ;
653
713
const BASE_AMOUNT_PENALTY_DIVISOR : u64 = 1 << 30 ;
654
714
655
- impl < L : Deref < Target = u64 > , T : Time , U : Deref < Target = T > > DirectedChannelLiquidity < L , T , U > {
715
+ impl < L : Deref < Target = u64 > , BRT : Deref < Target = HistoricalBucketRangeTracker > , T : Time , U : Deref < Target = T > > DirectedChannelLiquidity < L , BRT , T , U > {
656
716
/// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
657
717
/// this direction.
658
718
fn penalty_msat ( & self , amount_msat : u64 , params : & ProbabilisticScoringParameters ) -> u64 {
@@ -722,7 +782,7 @@ impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiqui
722
782
}
723
783
}
724
784
725
- impl < L : DerefMut < Target = u64 > , T : Time , U : DerefMut < Target = T > > DirectedChannelLiquidity < L , T , U > {
785
+ impl < L : DerefMut < Target = u64 > , BRT : DerefMut < Target = HistoricalBucketRangeTracker > , T : Time , U : DerefMut < Target = T > > DirectedChannelLiquidity < L , BRT , T , U > {
726
786
/// Adjusts the channel liquidity balance bounds when failing to route `amount_msat`.
727
787
fn failed_at_channel < Log : Deref > ( & mut self , amount_msat : u64 , chan_descr : fmt:: Arguments , logger : & Log ) where Log :: Target : Logger {
728
788
if amount_msat < self . max_liquidity_msat ( ) {
@@ -750,6 +810,21 @@ impl<L: DerefMut<Target = u64>, T: Time, U: DerefMut<Target = T>> DirectedChanne
750
810
self . set_max_liquidity_msat ( max_liquidity_msat) ;
751
811
}
752
812
813
+ fn update_history_buckets ( & mut self ) {
814
+ debug_assert ! ( * self . min_liquidity_offset_msat <= self . capacity_msat) ;
815
+ self . min_liquidity_offset_history . track_datapoint (
816
+ // Ensure the bucket index we pass is in the range [0, 7], even if the liquidity offset
817
+ // is zero or the channel's capacity, though the second should generally never happen.
818
+ ( self . min_liquidity_offset_msat . saturating_sub ( 1 ) * 8 / self . capacity_msat )
819
+ . try_into ( ) . unwrap_or ( 32 ) ) ; // 32 is bogus for 8 buckets, and will be ignored
820
+ debug_assert ! ( * self . max_liquidity_offset_msat <= self . capacity_msat) ;
821
+ self . max_liquidity_offset_history . track_datapoint (
822
+ // Ensure the bucket index we pass is in the range [0, 7], even if the liquidity offset
823
+ // is zero or the channel's capacity, though the second should generally never happen.
824
+ ( self . max_liquidity_offset_msat . saturating_sub ( 1 ) * 8 / self . capacity_msat )
825
+ . try_into ( ) . unwrap_or ( 32 ) ) ; // 32 is bogus for 8 buckets, and will be ignored
826
+ }
827
+
753
828
/// Adjusts the lower bound of the channel liquidity balance in this direction.
754
829
fn set_min_liquidity_msat ( & mut self , amount_msat : u64 ) {
755
830
* self . min_liquidity_offset_msat = amount_msat;
@@ -759,6 +834,7 @@ impl<L: DerefMut<Target = u64>, T: Time, U: DerefMut<Target = T>> DirectedChanne
759
834
self . decayed_offset_msat ( * self . max_liquidity_offset_msat )
760
835
} ;
761
836
* self . last_updated = self . now ;
837
+ self . update_history_buckets ( ) ;
762
838
}
763
839
764
840
/// Adjusts the upper bound of the channel liquidity balance in this direction.
@@ -770,6 +846,7 @@ impl<L: DerefMut<Target = u64>, T: Time, U: DerefMut<Target = T>> DirectedChanne
770
846
self . decayed_offset_msat ( * self . min_liquidity_offset_msat )
771
847
} ;
772
848
* self . last_updated = self . now ;
849
+ self . update_history_buckets ( ) ;
773
850
}
774
851
}
775
852
@@ -1236,7 +1313,9 @@ impl<T: Time> Writeable for ChannelLiquidity<T> {
1236
1313
let duration_since_epoch = T :: duration_since_epoch ( ) - self . last_updated . elapsed ( ) ;
1237
1314
write_tlv_fields ! ( w, {
1238
1315
( 0 , self . min_liquidity_offset_msat, required) ,
1316
+ ( 1 , Some ( self . min_liquidity_offset_history) , option) ,
1239
1317
( 2 , self . max_liquidity_offset_msat, required) ,
1318
+ ( 3 , Some ( self . max_liquidity_offset_history) , option) ,
1240
1319
( 4 , duration_since_epoch, required) ,
1241
1320
} ) ;
1242
1321
Ok ( ( ) )
@@ -1248,10 +1327,14 @@ impl<T: Time> Readable for ChannelLiquidity<T> {
1248
1327
fn read < R : Read > ( r : & mut R ) -> Result < Self , DecodeError > {
1249
1328
let mut min_liquidity_offset_msat = 0 ;
1250
1329
let mut max_liquidity_offset_msat = 0 ;
1330
+ let mut min_liquidity_offset_history = Some ( HistoricalBucketRangeTracker :: new ( ) ) ;
1331
+ let mut max_liquidity_offset_history = Some ( HistoricalBucketRangeTracker :: new ( ) ) ;
1251
1332
let mut duration_since_epoch = Duration :: from_secs ( 0 ) ;
1252
1333
read_tlv_fields ! ( r, {
1253
1334
( 0 , min_liquidity_offset_msat, required) ,
1335
+ ( 1 , min_liquidity_offset_history, option) ,
1254
1336
( 2 , max_liquidity_offset_msat, required) ,
1337
+ ( 3 , max_liquidity_offset_history, option) ,
1255
1338
( 4 , duration_since_epoch, required) ,
1256
1339
} ) ;
1257
1340
// On rust prior to 1.60 `Instant::duration_since` will panic if time goes backwards.
@@ -1269,14 +1352,16 @@ impl<T: Time> Readable for ChannelLiquidity<T> {
1269
1352
Ok ( Self {
1270
1353
min_liquidity_offset_msat,
1271
1354
max_liquidity_offset_msat,
1355
+ min_liquidity_offset_history : min_liquidity_offset_history. unwrap ( ) ,
1356
+ max_liquidity_offset_history : max_liquidity_offset_history. unwrap ( ) ,
1272
1357
last_updated,
1273
1358
} )
1274
1359
}
1275
1360
}
1276
1361
1277
1362
#[ cfg( test) ]
1278
1363
mod tests {
1279
- use super :: { ChannelLiquidity , ProbabilisticScoringParameters , ProbabilisticScorerUsingTime } ;
1364
+ use super :: { ChannelLiquidity , HistoricalBucketRangeTracker , ProbabilisticScoringParameters , ProbabilisticScorerUsingTime } ;
1280
1365
use util:: time:: Time ;
1281
1366
use util:: time:: tests:: SinceEpoch ;
1282
1367
@@ -1459,11 +1544,15 @@ mod tests {
1459
1544
let mut scorer = ProbabilisticScorer :: new ( params, & network_graph, & logger)
1460
1545
. with_channel ( 42 ,
1461
1546
ChannelLiquidity {
1462
- min_liquidity_offset_msat : 700 , max_liquidity_offset_msat : 100 , last_updated
1547
+ min_liquidity_offset_msat : 700 , max_liquidity_offset_msat : 100 , last_updated,
1548
+ min_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1549
+ max_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1463
1550
} )
1464
1551
. with_channel ( 43 ,
1465
1552
ChannelLiquidity {
1466
- min_liquidity_offset_msat : 700 , max_liquidity_offset_msat : 100 , last_updated
1553
+ min_liquidity_offset_msat : 700 , max_liquidity_offset_msat : 100 , last_updated,
1554
+ min_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1555
+ max_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1467
1556
} ) ;
1468
1557
let source = source_node_id ( ) ;
1469
1558
let target = target_node_id ( ) ;
@@ -1534,7 +1623,9 @@ mod tests {
1534
1623
let mut scorer = ProbabilisticScorer :: new ( params, & network_graph, & logger)
1535
1624
. with_channel ( 42 ,
1536
1625
ChannelLiquidity {
1537
- min_liquidity_offset_msat : 200 , max_liquidity_offset_msat : 400 , last_updated
1626
+ min_liquidity_offset_msat : 200 , max_liquidity_offset_msat : 400 , last_updated,
1627
+ min_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1628
+ max_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1538
1629
} ) ;
1539
1630
let source = source_node_id ( ) ;
1540
1631
let target = target_node_id ( ) ;
@@ -1592,7 +1683,9 @@ mod tests {
1592
1683
let mut scorer = ProbabilisticScorer :: new ( params, & network_graph, & logger)
1593
1684
. with_channel ( 42 ,
1594
1685
ChannelLiquidity {
1595
- min_liquidity_offset_msat : 200 , max_liquidity_offset_msat : 400 , last_updated
1686
+ min_liquidity_offset_msat : 200 , max_liquidity_offset_msat : 400 , last_updated,
1687
+ min_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1688
+ max_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1596
1689
} ) ;
1597
1690
let source = source_node_id ( ) ;
1598
1691
let target = target_node_id ( ) ;
@@ -1699,7 +1792,9 @@ mod tests {
1699
1792
let scorer = ProbabilisticScorer :: new ( params, & network_graph, & logger)
1700
1793
. with_channel ( 42 ,
1701
1794
ChannelLiquidity {
1702
- min_liquidity_offset_msat : 40 , max_liquidity_offset_msat : 40 , last_updated
1795
+ min_liquidity_offset_msat : 40 , max_liquidity_offset_msat : 40 , last_updated,
1796
+ min_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1797
+ max_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1703
1798
} ) ;
1704
1799
let source = source_node_id ( ) ;
1705
1800
let target = target_node_id ( ) ;
0 commit comments