askrene: copy flow and dijkstra from renepay.

Still don't actually try compiling them.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-08-07 11:19:54 +09:30
parent d109fcb568
commit e4b84f1ffb
5 changed files with 768 additions and 2 deletions

186
plugins/askrene/dijkstra.c Normal file
View file

@ -0,0 +1,186 @@
#define NDEBUG 1
#include "config.h"
#include <plugins/askrene/dijkstra.h>
/* 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;i<max_num_nodes;++i)
{
dijkstra->distance[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;
}

View file

@ -0,0 +1,30 @@
#ifndef LIGHTNING_PLUGINS_ASKRENE_DIJKSTRA_H
#define LIGHTNING_PLUGINS_ASKRENE_DIJKSTRA_H
#include "config.h"
#include <ccan/short_types/short_types.h>
#include <ccan/tal/tal.h>
#include <gheap.h>
/* 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 */

451
plugins/askrene/flow.c Normal file
View file

@ -0,0 +1,451 @@
#include "config.h"
#include <assert.h>
#include <ccan/tal/str/str.h>
#include <ccan/tal/tal.h>
#include <common/fp16.h>
#include <common/overflows.h>
#include <math.h>
#include <plugins/askrene/flow.h>
#include <stdio.h>
#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

99
plugins/askrene/flow.h Normal file
View file

@ -0,0 +1,99 @@
#ifndef LIGHTNING_PLUGINS_ASKRENE_FLOW_H
#define LIGHTNING_PLUGINS_ASKRENE_FLOW_H
#include "config.h"
#include <bitcoin/short_channel_id.h>
#include <ccan/htable/htable_type.h>
#include <common/amount.h>
#include <common/gossmap.h>
/* 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 */

View file

@ -8,8 +8,8 @@
#include <common/utils.h>
#include <math.h>
#include <plugins/askrene/mcf.h>
#include <plugins/renepay/dijkstra.h>
#include <plugins/renepay/flow.h>
#include <plugins/askrene/dijkstra.h>
#include <plugins/askrene/flow.h>
#include <stdint.h>
/* # Optimal payments