From e4b84f1ffb8566756cdbe79739f43efceb232942 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 7 Aug 2024 11:19:54 +0930 Subject: [PATCH] askrene: copy flow and dijkstra from renepay. Still don't actually try compiling them. Signed-off-by: Rusty Russell --- plugins/askrene/dijkstra.c | 186 +++++++++++++++ plugins/askrene/dijkstra.h | 30 +++ plugins/askrene/flow.c | 451 +++++++++++++++++++++++++++++++++++++ plugins/askrene/flow.h | 99 ++++++++ plugins/askrene/mcf.c | 4 +- 5 files changed, 768 insertions(+), 2 deletions(-) create mode 100644 plugins/askrene/dijkstra.c create mode 100644 plugins/askrene/dijkstra.h create mode 100644 plugins/askrene/flow.c create mode 100644 plugins/askrene/flow.h diff --git a/plugins/askrene/dijkstra.c b/plugins/askrene/dijkstra.c new file mode 100644 index 000000000..b3f9d39de --- /dev/null +++ b/plugins/askrene/dijkstra.c @@ -0,0 +1,186 @@ +#define NDEBUG 1 +#include "config.h" +#include + +/* In the heap we keep node idx, but in this structure we keep the distance + * value associated to every node, and their position in the heap as a pointer + * so that we can update the nodes inside the heap when the distance label is + * changed. + * + * Therefore this is no longer a multipurpose heap, the node_idx must be an + * index between 0 and less than max_num_nodes. */ +struct dijkstra { + // + s64 *distance; + u32 *base; + u32 **heapptr; + size_t heapsize; + struct gheap_ctx gheap_ctx; +}; + +static const s64 INFINITE = INT64_MAX; + +/* Required a global dijkstra for gheap. */ +static struct dijkstra *global_dijkstra; + +/* The heap comparer for Dijkstra search. Since the top element must be the one + * with the smallest distance, we use the operator >, rather than <. */ +static int dijkstra_less_comparer( + const void *const ctx UNUSED, + const void *const a, + const void *const b) +{ + return global_dijkstra->distance[*(u32*)a] + > global_dijkstra->distance[*(u32*)b]; +} + +/* The heap move operator for Dijkstra search. */ +static void dijkstra_item_mover(void *const dst, const void *const src) +{ + u32 src_idx = *(u32*)src; + *(u32*)dst = src_idx; + + // we keep track of the pointer position of each element in the heap, + // for easy update. + global_dijkstra->heapptr[src_idx] = dst; +} + +/* Allocation of resources for the heap. */ +struct dijkstra *dijkstra_new(const tal_t *ctx, size_t max_num_nodes) +{ + struct dijkstra *dijkstra = tal(ctx, struct dijkstra); + + dijkstra->distance = tal_arr(dijkstra,s64,max_num_nodes); + dijkstra->base = tal_arr(dijkstra,u32,max_num_nodes); + dijkstra->heapptr = tal_arrz(dijkstra,u32*,max_num_nodes); + + dijkstra->heapsize=0; + + dijkstra->gheap_ctx.fanout=2; + dijkstra->gheap_ctx.page_chunks=1024; + dijkstra->gheap_ctx.item_size=sizeof(dijkstra->base[0]); + dijkstra->gheap_ctx.less_comparer=dijkstra_less_comparer; + dijkstra->gheap_ctx.less_comparer_ctx=NULL; + dijkstra->gheap_ctx.item_mover=dijkstra_item_mover; + + return dijkstra; +} + + +void dijkstra_init(struct dijkstra *dijkstra) +{ + const size_t max_num_nodes = tal_count(dijkstra->distance); + dijkstra->heapsize=0; + for(size_t i=0;idistance[i]=INFINITE; + dijkstra->heapptr[i] = NULL; + } +} +size_t dijkstra_size(const struct dijkstra *dijkstra) +{ + return dijkstra->heapsize; +} + +size_t dijkstra_maxsize(const struct dijkstra *dijkstra) +{ + return tal_count(dijkstra->distance); +} + +static void dijkstra_append(struct dijkstra *dijkstra, u32 node_idx, s64 distance) +{ + assert(dijkstra_size(dijkstra) < dijkstra_maxsize(dijkstra)); + assert(node_idx < dijkstra_maxsize(dijkstra)); + + const size_t pos = dijkstra->heapsize; + + dijkstra->base[pos]=node_idx; + dijkstra->distance[node_idx]=distance; + dijkstra->heapptr[node_idx] = &(dijkstra->base[pos]); + dijkstra->heapsize++; +} + +void dijkstra_update(struct dijkstra *dijkstra, u32 node_idx, s64 distance) +{ + assert(node_idx < dijkstra_maxsize(dijkstra)); + + if(!dijkstra->heapptr[node_idx]) + { + // not in the heap + dijkstra_append(dijkstra, node_idx,distance); + global_dijkstra = dijkstra; + gheap_restore_heap_after_item_increase( + &dijkstra->gheap_ctx, + dijkstra->base, + dijkstra->heapsize, + dijkstra->heapptr[node_idx] + - dijkstra->base); + global_dijkstra = NULL; + return; + } + + if(dijkstra->distance[node_idx] > distance) + { + // distance decrease + dijkstra->distance[node_idx] = distance; + + global_dijkstra = dijkstra; + gheap_restore_heap_after_item_increase( + &dijkstra->gheap_ctx, + dijkstra->base, + dijkstra->heapsize, + dijkstra->heapptr[node_idx] + - dijkstra->base); + global_dijkstra = NULL; + }else + { + // distance increase + dijkstra->distance[node_idx] = distance; + + global_dijkstra = dijkstra; + gheap_restore_heap_after_item_decrease( + &dijkstra->gheap_ctx, + dijkstra->base, + dijkstra->heapsize, + dijkstra->heapptr[node_idx] + - dijkstra->base); + global_dijkstra = NULL; + + } + // assert(gheap_is_heap(&dijkstra->gheap_ctx, + // dijkstra->base, + // dijkstra_size())); +} + +u32 dijkstra_top(const struct dijkstra *dijkstra) +{ + return dijkstra->base[0]; +} + +bool dijkstra_empty(const struct dijkstra *dijkstra) +{ + return dijkstra->heapsize==0; +} + +void dijkstra_pop(struct dijkstra *dijkstra) +{ + if(dijkstra->heapsize==0) + return; + + const u32 top = dijkstra_top(dijkstra); + assert(dijkstra->heapptr[top]==dijkstra->base); + + global_dijkstra = dijkstra; + gheap_pop_heap( + &dijkstra->gheap_ctx, + dijkstra->base, + dijkstra->heapsize--); + global_dijkstra = NULL; + + dijkstra->heapptr[top]=NULL; +} + +const s64* dijkstra_distance_data(const struct dijkstra *dijkstra) +{ + return dijkstra->distance; +} diff --git a/plugins/askrene/dijkstra.h b/plugins/askrene/dijkstra.h new file mode 100644 index 000000000..f8ff62a8a --- /dev/null +++ b/plugins/askrene/dijkstra.h @@ -0,0 +1,30 @@ +#ifndef LIGHTNING_PLUGINS_ASKRENE_DIJKSTRA_H +#define LIGHTNING_PLUGINS_ASKRENE_DIJKSTRA_H +#include "config.h" +#include +#include +#include + +/* Allocation of resources for the heap. */ +struct dijkstra *dijkstra_new(const tal_t *ctx, size_t max_num_nodes); + +/* Initialization of the heap for a new Dijkstra search. */ +void dijkstra_init(struct dijkstra *dijkstra); + +/* Inserts a new element in the heap. If node_idx was already in the heap then + * its distance value is updated. */ +void dijkstra_update(struct dijkstra *dijkstra, u32 node_idx, s64 distance); + +u32 dijkstra_top(const struct dijkstra *dijkstra); +bool dijkstra_empty(const struct dijkstra *dijkstra); +void dijkstra_pop(struct dijkstra *dijkstra); + +const s64* dijkstra_distance_data(const struct dijkstra *dijkstra); + +/* Number of elements on the heap. */ +size_t dijkstra_size(const struct dijkstra *dijkstra); + +/* Maximum number of elements the heap can host */ +size_t dijkstra_maxsize(const struct dijkstra *dijkstra); + +#endif /* LIGHTNING_PLUGINS_ASKRENE_DIJKSTRA_H */ diff --git a/plugins/askrene/flow.c b/plugins/askrene/flow.c new file mode 100644 index 000000000..2f67fb3be --- /dev/null +++ b/plugins/askrene/flow.c @@ -0,0 +1,451 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef SUPERVERBOSE +#define SUPERVERBOSE(...) +#else +#define SUPERVERBOSE_ENABLED 1 +#endif + +struct amount_msat *tal_flow_amounts(const tal_t *ctx, const struct flow *flow) +{ + const size_t pathlen = tal_count(flow->path); + struct amount_msat *amounts = tal_arr(ctx, struct amount_msat, pathlen); + amounts[pathlen - 1] = flow->amount; + + for (int i = (int)pathlen - 2; i >= 0; i--) { + const struct half_chan *h = flow_edge(flow, i + 1); + amounts[i] = amounts[i + 1]; + if (!amount_msat_add_fee(&amounts[i], h->base_fee, + h->proportional_fee)) + goto function_fail; + } + + return amounts; + +function_fail: + return tal_free(amounts); +} + +const char *fmt_flows(const tal_t *ctx, const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct flow **flows) +{ + tal_t *this_ctx = tal(ctx, tal_t); + double tot_prob = + flowset_probability(tmpctx, flows, gossmap, chan_extra_map, NULL); + assert(tot_prob >= 0); + char *buff = tal_fmt(ctx, "%zu subflows, prob %2lf\n", tal_count(flows), + tot_prob); + for (size_t i = 0; i < tal_count(flows); i++) { + struct amount_msat fee, delivered; + tal_append_fmt(&buff, " "); + for (size_t j = 0; j < tal_count(flows[i]->path); j++) { + struct short_channel_id scid = + gossmap_chan_scid(gossmap, flows[i]->path[j]); + tal_append_fmt(&buff, "%s%s", j ? "->" : "", + fmt_short_channel_id(this_ctx, scid)); + } + delivered = flows[i]->amount; + if (!flow_fee(&fee, flows[i])) { + abort(); + } + tal_append_fmt(&buff, " prob %.2f, %s delivered with fee %s\n", + flows[i]->success_prob, + fmt_amount_msat(this_ctx, delivered), + fmt_amount_msat(this_ctx, fee)); + } + + tal_free(this_ctx); + return buff; +} + +/* Returns the greatest amount we can deliver to the destination using this + * route. It takes into account the current knowledge, pending HTLC, + * htlc_max and fees. + * + * It fails if the maximum that we can + * deliver at node i is smaller than the minimum required to forward the least + * amount greater than zero to the next node. */ +enum askrene_errorcode +flow_maximum_deliverable(struct amount_msat *max_deliverable, + const struct flow *flow, + const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + const struct gossmap_chan **bad_channel) +{ + assert(tal_count(flow->path) > 0); + assert(tal_count(flow->dirs) > 0); + assert(tal_count(flow->path) == tal_count(flow->dirs)); + struct amount_msat x; + enum askrene_errorcode err; + + err = channel_liquidity(&x, gossmap, chan_extra_map, flow->path[0], + flow->dirs[0]); + if(err){ + if(bad_channel)*bad_channel = flow->path[0]; + return err; + } + x = amount_msat_min(x, channel_htlc_max(flow->path[0], flow->dirs[0])); + + if(amount_msat_zero(x)) + { + if(bad_channel)*bad_channel = flow->path[0]; + return ASKRENE_BAD_CHANNEL; + } + + for (size_t i = 1; i < tal_count(flow->path); ++i) { + // ith node can forward up to 'liquidity_cap' because of the ith + // channel liquidity bound + struct amount_msat liquidity_cap; + + err = channel_liquidity(&liquidity_cap, gossmap, chan_extra_map, + flow->path[i], flow->dirs[i]); + if(err) { + if(bad_channel)*bad_channel = flow->path[i]; + return err; + } + + /* ith node can receive up to 'x', therefore he will not forward + * more than 'forward_cap' that we compute below inverting the + * fee equation. */ + struct amount_msat forward_cap; + err = channel_maximum_forward(&forward_cap, flow->path[i], + flow->dirs[i], x); + if(err) + { + if(bad_channel)*bad_channel = flow->path[i]; + return err; + } + struct amount_msat x_new = + amount_msat_min(forward_cap, liquidity_cap); + x_new = amount_msat_min( + x_new, channel_htlc_max(flow->path[i], flow->dirs[i])); + + /* safety check: amounts decrease along the route */ + assert(amount_msat_less_eq(x_new, x)); + + if(amount_msat_zero(x_new)) + { + if(bad_channel)*bad_channel = flow->path[i]; + return ASKRENE_BAD_CHANNEL; + } + + /* safety check: the max liquidity in the next hop + fees cannot + be greater than the max liquidity in the current hop, IF the + next hop is non-zero. */ + struct amount_msat x_check = x_new; + assert( + amount_msat_add_fee(&x_check, flow_edge(flow, i)->base_fee, + flow_edge(flow, i)->proportional_fee)); + assert(amount_msat_less_eq(x_check, x)); + + x = x_new; + } + assert(!amount_msat_zero(x)); + *max_deliverable = x; + return ASKRENE_NOERROR; +} + +/* Returns the smallest amount we can send so that the destination can get one + * HTLC of any size. It takes into account htlc_min and fees. + * */ +// static enum askrene_errorcode +// flow_minimum_sendable(struct amount_msat *min_sendable UNUSED, +// const struct flow *flow UNUSED, +// const struct gossmap *gossmap UNUSED, +// struct chan_extra_map *chan_extra_map UNUSED) +// { +// // TODO +// return ASKRENE_NOERROR; +// } + +/* How much do we deliver to destination using this set of routes */ +bool flowset_delivers(struct amount_msat *delivers, struct flow **flows) +{ + struct amount_msat final = AMOUNT_MSAT(0); + for (size_t i = 0; i < tal_count(flows); i++) { + if (!amount_msat_add(&final, flows[i]->amount, final)) + return false; + } + *delivers = final; + return true; +} + +/* Checks if the flows satisfy the liquidity bounds imposed by the known maximum + * liquidity and pending HTLCs. + * + * FIXME The function returns false even in the case of failure. The caller has + * no way of knowing the difference between a failure of evaluation and a + * negative answer. */ +// static bool check_liquidity_bounds(struct flow **flows, +// const struct gossmap *gossmap, +// struct chan_extra_map *chan_extra_map) +// { +// bool check = true; +// for (size_t i = 0; i < tal_count(flows); ++i) { +// struct amount_msat max_deliverable; +// if (!flow_maximum_deliverable(&max_deliverable, flows[i], +// gossmap, chan_extra_map)) +// return false; +// struct amount_msat delivers = flow_delivers(flows[i]); +// check &= amount_msat_less_eq(delivers, max_deliverable); +// } +// return check; +// } + +/* Compute the prob. of success of a set of concurrent set of flows. + * + * IMPORTANT: this is not simply the multiplication of the prob. of success of + * all of them, because they're not independent events. A flow that passes + * through a channel c changes that channel's liquidity and then if another flow + * passes through that same channel the previous liquidity change must be taken + * into account. + * + * P(A and B) != P(A) * P(B), + * + * but + * + * P(A and B) = P(A) * P(B | A) + * + * also due to the linear form of P() we have + * + * P(A and B) = P(A + B) + * */ +struct chan_inflight_flow +{ + struct amount_msat half[2]; +}; + +// TODO(eduardo): here chan_extra_map should be const +// TODO(eduardo): here flows should be const +double flowset_probability(const tal_t *ctx, struct flow **flows, + const struct gossmap *const gossmap, + struct chan_extra_map *chan_extra_map, char **fail) +{ + assert(flows); + assert(gossmap); + assert(chan_extra_map); + tal_t *this_ctx = tal(ctx, tal_t); + double prob = 1.0; + + // TODO(eduardo): should it be better to use a map instead of an array + // here? + const size_t max_num_chans = gossmap_max_chan_idx(gossmap); + struct chan_inflight_flow *in_flight = + tal_arr(this_ctx, struct chan_inflight_flow, max_num_chans); + + for (size_t i = 0; i < max_num_chans; ++i) { + in_flight[i].half[0] = in_flight[i].half[1] = AMOUNT_MSAT(0); + } + + for (size_t i = 0; i < tal_count(flows); ++i) { + const struct flow *f = flows[i]; + const size_t pathlen = tal_count(f->path); + struct amount_msat *amounts = tal_flow_amounts(this_ctx, f); + if (!amounts) + { + if (fail) + *fail = tal_fmt( + ctx, + "failed to compute amounts along the path"); + goto function_fail; + } + + for (size_t j = 0; j < pathlen; ++j) { + const struct chan_extra_half *h = + get_chan_extra_half_by_chan(gossmap, chan_extra_map, + f->path[j], f->dirs[j]); + if (!h) { + if (fail) + *fail = tal_fmt( + ctx, + "channel not found in chan_extra_map"); + goto function_fail; + } + const u32 c_idx = gossmap_chan_idx(gossmap, f->path[j]); + const int c_dir = f->dirs[j]; + + const struct amount_msat deliver = amounts[j]; + + struct amount_msat prev_flow; + if (!amount_msat_add(&prev_flow, h->htlc_total, + in_flight[c_idx].half[c_dir])) { + if (fail) + *fail = tal_fmt( + ctx, "in-flight amount_msat overflow"); + goto function_fail; + } + + double edge_prob = + edge_probability(h->known_min, h->known_max, + prev_flow, deliver); + if (edge_prob < 0) { + if (fail) + *fail = tal_fmt(ctx, + "edge_probability failed"); + goto function_fail; + } + prob *= edge_prob; + + if (!amount_msat_add(&in_flight[c_idx].half[c_dir], + in_flight[c_idx].half[c_dir], + deliver)) { + if (fail) + *fail = tal_fmt( + ctx, "in-flight amount_msat overflow"); + goto function_fail; + } + } + } + tal_free(this_ctx); + return prob; + + function_fail: + tal_free(this_ctx); + return -1; +} + +bool flow_spend(struct amount_msat *ret, struct flow *flow) +{ + assert(ret); + assert(flow); + const size_t pathlen = tal_count(flow->path); + struct amount_msat spend = flow->amount; + + for (int i = (int)pathlen - 2; i >= 0; i--) { + const struct half_chan *h = flow_edge(flow, i + 1); + if (!amount_msat_add_fee(&spend, h->base_fee, + h->proportional_fee)) + goto function_fail; + } + + *ret = spend; + return true; + +function_fail: + return false; +} + +bool flow_fee(struct amount_msat *ret, struct flow *flow) +{ + assert(ret); + assert(flow); + struct amount_msat fee; + struct amount_msat spend; + if (!flow_spend(&spend, flow)) + goto function_fail; + if (!amount_msat_sub(&fee, spend, flow->amount)) + goto function_fail; + + *ret = fee; + return true; + +function_fail: + return false; +} + +bool flowset_fee(struct amount_msat *ret, struct flow **flows) +{ + assert(ret); + assert(flows); + struct amount_msat fee = AMOUNT_MSAT(0); + for (size_t i = 0; i < tal_count(flows); i++) { + struct amount_msat this_fee; + if (!flow_fee(&this_fee, flows[i])) + return false; + if (!amount_msat_add(&fee, this_fee, fee)) + return false; + } + *ret = fee; + return true; +} + +/* Helper to access the half chan at flow index idx */ +const struct half_chan *flow_edge(const struct flow *flow, size_t idx) +{ + assert(flow); + assert(idx < tal_count(flow->path)); + return &flow->path[idx]->half[flow->dirs[idx]]; +} + +/* Assign the delivered amount to the flow if it fits + the path maximum capacity. */ +bool flow_assign_delivery(struct flow *flow, const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct amount_msat requested_amount) +{ + struct amount_msat max_deliverable = AMOUNT_MSAT(0); + if (flow_maximum_deliverable(&max_deliverable, flow, gossmap, + chan_extra_map, NULL)) + return false; + assert(!amount_msat_zero(max_deliverable)); + flow->amount = amount_msat_min(requested_amount, max_deliverable); + return true; +} + +/* Helper function to find the success_prob for a single flow + * + * IMPORTANT: flow->success_prob is misleading, because that's the prob. of + * success provided that there are no other flows in the current MPP flow set. + * */ +double flow_probability(struct flow *flow, const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map) +{ + assert(flow); + assert(gossmap); + assert(chan_extra_map); + const size_t pathlen = tal_count(flow->path); + struct amount_msat spend = flow->amount; + double prob = 1.0; + + for (int i = (int)pathlen - 1; i >= 0; i--) { + const struct half_chan *h = flow_edge(flow, i); + const struct chan_extra_half *eh = get_chan_extra_half_by_chan( + gossmap, chan_extra_map, flow->path[i], flow->dirs[i]); + + prob *= edge_probability(eh->known_min, eh->known_max, + eh->htlc_total, spend); + + if (prob < 0) + goto function_fail; + if (!amount_msat_add_fee(&spend, h->base_fee, + h->proportional_fee)) + goto function_fail; + } + + return prob; + +function_fail: + return -1.; +} + +u64 flow_delay(const struct flow *flow) +{ + u64 delay = 0; + for (size_t i = 0; i < tal_count(flow->path); i++) + delay += flow_edge(flow, i)->delay; + return delay; +} + +u64 flows_worst_delay(struct flow **flows) +{ + u64 maxdelay = 0; + for (size_t i = 0; i < tal_count(flows); i++) { + u64 delay = flow_delay(flows[i]); + if (delay > maxdelay) + maxdelay = delay; + } + return maxdelay; +} + +#ifndef SUPERVERBOSE_ENABLED +#undef SUPERVERBOSE +#endif diff --git a/plugins/askrene/flow.h b/plugins/askrene/flow.h new file mode 100644 index 000000000..7c90e327b --- /dev/null +++ b/plugins/askrene/flow.h @@ -0,0 +1,99 @@ +#ifndef LIGHTNING_PLUGINS_ASKRENE_FLOW_H +#define LIGHTNING_PLUGINS_ASKRENE_FLOW_H +#include "config.h" +#include +#include +#include +#include + +/* An actual partial flow. */ +struct flow { + const struct gossmap_chan **path; + /* The directions to traverse. */ + int *dirs; + /* Amounts for this flow (fees mean this shrinks across path). */ + double success_prob; + struct amount_msat amount; +}; + +const char *fmt_flows(const tal_t *ctx, const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct flow **flows); + +/* Helper to access the half chan at flow index idx */ +const struct half_chan *flow_edge(const struct flow *flow, size_t idx); + +/* A big number, meaning "don't bother" (not infinite, since you may add) */ +#define FLOW_INF_COST 100000000.0 + +/* Cost function to send @f msat through @c in direction @dir, + * given we already have a flow of prev_flow. */ +double flow_edge_cost(const struct gossmap *gossmap, + const struct gossmap_chan *c, int dir, + const struct amount_msat known_min, + const struct amount_msat known_max, + struct amount_msat prev_flow, + struct amount_msat f, + double mu, + double basefee_penalty, + double delay_riskfactor); + +/* Compute the prob. of success of a set of concurrent set of flows. */ +double flowset_probability(const tal_t *ctx, struct flow **flows, + const struct gossmap *const gossmap, + struct chan_extra_map *chan_extra_map, char **fail); + +/* How much do we need to send to make this flow arrive. */ +bool flow_spend(struct amount_msat *ret, struct flow *flow); + +/* How much do we pay in fees to make this flow arrive. */ +bool flow_fee(struct amount_msat *ret, struct flow *flow); + +bool flowset_fee(struct amount_msat *fee, struct flow **flows); + +bool flowset_delivers(struct amount_msat *delivers, struct flow **flows); + +static inline struct amount_msat flow_delivers(const struct flow *flow) +{ + return flow->amount; +} + +struct amount_msat *tal_flow_amounts(const tal_t *ctx, const struct flow *flow); + +/* FIXME: remove */ +enum askrene_errorcode { + ASKRENE_NOERROR = 0, + + ASKRENE_AMOUNT_OVERFLOW, + ASKRENE_CHANNEL_NOT_FOUND, + ASKRENE_BAD_CHANNEL, + ASKRENE_BAD_ALLOCATION, + ASKRENE_PRECONDITION_ERROR, + ASKRENE_UNEXPECTED, +}; + +enum askrene_errorcode +flow_maximum_deliverable(struct amount_msat *max_deliverable, + const struct flow *flow, + const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + const struct gossmap_chan **bad_channel); + +/* Assign the delivered amount to the flow if it fits + the path maximum capacity. */ +bool flow_assign_delivery(struct flow *flow, const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct amount_msat requested_amount); + +double flow_probability(struct flow *flow, const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map); + +u64 flow_delay(const struct flow *flow); +u64 flows_worst_delay(struct flow **flows); + +struct flow ** +flows_ensure_liquidity_constraints(const tal_t *ctx, struct flow **flows TAKES, + const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map); + +#endif /* LIGHTNING_PLUGINS_ASKRENE_FLOW_H */ diff --git a/plugins/askrene/mcf.c b/plugins/askrene/mcf.c index da0568a75..1755378ae 100644 --- a/plugins/askrene/mcf.c +++ b/plugins/askrene/mcf.c @@ -8,8 +8,8 @@ #include #include #include -#include -#include +#include +#include #include /* # Optimal payments