askrene: fix base fee.

I noticed this in the logs:

	plugin-cln-askrene: notify msg unusual: The flows had a fee of 151950msat, greater than max of 53697msat, retrying with mu of 10%...
	plugin-cln-askrene: notify msg unusual: The flows had a fee of 220126msat, greater than max of 53697msat, retrying with mu of 20%...

We would expect increasing mu to *reduce* the fee!

Turns out that our linear fee is a bad terrible approximation, because I
was using base_fee_penalty of 10.0.

 |
 |          /   __ <- real fee, with base: fee = base + propfee * amount.
 |         / __/
 |       _//
 |    __/
 | __/_/
 |/  _/
 | _/ <- linearized fee: fee = linear * amount
 |/
 +-----------------------------------

These cross over where linear = propfee + base / amount.  Assume we split the
payment into 10 parts, this implies that the base_fee_penalty should be 10 / amount
(this gives a slight penalty to the normal case, but that's ok).

This gives better results, too: we get down to 650099 sats in fees, vs 801613
before.

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 6273adbe47
commit 08df93cb25
4 changed files with 15 additions and 16 deletions

View file

@ -284,7 +284,6 @@ static const char *get_routes(const tal_t *ctx,
struct flow **flows; struct flow **flows;
const struct gossmap_node *srcnode, *dstnode; const struct gossmap_node *srcnode, *dstnode;
double delay_feefactor; double delay_feefactor;
double base_fee_penalty;
u32 mu; u32 mu;
const char *ret; const char *ret;
@ -349,12 +348,11 @@ static const char *get_routes(const tal_t *ctx,
} }
delay_feefactor = 1.0/1000000; delay_feefactor = 1.0/1000000;
base_fee_penalty = 10.0;
/* First up, don't care about fees. */ /* First up, don't care about fees. */
mu = 0; mu = 0;
flows = minflow(rq, rq, srcnode, dstnode, amount, flows = minflow(rq, rq, srcnode, dstnode, amount,
mu, delay_feefactor, base_fee_penalty); mu, delay_feefactor);
if (!flows) { if (!flows) {
ret = explain_failure(ctx, rq, srcnode, dstnode, amount); ret = explain_failure(ctx, rq, srcnode, dstnode, amount);
goto fail; goto fail;
@ -374,7 +372,7 @@ static const char *get_routes(const tal_t *ctx,
"The worst flow delay is %"PRIu64" (> %i), retrying with delay_feefactor %f...", "The worst flow delay is %"PRIu64" (> %i), retrying with delay_feefactor %f...",
flows_worst_delay(flows), 2016 - finalcltv, delay_feefactor); flows_worst_delay(flows), 2016 - finalcltv, delay_feefactor);
flows = minflow(rq, rq, srcnode, dstnode, amount, flows = minflow(rq, rq, srcnode, dstnode, amount,
mu, delay_feefactor, base_fee_penalty); mu, delay_feefactor);
if (!flows || delay_feefactor > 10) { if (!flows || delay_feefactor > 10) {
ret = rq_log(ctx, rq, LOG_UNUSUAL, ret = rq_log(ctx, rq, LOG_UNUSUAL,
"Could not find route without excessive delays"); "Could not find route without excessive delays");
@ -392,7 +390,7 @@ too_expensive:
fmt_amount_msat(tmpctx, maxfee), fmt_amount_msat(tmpctx, maxfee),
mu); mu);
flows = minflow(rq, rq, srcnode, dstnode, amount, flows = minflow(rq, rq, srcnode, dstnode, amount,
mu > 100 ? 100 : mu, delay_feefactor, base_fee_penalty); mu > 100 ? 100 : mu, delay_feefactor);
if (!flows || mu >= 100) { if (!flows || mu >= 100) {
ret = rq_log(ctx, rq, LOG_UNUSUAL, ret = rq_log(ctx, rq, LOG_UNUSUAL,
"Could not find route without excessive cost"); "Could not find route without excessive cost");

View file

@ -541,6 +541,14 @@ static s64 linear_fee_cost(u32 base_fee, u32 proportional_fee, u16 cltv_delta,
return pfee + bfee* base_fee_penalty+ delay*delay_feefactor; return pfee + bfee* base_fee_penalty+ delay*delay_feefactor;
} }
/* This is inversely proportional to the amount we expect to send. Let's
* assume we will send ~10th of the total amount per path. But note
* that it converts to parts per million! */
static double base_fee_penalty_estimate(struct amount_msat amount)
{
return amount_msat_ratio(AMOUNT_MSAT(10000000), amount);
}
static struct linear_network * static struct linear_network *
init_linear_network(const tal_t *ctx, const struct pay_parameters *params) init_linear_network(const tal_t *ctx, const struct pay_parameters *params)
{ {
@ -1249,7 +1257,7 @@ struct flow **minflow(const tal_t *ctx,
const struct gossmap_node *target, const struct gossmap_node *target,
struct amount_msat amount, struct amount_msat amount,
u32 mu, u32 mu,
double delay_feefactor, double base_fee_penalty) double delay_feefactor)
{ {
struct flow **flow_paths; struct flow **flow_paths;
/* We allocate everything off this, and free it at the end, /* We allocate everything off this, and free it at the end,
@ -1278,7 +1286,7 @@ struct flow **minflow(const tal_t *ctx,
} }
params->delay_feefactor = delay_feefactor; params->delay_feefactor = delay_feefactor;
params->base_fee_penalty = base_fee_penalty; params->base_fee_penalty = base_fee_penalty_estimate(amount);
params->k_factor = 8.0; params->k_factor = 8.0;
// build the uncertainty network with linearization and residual arcs // build the uncertainty network with linearization and residual arcs

View file

@ -21,12 +21,6 @@ struct route_query;
* fee. So if a CLTV delay on a node is 5 blocks, that's treated as if it * fee. So if a CLTV delay on a node is 5 blocks, that's treated as if it
* were a fee of 5 * @delay_feefactor. * were a fee of 5 * @delay_feefactor.
* *
* @base_fee_penalty: factor to compute additional proportional cost from each
* unit of base fee. So #base_fee_penalty will be added to the effective
* proportional fee for each msat of base fee.
*
* effective_ppm = proportional_fee + base_fee_msat * base_fee_penalty
*
* Return a series of subflows which deliver amount to target, or NULL. * Return a series of subflows which deliver amount to target, or NULL.
*/ */
struct flow **minflow(const tal_t *ctx, struct flow **minflow(const tal_t *ctx,
@ -35,6 +29,5 @@ struct flow **minflow(const tal_t *ctx,
const struct gossmap_node *target, const struct gossmap_node *target,
struct amount_msat amount, struct amount_msat amount,
u32 mu, u32 mu,
double delay_feefactor, double delay_feefactor);
double base_fee_penalty);
#endif /* LIGHTNING_PLUGINS_ASKRENE_MCF_H */ #endif /* LIGHTNING_PLUGINS_ASKRENE_MCF_H */

View file

@ -992,4 +992,4 @@ def test_real_data(node_factory, bitcoind):
if len(fees[n]) > len(fees[best]): if len(fees[n]) > len(fees[best]):
best = n best = n
assert (len(fees[best]), len(improved), total_first_fee, total_final_fee, percent_fee_reduction) == (10, 96, 19969585, 801613, 96) assert (len(fees[best]), len(improved), total_first_fee, total_final_fee, percent_fee_reduction) == (9, 96, 19961361, 650099, 97)