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;
const struct gossmap_node *srcnode, *dstnode;
double delay_feefactor;
double base_fee_penalty;
u32 mu;
const char *ret;
@ -349,12 +348,11 @@ static const char *get_routes(const tal_t *ctx,
}
delay_feefactor = 1.0/1000000;
base_fee_penalty = 10.0;
/* First up, don't care about fees. */
mu = 0;
flows = minflow(rq, rq, srcnode, dstnode, amount,
mu, delay_feefactor, base_fee_penalty);
mu, delay_feefactor);
if (!flows) {
ret = explain_failure(ctx, rq, srcnode, dstnode, amount);
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...",
flows_worst_delay(flows), 2016 - finalcltv, delay_feefactor);
flows = minflow(rq, rq, srcnode, dstnode, amount,
mu, delay_feefactor, base_fee_penalty);
mu, delay_feefactor);
if (!flows || delay_feefactor > 10) {
ret = rq_log(ctx, rq, LOG_UNUSUAL,
"Could not find route without excessive delays");
@ -392,7 +390,7 @@ too_expensive:
fmt_amount_msat(tmpctx, maxfee),
mu);
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) {
ret = rq_log(ctx, rq, LOG_UNUSUAL,
"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;
}
/* 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 *
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,
struct amount_msat amount,
u32 mu,
double delay_feefactor, double base_fee_penalty)
double delay_feefactor)
{
struct flow **flow_paths;
/* 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->base_fee_penalty = base_fee_penalty;
params->base_fee_penalty = base_fee_penalty_estimate(amount);
params->k_factor = 8.0;
// 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
* 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.
*/
struct flow **minflow(const tal_t *ctx,
@ -35,6 +29,5 @@ 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);
double delay_feefactor);
#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]):
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)