askrene: refine: remove refine_with_fee_and_limits
What changed, and why it matters
This commit removes a large block of unused code from the askrene routing plugin in Core Lightning. The deleted functions handled fee calculations, capacity limits, and HTLC constraints during payment route refinement, but they were no longer being called anywhere. There is no indication in the commit message or diff that this fixes a security bug; it appears to be routine code cleanup.
No immediate security action is required. Reviewers may want to confirm that the removed refinement logic is genuinely unused and that its responsibilities have been taken over by other code paths (e.g., the MCF solver or newer refinement functions) to avoid regressions in route validity.
Security signals we found
Large deletion of routing/refinement logic with no replacement code
Removed functions previously performed fee, capacity, and HTLC constraint validation
Commit message frames change as cleanup of unused code, not a security fix
Evidence from the diff
The commit deletes refine_with_fees_and_limits() and several helper functions (subtract_reservation, remove_flow_reservations, change_flow_delivers, flow_max_capacity, constrain_flow, flow_remaining_capacity, pick_most_likely_flow, flow_violates_min, duplicate_one_flow, edge_probability) from plugins/askrene/refine.c and their declarations from refine.h. The removed logic adjusted MCF-derived flows for fees, HTLC min/max, and channel capacity, then recomputed flowset probability. The commit message says the functions are unused and explicitly marks Changelog-None. No replacement logic is added; the diff is purely removal.
Changed components
plugins/askrene/refine.cplugins/askrene/refine.hInspect captured patch +0 / −483
diff --git a/plugins/askrene/refine.c b/plugins/askrene/refine.c
index d6e785e5..ee57628b 100644
--- a/plugins/askrene/refine.c
+++ b/plugins/askrene/refine.c
@@ -90,25 +90,6 @@ static void add_reservation(struct reserve_hop **reservations,
tal_arr_expand(reservations, rhop);
}
-static void subtract_reservation(struct reserve_hop **reservations,
- const struct route_query *rq,
- const struct gossmap_chan *chan,
- const struct short_channel_id_dir *scidd,
- struct amount_msat amt)
-{
- struct reserve_hop *prev;
- struct askrene *askrene = get_askrene(rq->plugin);
-
- prev = find_reservation(*reservations, scidd);
- assert(prev);
-
- reserve_remove(askrene->reserved, prev);
- if (!amount_msat_sub(&prev->amount, prev->amount, amt))
- abort();
- /* Adding a zero reserve is weird, but legal and easy! */
- reserve_add(askrene->reserved, prev, rq->cmd->id);
-}
-
void create_flow_reservations(const struct route_query *rq,
struct reserve_hop **reservations,
const struct flow *flow)
@@ -136,41 +117,6 @@ void create_flow_reservations(const struct route_query *rq,
}
}
-static void remove_flow_reservations(const struct route_query *rq,
- struct reserve_hop **reservations,
- const struct flow *flow)
-{
- struct amount_msat msat = flow->delivers;
- for (int i = tal_count(flow->path) - 1; i >= 0; i--) {
- const struct half_chan *h = flow_edge(flow, i);
- struct amount_msat amount_to_reserve;
- struct short_channel_id_dir scidd;
-
- get_scidd(rq->gossmap, flow, i, &scidd);
-
- /* Reserve more for local channels if it reduces capacity */
- if (!amount_msat_add(&amount_to_reserve, msat,
- get_additional_per_htlc_cost(rq, &scidd)))
- abort();
-
- subtract_reservation(reservations, rq, flow->path[i], &scidd,
- amount_to_reserve);
- if (!amount_msat_add_fee(&msat,
- h->base_fee, h->proportional_fee))
- plugin_err(rq->plugin, "Adding fee to amount");
- }
-}
-
-static void change_flow_delivers(const struct route_query *rq,
- struct flow *flow,
- struct reserve_hop **reservations,
- struct amount_msat new)
-{
- remove_flow_reservations(rq, reservations, flow);
- flow->delivers = new;
- create_flow_reservations(rq, reservations, flow);
-}
-
bool create_flow_reservations_verify(const struct route_query *rq,
struct reserve_hop **reservations,
const struct flow *flow)
@@ -229,418 +175,6 @@ enum why_capped {
CAPPED_CAPACITY,
};
-/* Get exact maximum we can deliver with this flow. Returns reason
- * why this is the limit (max_hltc or capacity), and optionally sets scidd */
-static enum why_capped flow_max_capacity(const struct route_query *rq,
- const struct flow *flow,
- struct amount_msat *deliverable,
- struct short_channel_id_dir *scidd_why,
- struct amount_msat *amount_why)
-{
- struct amount_msat max_msat = AMOUNT_MSAT(-1ULL);
- enum why_capped why_capped = CAPPED_CAPACITY;
-
- for (int i = tal_count(flow->path) - 1; i >= 0; i--) {
- const struct half_chan *h = flow_edge(flow, i);
- struct amount_msat min, max, htlc_max;
- struct short_channel_id_dir scidd;
-
- get_scidd(rq->gossmap, flow, i, &scidd);
- /* We can pass constraints due to addition of fees! */
- get_constraints(rq, flow->path[i], flow->dirs[i], &min, &max);
-
- if (amount_msat_greater(max_msat, max)) {
- why_capped = CAPPED_CAPACITY;
- if (scidd_why)
- *scidd_why = scidd;
- if (amount_why)
- *amount_why = max;
- max_msat = max;
- }
-
- htlc_max = get_chan_htlc_max(rq, flow->path[i], &scidd);
- if (amount_msat_greater(max_msat, htlc_max)) {
- why_capped = CAPPED_HTLC_MAX;
- if (scidd_why)
- *scidd_why = scidd;
- if (amount_why)
- *amount_why = htlc_max;
- max_msat = htlc_max;
- }
- if (!amount_msat_add_fee(&max_msat, h->base_fee, h->proportional_fee))
- max_msat = AMOUNT_MSAT(-1ULL);
- }
-
- /* Calculate deliverable max */
- *deliverable = max_msat;
- for (size_t i = 0; i < tal_count(flow->path); i++) {
- const struct half_chan *h = flow_edge(flow, i);
- *deliverable = amount_msat_sub_fee(*deliverable,
- h->base_fee,
- h->proportional_fee);
- }
- return why_capped;
-}
-
-
-/* We have a basic set of flows, but we need to add fees, and take into
- * account that "spendable" estimates are for a single HTLC. This can
- * push us again over capacity or htlc_maximum_msat.
- *
- * We may have to reduce the flow amount in response to these.
- *
- * We also check for going below htlc_maximum_msat at this point: this
- * is unusual and means it's small, so we just remove that flow if
- * this happens, as we can make it up by buffing up the other flows
- * (or, it's simply impossible).
- */
-static const char *constrain_flow(const tal_t *ctx,
- struct route_query *rq,
- struct flow *flow)
-{
- struct amount_msat deliverable, msat, amount_capped;
- enum why_capped why_capped;
- struct short_channel_id_dir scidd_capped;
-
- why_capped = flow_max_capacity(rq, flow, &deliverable,
- &scidd_capped, &amount_capped);
- if (amount_msat_less(deliverable, flow->delivers)) {
- rq_log(tmpctx, rq, LOG_INFORM,
- "Flow reduced to deliver %s not %s, because %s %s %s",
- fmt_amount_msat(tmpctx, deliverable),
- fmt_amount_msat(tmpctx, flow->delivers),
- fmt_short_channel_id_dir(tmpctx, &scidd_capped),
- why_capped == CAPPED_HTLC_MAX
- ? "advertizes htlc_maximum_msat"
- : "has remaining capacity",
- fmt_amount_msat(tmpctx, amount_capped));
- flow->delivers = deliverable;
- }
-
- /* Now, check if any of them violate htlc_min */
- msat = flow->delivers;
- for (int i = tal_count(flow->path) - 1; i >= 0; i--) {
- const struct half_chan *h = flow_edge(flow, i);
- struct amount_msat min;
- struct short_channel_id_dir scidd;
-
- get_scidd(rq->gossmap, flow, i, &scidd);
- min = get_chan_htlc_min(rq, flow->path[i], &scidd);
-
- if (amount_msat_less(msat, min)) {
- return rq_log(ctx, rq, LOG_UNUSUAL,
- "Amount %s below minimum %s across %s",
- fmt_amount_msat(tmpctx, msat),
- fmt_amount_msat(tmpctx, min),
- fmt_short_channel_id_dir(tmpctx, &scidd));
- }
- if (!amount_msat_add_fee(&msat,
- h->base_fee, h->proportional_fee))
- plugin_err(rq->plugin, "Adding fee to amount");
- }
- return NULL;
-}
-
-static struct amount_msat flow_remaining_capacity(const struct route_query *rq,
- struct reserve_hop **reservations,
- const struct flow *flow)
-{
- struct amount_msat max, diff;
-
- /* Remove ourselves from reservation temporarily, so we don't
- * accidentally cap! */
- remove_flow_reservations(rq, reservations, flow);
- flow_max_capacity(rq, flow, &max, NULL, NULL);
-
- /* This seems to happen. Which is strange, since flows should
- already be constrained. */
- if (!amount_msat_sub(&diff, max, flow->delivers)) {
- plugin_log(rq->plugin, LOG_BROKEN,
- "Flow delivers %s but max only %s? flow=%s",
- fmt_amount_msat(tmpctx, flow->delivers),
- fmt_amount_msat(tmpctx, max),
- fmt_flow_full(rq, rq, flow));
- for (size_t i = 0; i < tal_count(*reservations); i++) {
- plugin_log(rq->plugin, LOG_BROKEN,
- "Reservation #%zi: %s on %s",
- i,
- fmt_amount_msat(tmpctx, (*reservations)[i].amount),
- fmt_short_channel_id_dir(tmpctx, &(*reservations)[i].scidd));
- }
- diff = AMOUNT_MSAT(0);
- }
- create_flow_reservations(rq, reservations, flow);
- return diff;
-}
-
-/* What's the "best" flow to add to? */
-static struct flow *pick_most_likely_flow(const struct route_query *rq,
- struct flow **flows,
- struct reserve_hop **reservations,
- struct amount_msat additional)
-{
- double best_prob = 0;
- struct flow *best_flow = NULL;
-
- for (size_t i = 0; i < tal_count(flows); i++) {
- struct amount_msat cap;
- double prob = flow_probability(flows[i], rq);
- if (prob < best_prob)
- continue;
- cap = flow_remaining_capacity(rq, reservations, flows[i]);
- if (amount_msat_less(cap, additional))
- continue;
- best_prob = prob;
- best_flow = flows[i];
- }
-
- return best_flow;
-}
-
-/* A secondary check for htlc_min violations, after excess trimming. */
-static const char *flow_violates_min(const tal_t *ctx,
- struct route_query *rq,
- const struct flow *flow)
-{
- struct amount_msat msat = flow->delivers;
- for (int i = tal_count(flow->path) - 1; i >= 0; i--) {
- const struct half_chan *h = flow_edge(flow, i);
- struct amount_msat min;
- struct short_channel_id_dir scidd;
- get_scidd(rq->gossmap, flow, i, &scidd);
-
- min = get_chan_htlc_min(rq, flow->path[i], &scidd);
- if (amount_msat_less(msat, min)) {
- return rq_log(ctx, rq, LOG_UNUSUAL,
- "Sending %s across %s would violate htlc_min (~%s)",
- fmt_amount_msat(tmpctx, msat),
- fmt_short_channel_id_dir(tmpctx, &scidd),
- fmt_amount_msat(tmpctx, min));
- }
- if (!amount_msat_add_fee(&msat, h->base_fee, h->proportional_fee))
- plugin_err(rq->plugin, "Adding fee to amount");
- }
- return NULL;
-}
-
-/* If one flow is constrained by htlc_max, we might be able to simply
- * duplicate it. This is naive: it could still fail due to total
- * capacity, but it is a corner case anyway. */
-static bool duplicate_one_flow(const struct route_query *rq,
- struct reserve_hop **reservations,
- struct flow ***flows)
-{
- for (size_t i = 0; i < tal_count(*flows); i++) {
- struct flow *flow = (*flows)[i], *new_flow;
- struct amount_msat max, new_amount;
- /* Don't create 0 flow (shouldn't happen, but be sure) */
- if (amount_msat_less(flow->delivers, AMOUNT_MSAT(2)))
- continue;
-
- if (flow_max_capacity(rq, flow, &max, NULL, NULL)
- != CAPPED_HTLC_MAX)
- continue;
-
- new_flow = tal(*flows, struct flow);
- new_flow->path = tal_dup_talarr(new_flow,
- const struct gossmap_chan *,
- flow->path);
- new_flow->dirs = tal_dup_talarr(new_flow, int,
- flow->dirs);
- new_flow->delivers = amount_msat_div(flow->delivers, 2);
- create_flow_reservations(rq, reservations, new_flow);
-
- if (!amount_msat_sub(&new_amount,
- flow->delivers, new_flow->delivers))
- abort();
- change_flow_delivers(rq, flow, reservations, new_amount);
- tal_arr_expand(flows, new_flow);
- return true;
- }
- return false;
-}
-
-/* Stolen whole-cloth from @Lagrang3 in renepay's flow.c. Wrong
- * because of htlc overhead in reservations! */
-static double edge_probability(const struct route_query *rq,
- const struct short_channel_id_dir *scidd,
- struct amount_msat sent)
-{
- struct amount_msat numerator, denominator;
- struct amount_msat mincap, maxcap, additional;
- const struct gossmap_chan *c = gossmap_find_chan(rq->gossmap, &scidd->scid);
-
- get_constraints(rq, c, scidd->dir, &mincap, &maxcap);
-
- /* We add an extra per-htlc reservation for the *next* HTLC, so we "over-reserve"
- * on local channels. Undo that! */
- additional = get_additional_per_htlc_cost(rq, scidd);
- if (!amount_msat_accumulate(&mincap, additional)
- || !amount_msat_accumulate(&maxcap, additional))
- abort();
-
- if (amount_msat_less_eq(sent, mincap))
- return 1.0;
- else if (amount_msat_greater(sent, maxcap))
- return 0.0;
-
- /* Linear probability: 1 - (spend - min) / (max - min) */
-
- /* spend > mincap, from above. */
- if (!amount_msat_sub(&numerator, sent, mincap))
- abort();
- /* This can only fail is maxcap was < mincap,
- * so we would be captured above */
- if (!amount_msat_sub(&denominator, maxcap, mincap))
- abort();
- return 1.0 - amount_msat_ratio(numerator, denominator);
-}
-
-const char *
-refine_with_fees_and_limits(const tal_t *ctx,
- struct route_query *rq,
- struct amount_msat deliver,
- struct flow ***flows,
- double *flowset_probability)
-{
- struct reserve_hop *reservations = new_reservations(NULL, rq);
- struct amount_msat more_to_deliver;
- const char *flow_constraint_error = NULL;
- const char *ret;
- bool tried_split_flow = false;
-
- for (size_t i = 0; i < tal_count(*flows);) {
- struct flow *flow = (*flows)[i];
-
- flow_constraint_error = constrain_flow(tmpctx, rq, flow);
- if (!flow_constraint_error) {
- create_flow_reservations(rq, &reservations, flow);
- i++;
- continue;
- }
-
- rq_log(tmpctx, rq, LOG_UNUSUAL, "Flow %zu/%zu was too constrained (%s) so removing it",
- i, tal_count(*flows), flow_constraint_error);
- /* This flow was reduced to 0 / impossible, remove */
- tal_arr_remove(flows, i);
- }
-
- /* Due to granularity of MCF, we can deliver slightly more than expected:
- * trim one in that case. */
- if (!amount_msat_sub(&more_to_deliver, deliver,
- flowset_delivers(rq->plugin, *flows))) {
- struct amount_msat excess;
- if (!amount_msat_sub(&excess,
- flowset_delivers(rq->plugin, *flows),
- deliver))
- abort();
- for (size_t i = 0; i < tal_count(*flows); i++) {
- const char *err;
- struct amount_msat trimmed;
- if (!amount_msat_sub(&trimmed, (*flows)[i]->delivers, excess))
- continue;
-
- rq_log(tmpctx, rq, LOG_DBG,
- "%s extra, trimming flow %zu/%zu",
- fmt_amount_msat(tmpctx, excess), i, tal_count(*flows));
- change_flow_delivers(rq, (*flows)[i], &reservations, trimmed);
- /* In theory, this can violate min_htlc! Thanks @Lagrang3! */
- err = flow_violates_min(tmpctx, rq, (*flows)[i]);
- if (err) {
- /* This flow was reduced to 0 / impossible, remove */
- remove_flow_reservations(rq, &reservations, (*flows)[i]);
- tal_arr_remove(flows, i);
- i--;
- /* If this causes failure, indicate why! */
- flow_constraint_error = err;
- continue;
- }
- break;
- }
-
- /* Usually this should shed excess, *BUT* maybe one
- * was deleted instead for being below minimum */
- if (!amount_msat_sub(&more_to_deliver, deliver,
- flowset_delivers(rq->plugin, *flows))) {
- ret = rq_log(ctx, rq, LOG_UNUSUAL,
- "Flowset delivers %s instead of %s, can't shed excess?",
- fmt_amount_msat(tmpctx, flowset_delivers(rq->plugin, *flows)),
- fmt_amount_msat(tmpctx, deliver));
- goto out;
- }
-
- rq_log(tmpctx, rq, LOG_DBG,
- "After dealing with excess, more_to_deliver=%s",
- fmt_amount_msat(tmpctx, more_to_deliver));
- }
-
- /* The residual is minimal. In theory we could add one msat at a time
- * to the most probably flow which has capacity. For speed, we break it
- * into the number of flows, then assign each one. */
- while (!amount_msat_is_zero(more_to_deliver) && tal_count(*flows)) {
- struct flow *f;
- struct amount_msat extra, new_delivers;
-
- /* How much more do we deliver? Round up if we can */
- extra = amount_msat_div(more_to_deliver, tal_count(*flows));
- if (amount_msat_less(extra, more_to_deliver)) {
- if (!amount_msat_accumulate(&extra, AMOUNT_MSAT(1)))
- abort();
- }
-
- /* This happens when we have a single flow, and hit
- * htlc_max. For this corner case, we split into an
- * additional flow, but only once! */
- f = pick_most_likely_flow(rq, *flows, &reservations, extra);
- if (!f) {
- if (tried_split_flow || !duplicate_one_flow(rq, &reservations, flows)) {
- ret = rq_log(ctx, rq, LOG_BROKEN,
- "We couldn't quite afford it, we need to send %s more for fees: please submit a bug report!",
- fmt_amount_msat(tmpctx, more_to_deliver));
- goto out;
- }
- tried_split_flow = true;
- continue;
- }
-
- if (!amount_msat_add(&new_delivers, f->delivers, extra))
- abort();
- change_flow_delivers(rq, f, &reservations, new_delivers);
-
- /* Should not happen, since extra comes from div... */
- if (!amount_msat_sub(&more_to_deliver, more_to_deliver, extra))
- abort();
- }
-
- if (!amount_msat_eq(deliver, flowset_delivers(rq->plugin, *flows))) {
- /* This should only happen if there were no flows */
- if (tal_count(*flows) == 0) {
- ret = tal_steal(ctx, flow_constraint_error);
- goto out;
- }
- plugin_err(rq->plugin, "Flows delivered only %s of %s?",
- fmt_amount_msat(tmpctx, flowset_delivers(rq->plugin, *flows)),
- fmt_amount_msat(tmpctx, deliver));
- }
-
- ret = NULL;
-
- /* Total flowset probability is now easily calculated given reservations
- * contains the total amounts through each channel (once we remove them) */
- destroy_reservations(reservations, get_askrene(rq->plugin));
- tal_add_destructor2(reservations, destroy_reservations, get_askrene(rq->plugin));
-
- *flowset_probability = 1.0;
- for (size_t i = 0; i < tal_count(reservations); i++) {
- const struct reserve_hop *r = &reservations[i];
- *flowset_probability *= edge_probability(rq, &r->scidd, r->amount);
- }
-
-out:
- tal_free(reservations);
- return ret;
-}
-
/* Cache channel data along the path used by this flow. */
static struct channel_data *new_channel_path_cache(const tal_t *ctx,
struct route_query *rq,
diff --git a/plugins/askrene/refine.h b/plugins/askrene/refine.h
index b608e9a5..065cd1b2 100644
--- a/plugins/askrene/refine.h
+++ b/plugins/askrene/refine.h
@@ -14,23 +14,6 @@ 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.
- * 3. Check for htlc_minimum_msat violations (we simply remove those flows).
- * 4. Trim any flows which (after fees) now violate maximum htlc_minimum_msat/capacity bounds.
- *
- * We try to reassign missing sats to the remaining flows, which is usually easy.
- *
- * Returns NULL on success, or an error message for the caller.
- */
-const char *
-refine_with_fees_and_limits(const tal_t *ctx,
- struct route_query *rq,
- struct amount_msat deliver,
- 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. */
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.