askrene: calculate prob_cost_factor using ratio of typical mainnet channel.

During "test_real_data", then only successes with reduced fees were 92 on "mu=10", and only
1 on "mu=30": the rest went to mu=100 and failed.

I tried numerous approaches, and in the end, opted for the simplest:

The typical range of probability costs looks likes:
	min = 0, max = 924196240, mean = 10509.4, stddev = 1.9e+06

The typical range of linear fee costs looks like:
	min = 0, max = 101000000, mean = 81894.6, stddev = 2.6e+06

This implies a k factor of 8 makes the two comparable.

This makes the two numbers comparable, and thus makes "mu" much more
effective.  Here are the number of different mu values we succeeded at:

     87  mu=0
     90  mu=10
     42  mu=20
     24  mu=30
     17  mu=40
     19  mu=50
     19  mu=60
     11  mu=70
     95  mu=80
     19  mu=90

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-10-11 21:30:40 +10:30
parent 4897286c25
commit 6273adbe47
4 changed files with 27 additions and 95 deletions

View file

@ -285,7 +285,7 @@ static const char *get_routes(const tal_t *ctx,
const struct gossmap_node *srcnode, *dstnode;
double delay_feefactor;
double base_fee_penalty;
u32 prob_cost_factor, mu;
u32 mu;
const char *ret;
if (gossmap_refresh(askrene->gossmap, NULL)) {
@ -351,22 +351,10 @@ static const char *get_routes(const tal_t *ctx,
delay_feefactor = 1.0/1000000;
base_fee_penalty = 10.0;
/* From mcf.c: The input parameter `prob_cost_factor` in the function
* `minflow` is defined as the PPM from the delivery amount `T` we are
* *willing to pay* to increase the prob. of success by 0.1% */
/* This value is somewhat implied by our fee budget: say we would pay
* the entire budget for 100% probability, that means prob_cost_factor
* is (fee / amount) / 1000, or in PPM: (fee / amount) * 1000 */
if (amount_msat_is_zero(amount))
prob_cost_factor = 0;
else
prob_cost_factor = amount_msat_ratio(maxfee, amount) * 1000;
/* First up, don't care about fees. */
mu = 0;
flows = minflow(rq, rq, srcnode, dstnode, amount,
mu, delay_feefactor, base_fee_penalty, prob_cost_factor);
mu, delay_feefactor, base_fee_penalty);
if (!flows) {
ret = explain_failure(ctx, rq, srcnode, dstnode, amount);
goto fail;
@ -386,7 +374,7 @@ static const char *get_routes(const tal_t *ctx,
"The worst flow delay is %"PRIu64" (> %i), retrying with delay_feefactor %f...",
flows_worst_delay(flows), 2016 - finalcltv, delay_feefactor);
flows = minflow(rq, rq, srcnode, dstnode, amount,
mu, delay_feefactor, base_fee_penalty, prob_cost_factor);
mu, delay_feefactor, base_fee_penalty);
if (!flows || delay_feefactor > 10) {
ret = rq_log(ctx, rq, LOG_UNUSUAL,
"Could not find route without excessive delays");
@ -404,8 +392,8 @@ too_expensive:
fmt_amount_msat(tmpctx, maxfee),
mu);
flows = minflow(rq, rq, srcnode, dstnode, amount,
mu, delay_feefactor, base_fee_penalty, prob_cost_factor);
if (!flows || mu == 100) {
mu > 100 ? 100 : mu, delay_feefactor, base_fee_penalty);
if (!flows || mu >= 100) {
ret = rq_log(ctx, rq, LOG_UNUSUAL,
"Could not find route without excessive cost");
goto fail;

View file

@ -134,38 +134,7 @@
* However we propose to scale the prob. cost by a global factor k that
* translates into the monetization of prob. cost.
*
* k/1000, for instance, becomes the equivalent monetary cost
* of increasing the probability of success by 0.1% for P~100%.
*
* The input parameter `prob_cost_factor` in the function `minflow` is defined
* as the PPM from the delivery amount `T` we are *willing to pay* to increase the
* prob. of success by 0.1%:
*
* k_microsat = floor(1000*prob_cost_factor * T_sat)
*
* Is this enough to make integer prob. cost per unit flow?
* For `prob_cost_factor=10`; i.e. we pay 10ppm for increasing the prob. by
* 0.1%, we get that
*
* -> any arc with (b-a) > 10000 T, will have zero prob. cost, which is
* reasonable because even if all the flow passes through that arc, we get
* a 1.3 T/(b-a) ~ 0.01% prob. of failure at most.
*
* -> if (b-a) ~ 10000 T, then the arc will have unit cost, or just that we
* pay 1 microsat for every sat we send through this arc.
*
* -> it would be desirable to have a high proportional fee when (b-a)~T,
* because prob. of failure start to become very high.
* In this case we get to pay 10000 microsats for every sat.
*
* Once `k` is fixed then we can combine the linear prob. and fee costs, both
* are in monetary units.
*
* Note: with costs in microsats, because slopes represent ppm and flows are in
* sats, then our integer bounds with 64 bits are such that we can move as many
* as 10'000 BTC without overflow:
*
* 10^6 (max ppm) * 10^8 (sats per BTC) * 10^4 = 10^18
* This was chosen empirically from examination of typical network values.
*
* # References
*
@ -324,7 +293,7 @@ struct pay_parameters {
double delay_feefactor;
double base_fee_penalty;
u32 prob_cost_factor;
double k_factor;
};
/* Representation of the linear MCF network.
@ -470,7 +439,7 @@ static void linearize_channel(const struct pay_parameters *params,
cost[i] = params->cost_fraction[i]
*params->amount.millisatoshis /* Raw: linearize_channel */
*params->prob_cost_factor*1.0/(b-a);
*params->k_factor/(b-a);
}
}
@ -561,17 +530,13 @@ static void linear_network_add_adjacenct_arc(
* use `base_fee_penalty` to weight the base fee and `delay_feefactor` to
* weight the CLTV delay.
* */
static s64 linear_fee_cost(
const struct gossmap_chan *c,
const int dir,
double base_fee_penalty,
double delay_feefactor)
static s64 linear_fee_cost(u32 base_fee, u32 proportional_fee, u16 cltv_delta,
double base_fee_penalty,
double delay_feefactor)
{
assert(c);
assert(dir==0 || dir==1);
s64 pfee = c->half[dir].proportional_fee,
bfee = c->half[dir].base_fee,
delay = c->half[dir].delay;
s64 pfee = proportional_fee,
bfee = base_fee,
delay = cltv_delta;
return pfee + bfee* base_fee_penalty+ delay*delay_feefactor;
}
@ -644,9 +609,12 @@ init_linear_network(const tal_t *ctx, const struct pay_parameters *params)
// that are outgoing to `node`
linearize_channel(params, c, half, capacity, prob_cost);
const s64 fee_cost = linear_fee_cost(c,half,
params->base_fee_penalty,
params->delay_feefactor);
const s64 fee_cost = linear_fee_cost(
c->half[half].base_fee,
c->half[half].proportional_fee,
c->half[half].delay,
params->base_fee_penalty,
params->delay_feefactor);
// let's subscribe the 4 parts of the channel direction
// (c,half), the dual of these guys will be subscribed
@ -1281,8 +1249,7 @@ struct flow **minflow(const tal_t *ctx,
const struct gossmap_node *target,
struct amount_msat amount,
u32 mu,
double delay_feefactor, double base_fee_penalty,
u32 prob_cost_factor)
double delay_feefactor, double base_fee_penalty)
{
struct flow **flow_paths;
/* We allocate everything off this, and free it at the end,
@ -1312,7 +1279,7 @@ struct flow **minflow(const tal_t *ctx,
params->delay_feefactor = delay_feefactor;
params->base_fee_penalty = base_fee_penalty;
params->prob_cost_factor = prob_cost_factor;
params->k_factor = 8.0;
// build the uncertainty network with linearization and residual arcs
struct linear_network *linear_network= init_linear_network(working_ctx, params);

View file

@ -8,22 +8,10 @@
struct route_query;
enum {
RENEPAY_ERR_OK,
// No feasible flow found, either there is not enough known liquidity (or capacity)
// in the channels to complete the payment
RENEPAY_ERR_NOFEASIBLEFLOW,
// There is at least one feasible flow, but the the cheapest solution that we
// found is too expensive, we return the result anyways.
RENEPAY_ERR_NOCHEAPFLOW
};
/**
* optimal_payment_flow - API for min cost flow function(s).
* @ctx: context to allocate returned flows from
* @gossmap: the gossip map
* @rq: the route_query we're processing (for logging)
* @source: the source to start from
* @target: the target to pay
* @amount: the amount we want to reach @target
@ -39,16 +27,6 @@ enum {
*
* effective_ppm = proportional_fee + base_fee_msat * base_fee_penalty
*
* @prob_cost_factor: factor used to monetize the probability cost. It is
* defined as the number of ppm (parts per million of the total payment) we
* are willing to pay to improve the probability of success by 0.1%.
*
* k_microsat = floor(1000*prob_cost_factor * payment_sat)
*
* this k is used to compute a prob. cost in units of microsats
*
* cost(payment) = - k_microsat * log Prob(payment)
*
* Return a series of subflows which deliver amount to target, or NULL.
*/
struct flow **minflow(const tal_t *ctx,
@ -58,6 +36,5 @@ struct flow **minflow(const tal_t *ctx,
struct amount_msat amount,
u32 mu,
double delay_feefactor,
double base_fee_penalty,
u32 prob_cost_factor);
double base_fee_penalty);
#endif /* LIGHTNING_PLUGINS_ASKRENE_MCF_H */

View file

@ -433,11 +433,11 @@ def test_getroutes(node_factory):
10000000,
[[{'short_channel_id_dir': '0x2x1/1',
'next_node_id': nodemap[2],
'amount_msat': 500000,
'amount_msat': 4500004,
'delay': 99 + 6}],
[{'short_channel_id_dir': '0x2x3/1',
'next_node_id': nodemap[2],
'amount_msat': 9500009,
'amount_msat': 5500005,
'delay': 99 + 6}]])
@ -992,4 +992,4 @@ def test_real_data(node_factory, bitcoind):
if len(fees[n]) > len(fees[best]):
best = n
assert (len(fees[best]), len(improved), total_first_fee, total_final_fee, percent_fee_reduction) == (9, 91, 20917688, 5254665, 75)
assert (len(fees[best]), len(improved), total_first_fee, total_final_fee, percent_fee_reduction) == (10, 96, 19969585, 801613, 96)