askrene: refactor get_routes ...

Move the feature tuning algorithm to mcf.c, ie. the loops for searching
a good mu and delay_feefactor to satisfy the problem constraints.

We are looking to set the stage for an execution logic that allows for
multiple choices of routing algorithms, mainly for experimenting without
breaking the default working code.

Changelog-None

Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
This commit is contained in:
Lagrang3
2025-07-10 23:38:06 +09:30
committed by Rusty Russell
parent cea6c81181
commit 88bb1636bc
3 changed files with 175 additions and 121 deletions

View File

@@ -19,11 +19,9 @@
#include <errno.h>
#include <math.h>
#include <plugins/askrene/askrene.h>
#include <plugins/askrene/explain_failure.h>
#include <plugins/askrene/flow.h>
#include <plugins/askrene/layer.h>
#include <plugins/askrene/mcf.h>
#include <plugins/askrene/refine.h>
#include <plugins/askrene/reserve.h>
/* "spendable" for a channel assumes a single HTLC: for additional HTLCs,
@@ -333,22 +331,6 @@ const char *fmt_flow_full(const tal_t *ctx,
return str;
}
static struct amount_msat linear_flows_cost(struct flow **flows,
struct amount_msat total_amount,
double delay_feefactor)
{
struct amount_msat total = AMOUNT_MSAT(0);
for (size_t i = 0; i < tal_count(flows); i++) {
if (!amount_msat_accumulate(&total,
linear_flow_cost(flows[i],
total_amount,
delay_feefactor)))
abort();
}
return total;
}
/* Returns an error message, or sets *routes */
static const char *get_routes(const tal_t *ctx,
struct command *cmd,
@@ -371,8 +353,6 @@ static const char *get_routes(const tal_t *ctx,
struct route_query *rq = tal(ctx, struct route_query);
struct flow **flows;
const struct gossmap_node *srcnode, *dstnode;
double delay_feefactor;
u32 mu;
const char *ret;
struct timerel time_delta;
struct timemono time_start = time_mono();
@@ -446,109 +426,15 @@ static const char *get_routes(const tal_t *ctx,
goto fail;
}
delay_feefactor = 1.0/1000000;
/* First up, don't care about fees (well, just enough to tiebreak!) */
mu = 1;
flows = minflow(rq, rq, srcnode, dstnode, amount,
mu, delay_feefactor, single_path);
if (!flows) {
ret = explain_failure(ctx, rq, srcnode, dstnode, amount);
/* FIXME: single_path should signal a change in algorithm. */
ret = default_routes(rq, rq, srcnode, dstnode, amount, single_path,
maxfee, finalcltv, maxdelay, &flows, probability);
if (ret) {
goto fail;
}
/* Too much delay? */
while (finalcltv + flows_worst_delay(flows) > maxdelay) {
delay_feefactor *= 2;
rq_log(tmpctx, rq, LOG_UNUSUAL,
"The worst flow delay is %"PRIu64" (> %i), retrying with delay_feefactor %f...",
flows_worst_delay(flows), maxdelay - finalcltv, delay_feefactor);
flows = minflow(rq, rq, srcnode, dstnode, amount,
mu, delay_feefactor, single_path);
if (!flows || delay_feefactor > 10) {
ret = rq_log(ctx, rq, LOG_UNUSUAL,
"Could not find route without excessive delays");
goto fail;
}
}
/* Too expensive? */
too_expensive:
while (amount_msat_greater(flowset_fee(rq->plugin, flows), maxfee)) {
struct flow **new_flows;
if (mu == 1)
mu = 10;
else
mu += 10;
rq_log(tmpctx, rq, LOG_UNUSUAL,
"The flows had a fee of %s, greater than max of %s, retrying with mu of %u%%...",
fmt_amount_msat(tmpctx, flowset_fee(rq->plugin, flows)),
fmt_amount_msat(tmpctx, maxfee),
mu);
new_flows = minflow(rq, rq, srcnode, dstnode, amount,
mu > 100 ? 100 : mu, delay_feefactor, single_path);
if (!flows || mu >= 100) {
ret = rq_log(ctx, rq, LOG_UNUSUAL,
"Could not find route without excessive cost");
goto fail;
}
/* This is possible, because MCF's linear fees are not the same. */
if (amount_msat_greater(flowset_fee(rq->plugin, new_flows),
flowset_fee(rq->plugin, flows))) {
struct amount_msat old_cost = linear_flows_cost(flows, amount, delay_feefactor);
struct amount_msat new_cost = linear_flows_cost(new_flows, amount, delay_feefactor);
if (amount_msat_greater_eq(new_cost, old_cost)) {
rq_log(tmpctx, rq, LOG_BROKEN, "Old flows cost %s:",
fmt_amount_msat(tmpctx, old_cost));
for (size_t i = 0; i < tal_count(flows); i++) {
rq_log(tmpctx, rq, LOG_BROKEN,
"Flow %zu/%zu: %s (linear cost %s)", i, tal_count(flows),
fmt_flow_full(tmpctx, rq, flows[i]),
fmt_amount_msat(tmpctx, linear_flow_cost(flows[i],
amount,
delay_feefactor)));
}
rq_log(tmpctx, rq, LOG_BROKEN, "Old flows cost %s:",
fmt_amount_msat(tmpctx, new_cost));
for (size_t i = 0; i < tal_count(new_flows); i++) {
rq_log(tmpctx, rq, LOG_BROKEN,
"Flow %zu/%zu: %s (linear cost %s)", i, tal_count(new_flows),
fmt_flow_full(tmpctx, rq, new_flows[i]),
fmt_amount_msat(tmpctx, linear_flow_cost(new_flows[i],
amount,
delay_feefactor)));
}
}
}
tal_free(flows);
flows = new_flows;
}
if (finalcltv + flows_worst_delay(flows) > maxdelay) {
ret = rq_log(ctx, rq, LOG_UNUSUAL,
"Could not find route without excessive cost or delays");
goto fail;
}
/* The above did not take into account the extra funds to pay
* fees, so we try to adjust now. We could re-run MCF if this
* fails, but failure basically never happens where payment is
* still possible */
ret = refine_with_fees_and_limits(ctx, rq, amount, &flows, probability);
if (ret)
goto fail;
/* Again, a tiny corner case: refine step can make us exceed maxfee */
if (amount_msat_greater(flowset_fee(rq->plugin, flows), maxfee)) {
rq_log(tmpctx, rq, LOG_UNUSUAL,
"After final refinement, fee was excessive: retrying");
goto too_expensive;
}
rq_log(tmpctx, rq, LOG_DBG, "Final answer has %zu flows with mu=%u",
tal_count(flows), mu);
assert(tal_count(flows) > 0);
rq_log(tmpctx, rq, LOG_DBG, "Final answer has %zu flows",
tal_count(flows));
/* Convert back into routes, with delay and other information fixed */
*routes = tal_arr(ctx, struct route *, tal_count(flows));

View File

@@ -11,9 +11,11 @@
#include <plugins/askrene/algorithm.h>
#include <plugins/askrene/askrene.h>
#include <plugins/askrene/dijkstra.h>
#include <plugins/askrene/explain_failure.h>
#include <plugins/askrene/flow.h>
#include <plugins/askrene/graph.h>
#include <plugins/askrene/mcf.h>
#include <plugins/askrene/refine.h>
#include <plugins/libplugin.h>
#include <stdint.h>
@@ -1080,3 +1082,159 @@ fail:
tal_free(working_ctx);
return NULL;
}
static struct amount_msat linear_flows_cost(struct flow **flows,
struct amount_msat total_amount,
double delay_feefactor)
{
struct amount_msat total = AMOUNT_MSAT(0);
for (size_t i = 0; i < tal_count(flows); i++) {
if (!amount_msat_accumulate(&total,
linear_flow_cost(flows[i],
total_amount,
delay_feefactor)))
abort();
}
return total;
}
const char *default_routes(const tal_t *ctx, struct route_query *rq,
const struct gossmap_node *srcnode,
const struct gossmap_node *dstnode,
struct amount_msat amount, bool single_path,
struct amount_msat maxfee, u32 finalcltv,
u32 maxdelay, struct flow ***flows,
double *probability)
{
*flows = NULL;
const char *ret;
double delay_feefactor = 1.0 / 1000000;
/* First up, don't care about fees (well, just enough to tiebreak!) */
u32 mu = 1;
tal_free(*flows);
*flows = minflow(ctx, rq, srcnode, dstnode, amount, mu, delay_feefactor,
single_path);
if (!*flows) {
ret = explain_failure(ctx, rq, srcnode, dstnode, amount);
goto fail;
}
/* Too much delay? */
while (finalcltv + flows_worst_delay(*flows) > maxdelay) {
delay_feefactor *= 2;
rq_log(tmpctx, rq, LOG_UNUSUAL,
"The worst flow delay is %" PRIu64
" (> %i), retrying with delay_feefactor %f...",
flows_worst_delay(*flows), maxdelay - finalcltv,
delay_feefactor);
tal_free(*flows);
*flows = minflow(ctx, rq, srcnode, dstnode, amount, mu,
delay_feefactor, single_path);
if (!*flows || delay_feefactor > 10) {
ret = rq_log(
ctx, rq, LOG_UNUSUAL,
"Could not find route without excessive delays");
goto fail;
}
}
/* Too expensive? */
too_expensive:
while (amount_msat_greater(flowset_fee(rq->plugin, *flows), maxfee)) {
struct flow **new_flows;
if (mu == 1)
mu = 10;
else
mu += 10;
rq_log(tmpctx, rq, LOG_UNUSUAL,
"The flows had a fee of %s, greater than max of %s, "
"retrying with mu of %u%%...",
fmt_amount_msat(tmpctx, flowset_fee(rq->plugin, *flows)),
fmt_amount_msat(tmpctx, maxfee), mu);
new_flows =
minflow(ctx, rq, srcnode, dstnode, amount,
mu > 100 ? 100 : mu, delay_feefactor, single_path);
if (!*flows || mu >= 100) {
ret = rq_log(
ctx, rq, LOG_UNUSUAL,
"Could not find route without excessive cost");
goto fail;
}
/* This is possible, because MCF's linear fees are not the same.
*/
if (amount_msat_greater(flowset_fee(rq->plugin, new_flows),
flowset_fee(rq->plugin, *flows))) {
struct amount_msat old_cost =
linear_flows_cost(*flows, amount, delay_feefactor);
struct amount_msat new_cost = linear_flows_cost(
new_flows, amount, delay_feefactor);
if (amount_msat_greater_eq(new_cost, old_cost)) {
rq_log(tmpctx, rq, LOG_BROKEN,
"Old flows cost %s:",
fmt_amount_msat(tmpctx, old_cost));
for (size_t i = 0; i < tal_count(*flows); i++) {
rq_log(
tmpctx, rq, LOG_BROKEN,
"Flow %zu/%zu: %s (linear cost %s)",
i, tal_count(*flows),
fmt_flow_full(tmpctx, rq, (*flows)[i]),
fmt_amount_msat(
tmpctx, linear_flow_cost(
(*flows)[i], amount,
delay_feefactor)));
}
rq_log(tmpctx, rq, LOG_BROKEN,
"Old flows cost %s:",
fmt_amount_msat(tmpctx, new_cost));
for (size_t i = 0; i < tal_count(new_flows);
i++) {
rq_log(
tmpctx, rq, LOG_BROKEN,
"Flow %zu/%zu: %s (linear cost %s)",
i, tal_count(new_flows),
fmt_flow_full(tmpctx, rq,
new_flows[i]),
fmt_amount_msat(
tmpctx,
linear_flow_cost(
new_flows[i], amount,
delay_feefactor)));
}
}
}
tal_free(*flows);
*flows = new_flows;
}
if (finalcltv + flows_worst_delay(*flows) > maxdelay) {
ret = rq_log(
ctx, rq, LOG_UNUSUAL,
"Could not find route without excessive cost or delays");
goto fail;
}
/* The above did not take into account the extra funds to pay
* fees, so we try to adjust now. We could re-run MCF if this
* fails, but failure basically never happens where payment is
* still possible */
ret = refine_with_fees_and_limits(ctx, rq, amount, flows, probability);
if (ret)
goto fail;
/* Again, a tiny corner case: refine step can make us exceed maxfee */
if (amount_msat_greater(flowset_fee(rq->plugin, *flows), maxfee)) {
rq_log(tmpctx, rq, LOG_UNUSUAL,
"After final refinement, fee was excessive: retrying");
goto too_expensive;
}
return NULL;
fail:
assert(ret != NULL);
return ret;
}

View File

@@ -40,4 +40,14 @@ struct amount_msat linear_flow_cost(const struct flow *flow,
struct amount_msat total_amount,
double delay_feefactor);
/* A wrapper to the min. cost flow solver that actually takes into consideration
* the extra msats per channel needed to pay for fees. */
const char *default_routes(const tal_t *ctx, struct route_query *rq,
const struct gossmap_node *srcnode,
const struct gossmap_node *dstnode,
struct amount_msat amount, bool single_path,
struct amount_msat maxfee, u32 finalcltv,
u32 maxdelay, struct flow ***flows,
double *probability);
#endif /* LIGHTNING_PLUGINS_ASKRENE_MCF_H */