11#include "config.h"
2+ #include <ccan/asort/asort.h>
23#include <common/gossmap.h>
4+ #include <math.h>
35#include <plugins/askrene/child/additional_costs.h>
6+ #include <plugins/askrene/child/mcf.h>
47#include <plugins/askrene/child/route_query.h>
58#include <plugins/askrene/layer.h>
69#include <plugins/askrene/reserve.h>
710
11+ /* Lifetime of liquidity bounds is one day. "Lifetime" in the sense of the
12+ * exponential time decay: the time it takes for the liquidity lower bound to be
13+ * reduced by half is "lifetime" times ln(2) ~ 16 hours. */
14+ #define ASKRENE_RELAX_TIME_SECS 86400
15+
16+ /* It could be any number between 0 and 1. It represents the fraction of lower
17+ * liquidity bound that we adjust when we find a failure. The smaller it is the
18+ * more we trust previous knowledge. Similar to a "learning velocity" for AI. */
19+ #define ASKRENE_FAILURE_RELAX_FRACTION 0.5
20+
821struct amount_msat get_additional_per_htlc_cost (const struct route_query * rq ,
922 const struct short_channel_id_dir * scidd )
1023{
@@ -16,6 +29,169 @@ struct amount_msat get_additional_per_htlc_cost(const struct route_query *rq,
1629 return AMOUNT_MSAT (0 );
1730}
1831
32+ static int constraint_cmp (const struct constraint * a ,
33+ const struct constraint * b , void * unused )
34+ {
35+ if (a -> timestamp < b -> timestamp )
36+ return -1 ;
37+ if (a -> timestamp > b -> timestamp )
38+ return 1 ;
39+ return 0 ;
40+ }
41+
42+ /* Like a capacitor discharging, this is the physical process of information
43+ * getting older and entropy increasing. It satisfies the semigroup property so
44+ * it is a well defined Markovian time evolution operation. Same for the
45+ * "charge" operation.
46+ * Dicharge(x, t) = x * exp(-t/lifetime)
47+ * Charge(x, t) = C - (C -x) * exp(-t/lifetime)
48+ *
49+ * Discharge(x, t1+t2) = Discharge(Discharge(x, t1), t2),
50+ * Charge(x, t1+t2) = Charge(Charge(x, t1), t2),
51+ *
52+ * @min: apply discharge to it,
53+ * @max: apply charge to it,
54+ * @capacity: "capacitor"'s capacity,
55+ * @time_delta: time interval in seconds, 0-> nothing changes, infinity->full
56+ * charge/discharge
57+ * @lifetime: characteristic time of the system in seconds, ie. min is reduced
58+ * by half after ln(2)*lifetime seconds.
59+ */
60+ // FIXME: unit test
61+ static void exponential_time_charge_discharge (struct amount_msat * min ,
62+ struct amount_msat * max ,
63+ const struct amount_msat capacity ,
64+ const u64 time_delta ,
65+ const u64 lifetime )
66+ {
67+ double factor = exp ((-1.0 * time_delta ) / lifetime );
68+ struct amount_msat residual ;
69+
70+ if (!amount_msat_scale (min , * min , factor ))
71+ goto fail ;
72+
73+ if (!amount_msat_sub (& residual , capacity , * max ))
74+ goto fail ;
75+ if (!amount_msat_scale (& residual , residual , factor ))
76+ goto fail ;
77+ if (!amount_msat_sub (max , capacity , residual ))
78+ goto fail ;
79+
80+ fail :
81+ /* It should not fail, but if it does, we default to 0 knowledge. */
82+ * min = AMOUNT_MSAT (0 );
83+ * max = capacity ;
84+ }
85+
86+ /* Computes min/max bounds based on known constraints. It self-adjusts for
87+ * contradictory information giving precedence to more recent constraints. Time
88+ * decay is considered.
89+ * FIXME: this approach was completey cooked by hand because it is better than
90+ * simply trusting all constraints as we have seen during tests (see CLN #9282).
91+ * However it would be nice to have a theoretically sound adjustment, eg.
92+ * Maximum Likelyhood, if applicable. */
93+ // FIXME: unit test
94+ static void constraints_get_bounds_selfadjust (struct constraint * constraints ,
95+ const struct amount_msat capacity ,
96+ struct amount_msat * min ,
97+ struct amount_msat * max )
98+ {
99+ assert (ASKRENE_FAILURE_RELAX_FRACTION >= 0.0 &&
100+ ASKRENE_FAILURE_RELAX_FRACTION <= 1.0 );
101+ u64 last_timestamp = 0 , delta ;
102+ double prob_fail ;
103+ struct amount_msat x , amount , high ;
104+
105+ * min = AMOUNT_MSAT (0 );
106+ * max = capacity ;
107+ asort (constraints , tal_count (constraints ), constraint_cmp , NULL );
108+ for (size_t i = 0 ; i < tal_count (constraints ); i ++ ) {
109+ assert (constraints [i ].timestamp >= last_timestamp );
110+ delta = constraints [i ].timestamp - last_timestamp ;
111+ last_timestamp = constraints [i ].timestamp ;
112+
113+ /* time relax the bound we carry */
114+ exponential_time_charge_discharge (min , max , capacity , delta ,
115+ ASKRENE_RELAX_TIME_SECS );
116+
117+ if (amount_msat_greater_eq (constraints [i ].max ,
118+ AMOUNT_MSAT (UINT64_MAX ))) {
119+ /* this is an "unconstrained" event, a min value bound
120+ */
121+ x = constraints [i ].min ;
122+ * min = amount_msat_max (* min , x );
123+ * max = amount_msat_max (* max , x );
124+ } else {
125+ /* this is a "constrained" event, a max value bound */
126+ x = constraints [i ].max ;
127+ if (amount_msat_greater (x , * max )) {
128+ /* Trivial case, we were expecting x to fail. */
129+ } else if (amount_msat_less (x , * min )) {
130+ /* This should have succeeded 100% of the times,
131+ * our knowledge was wrong. */
132+ * min = amount_msat_min (* min , x );
133+ * max = amount_msat_min (* max , x );
134+ if (!amount_msat_scale (
135+ min , * min ,
136+ 1.0 - ASKRENE_FAILURE_RELAX_FRACTION )) {
137+ * min = AMOUNT_MSAT (0 );
138+ }
139+ } else {
140+ /* We got failure for a quantity between min and
141+ * max bounds. We relax a little the lower bound
142+ * in relation to the probability of this event
143+ * taking place. If p~1 this was expected,
144+ * min/max reflected reality. On the other hand
145+ * if p~0, we were either unlucky or more likely
146+ * our lower bound was too high. */
147+
148+ /* off-by-one because the high bound in MCF
149+ * means "we know the liquidity is below this
150+ * value", which makes some equations take a
151+ * simpler form. */
152+ if (!amount_msat_add (& high , * max ,
153+ AMOUNT_MSAT (1 )))
154+ high = capacity ;
155+ /* off-by-one because
156+ * json_askrene_inform_channel already
157+ * substracted 1msat here, meaning we tried x+1
158+ * and it failed. */
159+ if (!amount_msat_add (& amount , x ,
160+ AMOUNT_MSAT (1 )))
161+ amount = capacity ;
162+ prob_fail = 1.0 - pickhardt_richter_probability (
163+ * min , high , amount );
164+ assert (prob_fail >= 0 && prob_fail <= 1.0 );
165+
166+ * max = amount_msat_min (* max , x );
167+ if (!amount_msat_scale (
168+ min , * min ,
169+ 1.0 + ASKRENE_FAILURE_RELAX_FRACTION *
170+ (prob_fail - 1.0 ))) {
171+ * min = AMOUNT_MSAT (0 );
172+ }
173+ }
174+ }
175+ }
176+ }
177+
178+ /* Constraints produced bounds this way since askrene was first written. We keep
179+ * it around momentarily. */
180+ // static void constraints_get_bounds_legacy(const struct constraint *constraints,
181+ // const struct amount_msat capacity,
182+ // struct amount_msat *min,
183+ // struct amount_msat *max)
184+ // {
185+ // *min = AMOUNT_MSAT(0);
186+ // *max = capacity;
187+ // for (size_t i = 0; i < tal_count(constraints); i++) {
188+ // *min = amount_msat_max(*min, constraints[i].min);
189+ // *max = amount_msat_min(*max, constraints[i].max);
190+ // }
191+ // if(amount_msat_greater(*min, *max))
192+ // *min = *max;
193+ // }
194+
19195void get_constraints (const struct route_query * rq ,
20196 const struct gossmap_chan * chan ,
21197 int dir ,
@@ -24,28 +200,29 @@ void get_constraints(const struct route_query *rq,
24200{
25201 struct short_channel_id_dir scidd ;
26202 size_t idx = gossmap_chan_idx (rq -> gossmap , chan );
27-
28- * min = AMOUNT_MSAT (0 );
203+ struct constraint * constraints = tal_arr (rq , struct constraint , 0 );
29204
30205 /* Fast path: no information known, no reserve. */
31206 if (idx < tal_count (rq -> capacities ) && rq -> capacities [idx ] != 0 ) {
207+ * min = AMOUNT_MSAT (0 );
32208 * max = amount_msat (fp16_to_u64 (rq -> capacities [idx ]) * 1000 );
33209 return ;
34210 }
35211
212+ const struct amount_msat capacity =
213+ gossmap_chan_get_capacity (rq -> gossmap , chan );
214+
36215 /* Naive implementation! */
37216 scidd .scid = gossmap_chan_scid (rq -> gossmap , chan );
38217 scidd .dir = dir ;
39- * max = AMOUNT_MSAT (-1ULL );
40218
41219 /* Look through layers for any constraints (might be dummy
42220 * ones, for created channels!) */
43221 for (size_t i = 0 ; i < tal_count (rq -> layers ); i ++ )
44- layer_apply_constraints (rq -> layers [i ], & scidd , min , max );
222+ constraints = layer_get_constraints (rq , rq -> layers [i ], & scidd ,
223+ take (constraints ));
45224
46- /* Might be here because it's reserved, but capacity is normal. */
47- if (amount_msat_eq (* max , AMOUNT_MSAT (-1ULL )))
48- * max = gossmap_chan_get_capacity (rq -> gossmap , chan );
225+ constraints_get_bounds_selfadjust (constraints , capacity , min , max );
49226
50227 /* Finally, if any is in use, subtract that! */
51228 reserve_sub (rq -> reserved , & scidd , rq -> layers , min );
0 commit comments