From 4a840c262dc929ac15967fd2e313bded8a330655 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Wed, 11 Dec 2024 12:34:07 +0100 Subject: [PATCH 1/4] askrene: add runtime of getroutes to the logs Add log information about the runtime of getroutes. Changelog-None: askrene: add runtime of getroutes to the logs Signed-off-by: Lagrang3 --- plugins/askrene/askrene.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/askrene/askrene.c b/plugins/askrene/askrene.c index 09b893d72aa5..dd8a90ea745d 100644 --- a/plugins/askrene/askrene.c +++ b/plugins/askrene/askrene.c @@ -9,6 +9,7 @@ #include "config.h" #include #include +#include #include #include #include @@ -373,6 +374,8 @@ static const char *get_routes(const tal_t *ctx, double delay_feefactor; u32 mu; const char *ret; + struct timerel time_delta; + struct timemono time_start = time_mono(); if (gossmap_refresh(askrene->gossmap)) { /* FIXME: gossmap_refresh callbacks to we can update in place */ @@ -586,7 +589,9 @@ static const char *get_routes(const tal_t *ctx, } gossmap_remove_localmods(askrene->gossmap, localmods); - + time_delta = timemono_between(time_mono(), time_start); + rq_log(tmpctx, rq, LOG_DBG, "get_routes completed in %" PRIu64 " ms", + time_to_msec(time_delta)); return NULL; /* Explicit failure path keeps the compiler (gcc version 12.3.0 -O3) from @@ -594,6 +599,9 @@ static const char *get_routes(const tal_t *ctx, fail: assert(ret != NULL); gossmap_remove_localmods(askrene->gossmap, localmods); + time_delta = timemono_between(time_mono(), time_start); + rq_log(tmpctx, rq, LOG_DBG, "get_routes failed after %" PRIu64 " ms", + time_to_msec(time_delta)); return ret; } From 0581f8e6f97552018d2f15bf6472a8e78cdb6b0a Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Fri, 14 Mar 2025 08:22:19 +0100 Subject: [PATCH 2/4] 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 --- plugins/askrene/askrene.c | 128 ++----------------------------- plugins/askrene/mcf.c | 155 ++++++++++++++++++++++++++++++++++++++ plugins/askrene/mcf.h | 10 +++ 3 files changed, 172 insertions(+), 121 deletions(-) diff --git a/plugins/askrene/askrene.c b/plugins/askrene/askrene.c index dd8a90ea745d..90c8d277fe0c 100644 --- a/plugins/askrene/askrene.c +++ b/plugins/askrene/askrene.c @@ -19,11 +19,9 @@ #include #include #include -#include #include #include #include -#include #include /* "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); - 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) + /* 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; - - /* 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)); diff --git a/plugins/askrene/mcf.c b/plugins/askrene/mcf.c index 6339510a5ce9..5d6a9cc90770 100644 --- a/plugins/askrene/mcf.c +++ b/plugins/askrene/mcf.c @@ -11,9 +11,11 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -1080,3 +1082,156 @@ struct flow **minflow(const tal_t *ctx, 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) +{ + const char *ret; + double delay_feefactor = 1.0 / 1000000; + + /* First up, don't care about fees (well, just enough to tiebreak!) */ + u32 mu = 1; + *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); + *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; +} diff --git a/plugins/askrene/mcf.h b/plugins/askrene/mcf.h index f8100e766dd6..701a1ac5bf75 100644 --- a/plugins/askrene/mcf.h +++ b/plugins/askrene/mcf.h @@ -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 */ From 5e2d154fb7481ee605bb8a25d5778485eff69c37 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Fri, 14 Mar 2025 10:55:34 +0100 Subject: [PATCH 3/4] askrene: refactor get_routes Prefer the following programming pattern: do_getroutes(){ do_something1(); do_something2(); do_something3(); } rather than get_routes(){ do_something1(); do_something2(); } do_getroutes(){ get_routes(); do_something3(); } Changelog-None Signed-off-by: Lagrang3 --- plugins/askrene/askrene.c | 305 ++++++++++++++++++++------------------ 1 file changed, 162 insertions(+), 143 deletions(-) diff --git a/plugins/askrene/askrene.c b/plugins/askrene/askrene.c index 90c8d277fe0c..075b71fe1eab 100644 --- a/plugins/askrene/askrene.c +++ b/plugins/askrene/askrene.c @@ -331,60 +331,35 @@ const char *fmt_flow_full(const tal_t *ctx, return str; } -/* Returns an error message, or sets *routes */ -static const char *get_routes(const tal_t *ctx, - struct command *cmd, - const struct node_id *source, - const struct node_id *dest, - struct amount_msat amount, - struct amount_msat maxfee, - u32 finalcltv, - u32 maxdelay, - const char **layers, - struct gossmap_localmods *localmods, - const struct layer *local_layer, - bool single_path, - struct route ***routes, - struct amount_msat **amounts, - const struct additional_cost_htable *additional_costs, - double *probability) -{ - struct askrene *askrene = get_askrene(cmd->plugin); - struct route_query *rq = tal(ctx, struct route_query); - struct flow **flows; - const struct gossmap_node *srcnode, *dstnode; - const char *ret; - struct timerel time_delta; - struct timemono time_start = time_mono(); - - if (gossmap_refresh(askrene->gossmap)) { - /* FIXME: gossmap_refresh callbacks to we can update in place */ - tal_free(askrene->capacities); - askrene->capacities = get_capacities(askrene, askrene->plugin, askrene->gossmap); - } - - rq->cmd = cmd; - rq->plugin = cmd->plugin; - rq->gossmap = askrene->gossmap; - rq->reserved = askrene->reserved; - rq->layers = tal_arr(rq, const struct layer *, 0); - rq->capacities = tal_dup_talarr(rq, fp16_t, askrene->capacities); - rq->additional_costs = additional_costs; +struct getroutes_info { + struct command *cmd; + struct node_id *source, *dest; + struct amount_msat *amount, *maxfee; + u32 *finalcltv, *maxdelay; + const char **layers; + struct additional_cost_htable *additional_costs; + /* Non-NULL if we are told to use "auto.localchans" */ + struct layer *local_layer; +}; +static void apply_layers(struct askrene *askrene, struct route_query *rq, + struct gossmap_localmods *localmods, + const struct getroutes_info *info) +{ /* Layers must exist, but might be special ones! */ - for (size_t i = 0; i < tal_count(layers); i++) { - const struct layer *l = find_layer(askrene, layers[i]); + for (size_t i = 0; i < tal_count(info->layers); i++) { + const struct layer *l = find_layer(askrene, info->layers[i]); if (!l) { - if (streq(layers[i], "auto.localchans")) { + if (streq(info->layers[i], "auto.localchans")) { plugin_log(rq->plugin, LOG_DBG, "Adding auto.localchans"); - l = local_layer; - } else if (streq(layers[i], "auto.no_mpp_support")) { + l = info->local_layer; + } else if (streq(info->layers[i], "auto.no_mpp_support")) { plugin_log(rq->plugin, LOG_DBG, "Adding auto.no_mpp_support, sorry"); - l = remove_small_channel_layer(layers, askrene, amount, localmods); + l = remove_small_channel_layer(info->layers, askrene, *info->amount, localmods); } else { - assert(streq(layers[i], "auto.sourcefree")); + assert(streq(info->layers[i], "auto.sourcefree")); plugin_log(rq->plugin, LOG_DBG, "Adding auto.sourcefree"); - l = source_free_layer(layers, askrene, source, localmods); + l = source_free_layer(info->layers, askrene, info->source, localmods); } } @@ -396,46 +371,14 @@ static const char *get_routes(const tal_t *ctx, * override them (incl local channels) */ layer_clear_overridden_capacities(l, askrene->gossmap, rq->capacities); } +} - /* Clear scids with reservations, too, so we don't have to look up - * all the time! */ - reserves_clear_capacities(askrene->reserved, askrene->gossmap, rq->capacities); - - gossmap_apply_localmods(askrene->gossmap, localmods); - - /* localmods can add channels, so we need to allocate biases array *afterwards* */ - rq->biases = tal_arrz(rq, s8, gossmap_max_chan_idx(askrene->gossmap) * 2); - - /* Note any channel biases */ - for (size_t i = 0; i < tal_count(rq->layers); i++) - layer_apply_biases(rq->layers[i], askrene->gossmap, rq->biases); - - srcnode = gossmap_find_node(askrene->gossmap, source); - if (!srcnode) { - ret = rq_log(ctx, rq, LOG_INFORM, - "Unknown source node %s", - fmt_node_id(tmpctx, source)); - goto fail; - } - - dstnode = gossmap_find_node(askrene->gossmap, dest); - if (!dstnode) { - ret = rq_log(ctx, rq, LOG_INFORM, - "Unknown destination node %s", - fmt_node_id(tmpctx, dest)); - goto fail; - } - - /* 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; - } - assert(tal_count(flows) > 0); - rq_log(tmpctx, rq, LOG_DBG, "Final answer has %zu flows", - tal_count(flows)); - +static void convert_flows_to_routes(const tal_t *ctx, struct route_query *rq, + struct route ***routes, + struct amount_msat **amounts, + u32 finalcltv, + struct flow **flows) +{ /* Convert back into routes, with delay and other information fixed */ *routes = tal_arr(ctx, struct route *, tal_count(flows)); *amounts = tal_arr(ctx, struct amount_msat, tal_count(flows)); @@ -473,22 +416,6 @@ static const char *get_routes(const tal_t *ctx, i, tal_count(flows), fmt_route(tmpctx, r, (*amounts)[i], finalcltv)); } - - gossmap_remove_localmods(askrene->gossmap, localmods); - time_delta = timemono_between(time_mono(), time_start); - rq_log(tmpctx, rq, LOG_DBG, "get_routes completed in %" PRIu64 " ms", - time_to_msec(time_delta)); - return NULL; - - /* Explicit failure path keeps the compiler (gcc version 12.3.0 -O3) from - * warning about uninitialized variables in the caller */ -fail: - assert(ret != NULL); - gossmap_remove_localmods(askrene->gossmap, localmods); - time_delta = timemono_between(time_mono(), time_start); - rq_log(tmpctx, rq, LOG_DBG, "get_routes failed after %" PRIu64 " ms", - time_to_msec(time_delta)); - return ret; } void get_constraints(const struct route_query *rq, @@ -527,16 +454,40 @@ void get_constraints(const struct route_query *rq, reserve_sub(rq->reserved, &scidd, max); } -struct getroutes_info { - struct command *cmd; - struct node_id *source, *dest; - struct amount_msat *amount, *maxfee; - u32 *finalcltv, *maxdelay; - const char **layers; - struct additional_cost_htable *additional_costs; - /* Non-NULL if we are told to use "auto.localchans" */ - struct layer *local_layer; -}; +static void json_add_getroutes( + struct json_stream *js, + struct route **routes, + const struct amount_msat *amounts, + double probability, + u32 final_cltv) +{ + json_add_u64(js, "probability_ppm", (u64)(probability * 1000000)); + json_array_start(js, "routes"); + for (size_t i = 0; i < tal_count(routes); i++) { + json_object_start(js, NULL); + json_add_u64(js, "probability_ppm", + (u64)(routes[i]->success_prob * 1000000)); + json_add_amount_msat(js, "amount_msat", amounts[i]); + json_add_u32(js, "final_cltv", final_cltv); + json_array_start(js, "path"); + for (size_t j = 0; j < tal_count(routes[i]->hops); j++) { + struct short_channel_id_dir scidd; + const struct route_hop *r = &routes[i]->hops[j]; + json_object_start(js, NULL); + scidd.scid = r->scid; + scidd.dir = r->direction; + json_add_short_channel_id_dir( + js, "short_channel_id_dir", scidd); + json_add_node_id(js, "next_node_id", &r->node_id); + json_add_amount_msat(js, "amount_msat", r->amount); + json_add_u32(js, "delay", r->delay); + json_object_end(js); + } + json_array_end(js); + json_object_end(js); + } + json_array_end(js); +} static struct command_result *do_getroutes(struct command *cmd, struct gossmap_localmods *localmods, @@ -546,43 +497,111 @@ static struct command_result *do_getroutes(struct command *cmd, double probability; struct amount_msat *amounts; struct route **routes; + struct flow **flows; struct json_stream *response; - err = get_routes(cmd, cmd, - info->source, info->dest, - *info->amount, *info->maxfee, *info->finalcltv, - *info->maxdelay, info->layers, localmods, info->local_layer, - have_layer(info->layers, "auto.no_mpp_support"), - &routes, &amounts, info->additional_costs, &probability); + /* get me the global state structure */ + struct askrene *askrene = get_askrene(cmd->plugin); + + /* update the gossmap */ + if (gossmap_refresh(askrene->gossmap)) { + /* FIXME: gossmap_refresh callbacks to we can update in place */ + tal_free(askrene->capacities); + askrene->capacities = + get_capacities(askrene, askrene->plugin, askrene->gossmap); + } + + /* build this request structure */ + struct route_query *rq = tal(cmd, struct route_query); + rq->cmd = cmd; + rq->plugin = cmd->plugin; + rq->gossmap = askrene->gossmap; + rq->reserved = askrene->reserved; + rq->layers = tal_arr(rq, const struct layer *, 0); + rq->capacities = tal_dup_talarr(rq, fp16_t, askrene->capacities); + /* FIXME: we still need to do something useful with these */ + rq->additional_costs = info->additional_costs; + + /* apply selected layers to the localmods */ + apply_layers(askrene, rq, localmods, info); + + /* Clear scids with reservations, too, so we don't have to look up + * all the time! */ + reserves_clear_capacities(askrene->reserved, askrene->gossmap, + rq->capacities); + + /* we temporarily apply localmods */ + gossmap_apply_localmods(askrene->gossmap, localmods); + + /* localmods can add channels, so we need to allocate biases array + * *afterwards* */ + rq->biases = + tal_arrz(rq, s8, gossmap_max_chan_idx(askrene->gossmap) * 2); + + /* Note any channel biases */ + for (size_t i = 0; i < tal_count(rq->layers); i++) + layer_apply_biases(rq->layers[i], askrene->gossmap, rq->biases); + + /* checkout the source */ + const struct gossmap_node *srcnode = + gossmap_find_node(askrene->gossmap, info->source); + if (!srcnode) { + err = rq_log(tmpctx, rq, LOG_INFORM, "Unknown source node %s", + fmt_node_id(tmpctx, info->source)); + goto fail; + } + + /* checkout the destination */ + const struct gossmap_node *dstnode = + gossmap_find_node(askrene->gossmap, info->dest); + if (!dstnode) { + err = rq_log(tmpctx, rq, LOG_INFORM, + "Unknown destination node %s", + fmt_node_id(tmpctx, info->dest)); + goto fail; + } + + /* Compute the routes. At this point we might select between multiple + * algorithms. */ + struct timemono time_start = time_mono(); + err = default_routes(rq, rq, srcnode, dstnode, *info->amount, + /* only one path? = */ + have_layer(info->layers, "auto.no_mpp_support"), + *info->maxfee, *info->finalcltv, *info->maxdelay, + &flows, &probability); + struct timerel time_delta = timemono_between(time_mono(), time_start); + + /* log the time of computation */ + rq_log(tmpctx, rq, LOG_DBG, "get_routes %s %" PRIu64 " ms", + err ? "failed after" : "completed in", + time_to_msec(time_delta)); if (err) - return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "%s", err); + goto fail; + /* otherwise we continue */ + assert(tal_count(flows) > 0); + rq_log(tmpctx, rq, LOG_DBG, "Final answer has %zu flows", + tal_count(flows)); + + /* convert flows to routes */ + convert_flows_to_routes(rq, rq, &routes, &amounts, *info->finalcltv, + flows); + assert(tal_count(routes) == tal_count(flows)); + assert(tal_count(amounts) == tal_count(flows)); + + /* At last we remove the localmods from the gossmap. */ + gossmap_remove_localmods(askrene->gossmap, localmods); + + /* output the results */ response = jsonrpc_stream_success(cmd); - json_add_u64(response, "probability_ppm", (u64)(probability * 1000000)); - json_array_start(response, "routes"); - for (size_t i = 0; i < tal_count(routes); i++) { - json_object_start(response, NULL); - json_add_u64(response, "probability_ppm", (u64)(routes[i]->success_prob * 1000000)); - json_add_amount_msat(response, "amount_msat", amounts[i]); - json_add_u32(response, "final_cltv", *info->finalcltv); - json_array_start(response, "path"); - for (size_t j = 0; j < tal_count(routes[i]->hops); j++) { - struct short_channel_id_dir scidd; - const struct route_hop *r = &routes[i]->hops[j]; - json_object_start(response, NULL); - scidd.scid = r->scid; - scidd.dir = r->direction; - json_add_short_channel_id_dir(response, "short_channel_id_dir", scidd); - json_add_node_id(response, "next_node_id", &r->node_id); - json_add_amount_msat(response, "amount_msat", r->amount); - json_add_u32(response, "delay", r->delay); - json_object_end(response); - } - json_array_end(response); - json_object_end(response); - } - json_array_end(response); + json_add_getroutes(response, routes, amounts, probability, + *info->finalcltv); return command_finished(cmd, response); + +fail: + assert(err); + gossmap_remove_localmods(askrene->gossmap, localmods); + return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "%s", err); } static void add_localchan(struct gossmap_localmods *mods, From 28dc007aab7f36d1fdd6502a54a7193a144f05e4 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Fri, 14 Mar 2025 12:25:00 +0100 Subject: [PATCH 4/4] askrene: add a dev parameter to switch algorithm Changelog-None Signed-off-by: Lagrang3 --- plugins/askrene/askrene.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/plugins/askrene/askrene.c b/plugins/askrene/askrene.c index 075b71fe1eab..b1e02a950d72 100644 --- a/plugins/askrene/askrene.c +++ b/plugins/askrene/askrene.c @@ -331,6 +331,24 @@ const char *fmt_flow_full(const tal_t *ctx, return str; } +enum algorithm { + ALGO_DEFAULT, +}; + +static struct command_result * +param_algorithm(struct command *cmd, const char *name, const char *buffer, + const jsmntok_t *tok, enum algorithm **algo) +{ + const char *algo_str = json_strdup(cmd, buffer, tok); + *algo = tal(cmd, enum algorithm); + if (streq(algo_str, "default")) + **algo = ALGO_DEFAULT; + else + return command_fail_badparam(cmd, name, buffer, tok, + "unknown algorithm"); + return NULL; +} + struct getroutes_info { struct command *cmd; struct node_id *source, *dest; @@ -340,6 +358,8 @@ struct getroutes_info { struct additional_cost_htable *additional_costs; /* Non-NULL if we are told to use "auto.localchans" */ struct layer *local_layer; + /* algorithm selection, only dev */ + enum algorithm *dev_algo; }; static void apply_layers(struct askrene *askrene, struct route_query *rq, @@ -562,8 +582,9 @@ static struct command_result *do_getroutes(struct command *cmd, } /* Compute the routes. At this point we might select between multiple - * algorithms. */ + * algorithms. Right now there is only one algorithm available. */ struct timemono time_start = time_mono(); + assert(*info->dev_algo == ALGO_DEFAULT); err = default_routes(rq, rq, srcnode, dstnode, *info->amount, /* only one path? = */ have_layer(info->layers, "auto.no_mpp_support"), @@ -723,6 +744,8 @@ static struct command_result *json_getroutes(struct command *cmd, p_req("final_cltv", param_u32, &info->finalcltv), p_opt_def("maxdelay", param_u32, &info->maxdelay, maxdelay_allowed), + p_opt_dev("dev_algorithm", param_algorithm, + &info->dev_algo, ALGO_DEFAULT), NULL)) return command_param_failed();