askrene: refine: expose some of the refine API
What changed, and why it matters
This commit is a routine internal code reorganization in Core Lightning's routing plugin (askrene). It exposes some helper functions and adds new utilities for merging duplicate payment routes and checking route constraints. There is no indication of a security fix or vulnerability being patched.
No security action required. Review as normal code-quality change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors plugins/askrene/refine.c/h. It removes ‘static’ from new_reservations() and create_flow_reservations(), making them callable from other translation units. It extracts the ‘write selected flows’ logic into a helper, adds create_flow_reservations_verify() (checks liquidity constraints before reserving), squash_flows() (merges duplicate paths to save fees), and flows_probability() (computes combined probability of a flow set). The changes are API/internal-structure oriented and do not alter wire protocol, authentication, cryptography, or remote-exposed behavior.
Changed components
plugins/askrene/refine.cplugins/askrene/refine.hInspect captured patch +150 / −17
diff --git a/plugins/askrene/refine.c b/plugins/askrene/refine.c
index 97472443..0d1a2c2f 100644
--- a/plugins/askrene/refine.c
+++ b/plugins/askrene/refine.c
@@ -6,6 +6,7 @@
#include <plugins/askrene/flow.h>
#include <plugins/askrene/refine.h>
#include <plugins/askrene/reserve.h>
+#include <string.h>
/* Channel data for fast retrieval. */
struct channel_data {
@@ -35,8 +36,8 @@ static void destroy_reservations(struct reserve_hop *rhops, struct askrene *askr
reserve_remove(askrene->reserved, &rhops[i]);
}
-static struct reserve_hop *new_reservations(const tal_t *ctx,
- const struct route_query *rq)
+struct reserve_hop *new_reservations(const tal_t *ctx,
+ const struct route_query *rq)
{
struct reserve_hop *rhops = tal_arr(ctx, struct reserve_hop, 0);
@@ -108,9 +109,9 @@ static void subtract_reservation(struct reserve_hop **reservations,
reserve_add(askrene->reserved, prev, rq->cmd->id);
}
-static void create_flow_reservations(const struct route_query *rq,
- struct reserve_hop **reservations,
- const struct flow *flow)
+void create_flow_reservations(const struct route_query *rq,
+ struct reserve_hop **reservations,
+ const struct flow *flow)
{
struct amount_msat msat;
@@ -170,6 +171,32 @@ static void change_flow_delivers(const struct route_query *rq,
create_flow_reservations(rq, reservations, flow);
}
+bool create_flow_reservations_verify(const struct route_query *rq,
+ struct reserve_hop **reservations,
+ const struct flow *flow)
+{
+ struct amount_msat msat;
+ msat = flow->delivers;
+ for (int i = tal_count(flow->path) - 1; i >= 0; i--) {
+ struct amount_msat known_min, known_max;
+ const struct half_chan *h = flow_edge(flow, i);
+ struct amount_msat amount_to_reserve = msat;
+ struct short_channel_id_dir scidd;
+
+ get_scidd(rq->gossmap, flow, i, &scidd);
+ get_constraints(rq, flow->path[i], flow->dirs[i], &known_min,
+ &known_max);
+ if (amount_msat_greater(amount_to_reserve, known_max))
+ return false;
+
+ if (!amount_msat_add_fee(&msat, h->base_fee,
+ h->proportional_fee))
+ abort();
+ }
+ create_flow_reservations(rq, reservations, flow);
+ return true;
+}
+
/* We use an fp16_t approximatin for htlc_max/min: this gets the exact value. */
static struct amount_msat get_chan_htlc_max(const struct route_query *rq,
const struct gossmap_chan *c,
@@ -846,6 +873,24 @@ static struct amount_msat remove_excess(struct flow **flows,
return all_deliver;
}
+static void write_selected_flows(const tal_t *ctx, size_t *flows_index,
+ struct flow ***flows)
+{
+ struct flow **tmp_flows = tal_arr(ctx, struct flow *, 0);
+ for (size_t i = 0; i < tal_count(flows_index); i++) {
+ tal_arr_expand(&tmp_flows, (*flows)[flows_index[i]]);
+ (*flows)[flows_index[i]] = NULL;
+ }
+ for (size_t i = 0; i < tal_count(*flows); i++) {
+ (*flows)[i] = tal_free((*flows)[i]);
+ }
+ tal_resize(flows, 0);
+ for (size_t i = 0; i < tal_count(tmp_flows); i++) {
+ tal_arr_expand(flows, tmp_flows[i]);
+ }
+ tal_free(tmp_flows);
+}
+
/* FIXME: on failure return error message */
const char *refine_flows(const tal_t *ctx, struct route_query *rq,
struct amount_msat deliver, struct flow ***flows)
@@ -911,18 +956,7 @@ const char *refine_flows(const tal_t *ctx, struct route_query *rq,
}
/* finally write the remaining flows */
- struct flow **tmp_flows = tal_arr(working_ctx, struct flow *, 0);
- for (size_t i = 0; i < tal_count(flows_index); i++) {
- tal_arr_expand(&tmp_flows, (*flows)[flows_index[i]]);
- (*flows)[flows_index[i]] = NULL;
- }
- for (size_t i = 0; i < tal_count(*flows); i++) {
- (*flows)[i] = tal_free((*flows)[i]);
- }
- tal_resize(flows, 0);
- for (size_t i = 0; i < tal_count(tmp_flows); i++) {
- tal_arr_expand(flows, tmp_flows[i]);
- }
+ write_selected_flows(working_ctx, flows_index, flows);
tal_free(working_ctx);
return NULL;
@@ -931,3 +965,80 @@ fail:
tal_free(working_ctx);
return error_message;
}
+
+/* Order of flows according to path string */
+static int cmppath_flows(const size_t *a, const size_t *b, char **paths_str)
+{
+ return strcmp(paths_str[*a], paths_str[*b]);
+}
+
+void squash_flows(const tal_t *ctx, struct route_query *rq,
+ struct flow ***flows)
+{
+ const tal_t *working_ctx = tal(ctx, tal_t);
+ size_t *flows_index = tal_arrz(working_ctx, size_t, tal_count(*flows));
+ char **paths_str = tal_arrz(working_ctx, char *, tal_count(*flows));
+ struct channel_data **channel_mpp_cache =
+ new_channel_mpp_cache(working_ctx, rq, *flows);
+ struct amount_msat *max_deliverable = tal_arrz(
+ working_ctx, struct amount_msat, tal_count(channel_mpp_cache));
+
+ for (size_t i = 0; i < tal_count(flows_index); i++) {
+ struct flow *flow = (*flows)[i];
+ struct short_channel_id_dir scidd;
+ flows_index[i] = i;
+ paths_str[i] = tal_strdup(working_ctx, "");
+ max_deliverable[i] = path_max_deliverable(channel_mpp_cache[i]);
+
+ for (size_t j = 0; j < tal_count(flow->path); j++) {
+ scidd.scid =
+ gossmap_chan_scid(rq->gossmap, flow->path[j]);
+ scidd.dir = flow->dirs[j];
+ tal_append_fmt(
+ &paths_str[i], "%s%s", j > 0 ? "->" : "",
+ fmt_short_channel_id_dir(working_ctx, &scidd));
+ }
+ }
+
+ asort(flows_index, tal_count(flows_index), cmppath_flows, paths_str);
+ for (size_t i = 0; i < tal_count(flows_index); i++) {
+ const size_t j = i + 1;
+ struct amount_msat combined;
+ struct amount_msat max = max_deliverable[flows_index[i]];
+
+ /* same path? We merge */
+ while (j < tal_count(flows_index) &&
+ cmppath_flows(&flows_index[i],
+ &flows_index[j],
+ paths_str) == 0) {
+ if (!amount_msat_add(
+ &combined, (*flows)[flows_index[i]]->delivers,
+ (*flows)[flows_index[j]]->delivers))
+ abort();
+ /* do we break any HTLC max limits */
+ if (amount_msat_greater(combined, max))
+ break;
+ (*flows)[flows_index[i]]->delivers = combined;
+ tal_arr_remove(&flows_index, j);
+ }
+ }
+
+ write_selected_flows(working_ctx, flows_index, flows);
+
+ tal_free(working_ctx);
+}
+
+double flows_probability(const tal_t *ctx, struct route_query *rq,
+ struct flow ***flows)
+{
+ const tal_t *working_ctx = tal(ctx, tal_t);
+ struct reserve_hop *reservations = new_reservations(working_ctx, rq);
+ double probability = 1.0;
+
+ for (size_t i = 0; i < tal_count(*flows); i++) {
+ probability *= flow_probability((*flows)[i], rq);
+ create_flow_reservations(rq, &reservations, (*flows)[i]);
+ }
+ tal_free(working_ctx);
+ return probability;
+}
diff --git a/plugins/askrene/refine.h b/plugins/askrene/refine.h
index 8757f736..b608e9a5 100644
--- a/plugins/askrene/refine.h
+++ b/plugins/askrene/refine.h
@@ -7,6 +7,13 @@ struct route_query;
struct amount_msat;
struct flow;
+struct reserve_hop *new_reservations(const tal_t *ctx,
+ const struct route_query *rq);
+
+void create_flow_reservations(const struct route_query *rq,
+ struct reserve_hop **reservations,
+ const struct flow *flow);
+
/* We got an answer from min-cost-flow, but we now need to:
* 1. Add fixup exact delivery amounts since MCF deals in larger granularity than msat.
* 2. Add fees which accumulate through the route.
@@ -24,8 +31,23 @@ refine_with_fees_and_limits(const tal_t *ctx,
struct flow ***flows,
double *flowset_probability);
+/* create flow reservations, but first verify that the flow indeeds fits in the
+ * liquidity constraints. Takes into account reservations that include per HTLC
+ * extra amounts to pay for onchain fees. */
+bool create_flow_reservations_verify(const struct route_query *rq,
+ struct reserve_hop **reservations,
+ const struct flow *flow);
+
/* Modify flows to meet HTLC min/max requirements.
* It takes into account the exact value of the fees expected at each hop. */
const char *refine_flows(const tal_t *ctx, struct route_query *rq,
struct amount_msat deliver, struct flow ***flows);
+
+/* Duplicated flows are merged into one. This saves in base fee and HTLC fees.
+ */
+void squash_flows(const tal_t *ctx, struct route_query *rq,
+ struct flow ***flows);
+
+double flows_probability(const tal_t *ctx, struct route_query *rq,
+ struct flow ***flows);
#endif /* LIGHTNING_PLUGINS_ASKRENE_REFINE_H */
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.