Files
palladum-lightning/plugins/askrene/askrene.c
Rusty Russell 0ede29b81f askrene: fork before calling the route solver.
This is fairly simple.  We do all the prep work, fire off the child,
and it continues all the way to producing JSON output (or an error).
The parent then forwards it.

Limitations (fixed in successive patches):

1. Child logging currently gets lost.
2. We wait for the child, so this code is not a speedup.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2026-02-19 17:04:35 +10:30

1616 lines
48 KiB
C

/* All your payment questions answered!
*
* This powerful oracle combines data from the network, and then
* determines optimal routes.
*
* When you feed it information, these are remembered as "layers", so you
* can ask questions with (or without) certain layers.
*/
#include "config.h"
#include <ccan/array_size/array_size.h>
#include <ccan/json_out/json_out.h>
#include <ccan/noerr/noerr.h>
#include <ccan/read_write_all/read_write_all.h>
#include <ccan/tal/grab_file/grab_file.h>
#include <ccan/tal/str/str.h>
#include <common/clock_time.h>
#include <common/dijkstra.h>
#include <common/gossmap.h>
#include <common/gossmods_listpeerchannels.h>
#include <common/json_param.h>
#include <common/json_stream.h>
#include <common/route.h>
#include <errno.h>
#include <inttypes.h>
#include <math.h>
#include <plugins/askrene/askrene.h>
#include <plugins/askrene/flow.h>
#include <plugins/askrene/layer.h>
#include <plugins/askrene/mcf.h>
#include <plugins/askrene/reserve.h>
#include <sys/wait.h>
#include <unistd.h>
/* "spendable" for a channel assumes a single HTLC: for additional HTLCs,
* the need to pay for fees (if we're the owner) reduces it */
struct per_htlc_cost {
struct short_channel_id_dir scidd;
struct amount_msat per_htlc_cost;
};
static const struct short_channel_id_dir *
per_htlc_cost_key(const struct per_htlc_cost *phc)
{
return &phc->scidd;
}
static inline bool per_htlc_cost_eq_key(const struct per_htlc_cost *phc,
const struct short_channel_id_dir *scidd)
{
return short_channel_id_dir_eq(scidd, &phc->scidd);
}
HTABLE_DEFINE_NODUPS_TYPE(struct per_htlc_cost,
per_htlc_cost_key,
hash_scidd,
per_htlc_cost_eq_key,
additional_cost_htable);
static bool have_layer(const char **layers, const char *name)
{
for (size_t i = 0; i < tal_count(layers); i++) {
if (streq(layers[i], name))
return true;
}
return false;
}
/* A direction, either "in" or "out", internally handled as a boolean. */
static struct command_result *param_direction(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
bool **out_direction)
{
const char *value;
struct command_result *ret =
param_string(cmd, name, buffer, tok, &value);
if (ret)
return ret;
*out_direction = tal(cmd, bool);
if (streq(value, "in"))
**out_direction = false;
else if (streq(value, "out"))
**out_direction = true;
else {
tal_free(value);
return command_fail_badparam(
cmd, name, buffer, tok,
"Expected either in or out values.");
}
tal_free(value);
return NULL;
}
/* Valid, known layers */
static struct command_result *param_layer_names(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
const char ***arr)
{
size_t i;
const jsmntok_t *t;
if (tok->type != JSMN_ARRAY)
return command_fail_badparam(cmd, name, buffer, tok,
"should be an array");
*arr = tal_arr(cmd, const char *, tok->size);
json_for_each_arr(i, t, tok) {
if (t->type != JSMN_STRING)
return command_fail_badparam(cmd, name, buffer, t,
"should be a string");
(*arr)[i] = json_strdup(*arr, buffer, t);
/* Must be a known layer name */
if (streq((*arr)[i], "auto.localchans")
|| streq((*arr)[i], "auto.no_mpp_support")
|| streq((*arr)[i], "auto.sourcefree")
|| streq((*arr)[i], "auto.include_fees"))
continue;
if (!find_layer(get_askrene(cmd->plugin), (*arr)[i])) {
return command_fail_badparam(cmd, name, buffer, t,
"unknown layer");
}
}
return NULL;
}
static struct command_result *param_known_layer(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
struct layer **layer)
{
const char *layername;
struct command_result *ret = param_string(cmd, name, buffer, tok, &layername);
if (ret)
return ret;
*layer = find_layer(get_askrene(cmd->plugin), layername);
tal_free(layername);
if (!*layer)
return command_fail_badparam(cmd, name, buffer, tok, "Unknown layer");
return NULL;
}
static struct command_result *parse_reserve_hop(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
struct reserve_hop *rhop)
{
const char *err;
const char *layername = NULL;
err = json_scan(tmpctx, buffer, tok, "{short_channel_id_dir:%,amount_msat:%,layer?:%}",
JSON_SCAN(json_to_short_channel_id_dir, &rhop->scidd),
JSON_SCAN(json_to_msat, &rhop->amount),
JSON_SCAN_TAL(tmpctx, json_strdup, &layername));
if (err)
return command_fail_badparam(cmd, name, buffer, tok, err);
if (layername) {
rhop->layer = find_layer(get_askrene(cmd->plugin), layername);
if (!rhop->layer)
return command_fail_badparam(cmd, name, buffer, tok, "Unknown layer");
} else
rhop->layer = NULL;
return NULL;
}
static struct command_result *param_reserve_path(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
struct reserve_hop **path)
{
size_t i;
const jsmntok_t *t;
if (tok->type != JSMN_ARRAY)
return command_fail_badparam(cmd, name, buffer, tok, "should be an array");
*path = tal_arr(cmd, struct reserve_hop, tok->size);
json_for_each_arr(i, t, tok) {
struct command_result *ret;
ret = parse_reserve_hop(cmd, name, buffer, t, &(*path)[i]);
if (ret)
return ret;
}
return NULL;
}
static fp16_t *get_capacities(const tal_t *ctx,
struct plugin *plugin, struct gossmap *gossmap)
{
fp16_t *caps;
struct gossmap_chan *c;
caps = tal_arrz(ctx, fp16_t, gossmap_max_chan_idx(gossmap));
for (c = gossmap_first_chan(gossmap);
c;
c = gossmap_next_chan(gossmap, c)) {
struct amount_msat cap;
cap = gossmap_chan_get_capacity(gossmap, c);
/* Pessimistic: round down! */
caps[gossmap_chan_idx(gossmap, c)]
= u64_to_fp16(cap.millisatoshis/1000, false); /* Raw: fp16 */
}
return caps;
}
/* If we're the payer, we don't add delay or fee to our own outgoing
* channels. This wouldn't be right if we looped back through ourselves,
* but we won't. */
/* FIXME: We could cache this until gossmap/layer changes... */
static struct layer *source_free_layer(const tal_t *ctx,
struct askrene *askrene,
const struct node_id *source,
struct gossmap_localmods *localmods)
{
/* We apply existing localmods so we see *all* channels */
struct gossmap *gossmap = askrene->gossmap;
const struct gossmap_node *srcnode;
const struct amount_msat zero_base_fee = AMOUNT_MSAT(0);
const u16 zero_delay = 0;
const u32 zero_prop_fee = 0;
struct layer *layer = new_temp_layer(ctx, askrene, "auto.sourcefree");
/* We apply this so we see any created channels */
gossmap_apply_localmods(gossmap, localmods);
/* If we're not in map, we complain later */
srcnode = gossmap_find_node(gossmap, source);
for (size_t i = 0; srcnode && i < srcnode->num_chans; i++) {
struct short_channel_id_dir scidd;
const struct gossmap_chan *c;
c = gossmap_nth_chan(gossmap, srcnode, i, &scidd.dir);
scidd.scid = gossmap_chan_scid(gossmap, c);
layer_add_update_channel(layer, &scidd,
NULL, NULL, NULL,
&zero_base_fee, &zero_prop_fee,
&zero_delay);
}
gossmap_remove_localmods(gossmap, localmods);
return layer;
}
/* We're going to abuse MCF, and take the largest flow it gives and ram everything
* through it. This is more effective if there's at least a *chance* that can handle
* the full amount.
*
* It's far from perfect, but I have very little sympathy: if you want
* to receive amounts reliably, enable MPP.
*/
static struct layer *remove_small_channel_layer(const tal_t *ctx,
struct askrene *askrene,
struct amount_msat min_amount,
struct gossmap_localmods *localmods)
{
struct layer *layer = new_temp_layer(ctx, askrene, "auto.no_mpp_support");
struct gossmap *gossmap = askrene->gossmap;
struct gossmap_chan *c;
/* We apply this so we see any created channels */
gossmap_apply_localmods(gossmap, localmods);
for (c = gossmap_first_chan(gossmap); c; c = gossmap_next_chan(gossmap, c)) {
struct short_channel_id_dir scidd;
if (amount_msat_greater_eq(gossmap_chan_get_capacity(gossmap, c),
min_amount))
continue;
scidd.scid = gossmap_chan_scid(gossmap, c);
/* Layer will disable this in both directions */
for (scidd.dir = 0; scidd.dir < 2; scidd.dir++) {
const bool enabled = false;
layer_add_update_channel(layer, &scidd, &enabled,
NULL, NULL, NULL, NULL, NULL);
}
}
gossmap_remove_localmods(gossmap, localmods);
return layer;
}
struct amount_msat get_additional_per_htlc_cost(const struct route_query *rq,
const struct short_channel_id_dir *scidd)
{
const struct per_htlc_cost *phc;
phc = additional_cost_htable_get(rq->additional_costs, scidd);
if (phc)
return phc->per_htlc_cost;
else
return AMOUNT_MSAT(0);
}
const char *rq_log(const tal_t *ctx,
const struct route_query *rq,
enum log_level level,
const char *fmt,
...)
{
va_list args;
const char *msg;
va_start(args, fmt);
msg = tal_vfmt(ctx, fmt, args);
va_end(args);
plugin_notify_message(rq->cmd, level, "%s", msg);
/* Notifications already get logged at debug. Otherwise reduce
* severity. */
if (level != LOG_DBG)
plugin_log(rq->plugin,
level == LOG_BROKEN ? level : level - 1,
"%s: %s", rq->cmd->id, msg);
return msg;
}
static const char *fmt_route(const tal_t *ctx,
const struct route *route,
struct amount_msat delivers,
u32 final_cltv)
{
char *str = tal_strdup(ctx, "");
for (size_t i = 0; i < tal_count(route->hops); i++) {
struct short_channel_id_dir scidd;
scidd.scid = route->hops[i].scid;
scidd.dir = route->hops[i].direction;
tal_append_fmt(&str, "%s/%u %s -> ",
fmt_amount_msat(tmpctx, route->hops[i].amount),
route->hops[i].delay,
fmt_short_channel_id_dir(tmpctx, &scidd));
}
tal_append_fmt(&str, "%s/%u",
fmt_amount_msat(tmpctx, delivers), final_cltv);
return str;
}
const char *fmt_flow_full(const tal_t *ctx,
const struct route_query *rq,
const struct flow *flow)
{
struct amount_msat amt = flow->delivers;
char *str = fmt_amount_msat(ctx, flow->delivers);
for (int i = tal_count(flow->path) - 1; i >= 0; i--) {
struct short_channel_id_dir scidd;
struct amount_msat min, max;
scidd.scid = gossmap_chan_scid(rq->gossmap, flow->path[i]);
scidd.dir = flow->dirs[i];
if (!amount_msat_add_fee(&amt,
flow->path[i]->half[scidd.dir].base_fee,
flow->path[i]->half[scidd.dir].proportional_fee))
abort();
get_constraints(rq, flow->path[i], scidd.dir, &min, &max);
tal_append_fmt(&str, " <- %s %s (cap=%s,fee=%u+%u,delay=%u)",
fmt_amount_msat(tmpctx, amt),
fmt_short_channel_id_dir(tmpctx, &scidd),
fmt_amount_msat(tmpctx, max),
flow->path[i]->half[scidd.dir].base_fee,
flow->path[i]->half[scidd.dir].proportional_fee,
flow->path[i]->half[scidd.dir].delay);
}
return str;
}
enum algorithm {
/* Min. Cost Flow by successive shortests paths. */
ALGO_DEFAULT,
/* Algorithm that finds the optimal routing solution constrained to a
* single path. */
ALGO_SINGLE_PATH,
};
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 if (streq(algo_str, "single-path"))
**algo = ALGO_SINGLE_PATH;
else
return command_fail_badparam(cmd, name, buffer, tok,
"unknown algorithm");
return NULL;
}
struct getroutes_info {
struct command *cmd;
struct node_id source, dest;
struct amount_msat amount, maxfee;
u32 finalcltv, maxdelay;
/* algorithm selection, only dev */
enum algorithm dev_algo;
const char **layers;
struct additional_cost_htable *additional_costs;
/* Non-NULL if we are told to use "auto.localchans" */
struct layer *local_layer;
u32 maxparts;
};
static void apply_layers(struct askrene *askrene, struct route_query *rq,
const struct node_id *source,
struct amount_msat amount,
struct gossmap_localmods *localmods,
const char **layers,
const struct layer *local_layer)
{
/* 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]);
if (!l) {
if (streq(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")) {
plugin_log(rq->plugin, LOG_DBG, "Adding auto.no_mpp_support, sorry");
l = remove_small_channel_layer(layers, askrene, amount, localmods);
} else if (streq(layers[i], "auto.include_fees")) {
plugin_log(rq->plugin, LOG_DBG,
"Adding auto.include_fees");
/* This layer takes effect when converting flows
* into routes. */
continue;
} else {
assert(streq(layers[i], "auto.sourcefree"));
plugin_log(rq->plugin, LOG_DBG, "Adding auto.sourcefree");
l = source_free_layer(layers, askrene, source, localmods);
}
}
tal_arr_expand(&rq->layers, l);
/* FIXME: Implement localmods_merge, and cache this in layer? */
layer_add_localmods(l, rq->gossmap, localmods);
/* Clear any entries in capacities array if we
* override them (incl local channels) */
layer_clear_overridden_capacities(l, askrene->gossmap, rq->capacities);
}
}
/* Convert back into routes, with delay and other information fixed */
static struct route **convert_flows_to_routes(const tal_t *ctx,
struct route_query *rq,
u32 finalcltv,
struct flow **flows,
struct amount_msat **amounts,
bool include_fees)
{
struct route **routes;
routes = tal_arr(ctx, struct route *, tal_count(flows));
*amounts = tal_arr(ctx, struct amount_msat, tal_count(flows));
for (size_t i = 0; i < tal_count(flows); i++) {
struct route *r;
struct amount_msat msat;
u32 delay;
routes[i] = r = tal(routes, struct route);
r->success_prob = flow_probability(flows[i], rq);
r->hops = tal_arr(r, struct route_hop, tal_count(flows[i]->path));
msat = flows[i]->delivers;
delay = finalcltv;
if (!include_fees) {
/* Fill in backwards to calc amount and delay */
for (int j = tal_count(flows[i]->path) - 1; j >= 0;
j--) {
struct route_hop *rh = &r->hops[j];
struct gossmap_node *far_end;
const struct half_chan *h =
flow_edge(flows[i], j);
if (!amount_msat_add_fee(&msat, h->base_fee,
h->proportional_fee))
plugin_err(rq->plugin,
"Adding fee to amount");
delay += h->delay;
rh->scid = gossmap_chan_scid(rq->gossmap,
flows[i]->path[j]);
rh->direction = flows[i]->dirs[j];
far_end = gossmap_nth_node(rq->gossmap,
flows[i]->path[j],
!flows[i]->dirs[j]);
gossmap_node_get_id(rq->gossmap, far_end,
&rh->node_id);
rh->amount = msat;
rh->delay = delay;
}
(*amounts)[i] = flows[i]->delivers;
} else {
/* Fill in backwards to calc delay */
for (int j = tal_count(flows[i]->path) - 1; j >= 0;
j--) {
struct route_hop *rh = &r->hops[j];
struct gossmap_node *far_end;
const struct half_chan *h =
flow_edge(flows[i], j);
delay += h->delay;
rh->scid = gossmap_chan_scid(rq->gossmap,
flows[i]->path[j]);
rh->direction = flows[i]->dirs[j];
far_end = gossmap_nth_node(rq->gossmap,
flows[i]->path[j],
!flows[i]->dirs[j]);
gossmap_node_get_id(rq->gossmap, far_end,
&rh->node_id);
rh->delay = delay;
}
/* Compute fees forward */
for (int j = 0; j < tal_count(flows[i]->path); j++) {
struct route_hop *rh = &r->hops[j];
const struct half_chan *h =
flow_edge(flows[i], j);
rh->amount = msat;
msat = amount_msat_sub_fee(msat, h->base_fee,
h->proportional_fee);
}
(*amounts)[i] = msat;
}
rq_log(tmpctx, rq, LOG_INFORM, "Flow %zu/%zu: %s",
i, tal_count(flows),
fmt_route(tmpctx, r, (*amounts)[i], finalcltv));
}
return routes;
}
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);
}
void get_constraints(const struct route_query *rq,
const struct gossmap_chan *chan,
int dir,
struct amount_msat *min,
struct amount_msat *max)
{
struct short_channel_id_dir scidd;
size_t idx = gossmap_chan_idx(rq->gossmap, chan);
*min = AMOUNT_MSAT(0);
/* Fast path: no information known, no reserve. */
if (idx < tal_count(rq->capacities) && rq->capacities[idx] != 0) {
*max = amount_msat(fp16_to_u64(rq->capacities[idx]) * 1000);
return;
}
/* Naive implementation! */
scidd.scid = gossmap_chan_scid(rq->gossmap, chan);
scidd.dir = dir;
*max = AMOUNT_MSAT(-1ULL);
/* Look through layers for any constraints (might be dummy
* ones, for created channels!) */
for (size_t i = 0; i < tal_count(rq->layers); i++)
layer_apply_constraints(rq->layers[i], &scidd, min, max);
/* Might be here because it's reserved, but capacity is normal. */
if (amount_msat_eq(*max, AMOUNT_MSAT(-1ULL)))
*max = gossmap_chan_get_capacity(rq->gossmap, chan);
/* Finally, if any is in use, subtract that! */
reserve_sub(rq->reserved, &scidd, rq->layers, min);
reserve_sub(rq->reserved, &scidd, rq->layers, max);
}
/* Returns fd to child */
static int fork_router_child(struct route_query *rq,
enum algorithm algo,
struct timemono deadline,
const struct gossmap_node *srcnode,
const struct gossmap_node *dstnode,
struct amount_msat amount,
struct amount_msat maxfee,
u32 finalcltv, u32 maxdelay,
bool include_fees,
const char *cmd_id,
struct json_filter *cmd_filter,
int *child_pid)
{
int replyfds[2];
double probability;
struct flow **flows;
struct route **routes;
struct amount_msat *amounts;
const char *err, *p;
size_t len;
if (pipe(replyfds) != 0)
return -1;
*child_pid = fork();
if (*child_pid < 0) {
close_noerr(replyfds[0]);
close_noerr(replyfds[1]);
return -1;
}
if (*child_pid != 0) {
close(replyfds[1]);
return replyfds[0];
}
/* We are the child. Run the algo */
close(replyfds[0]);
if (algo == ALGO_SINGLE_PATH) {
err = single_path_routes(rq, rq, deadline, srcnode, dstnode,
amount, maxfee, finalcltv,
maxdelay, &flows, &probability);
} else {
assert(algo == ALGO_DEFAULT);
err = default_routes(rq, rq, deadline, srcnode, dstnode,
amount, maxfee, finalcltv, maxdelay,
&flows, &probability);
}
if (err) {
write_all(replyfds[1], err, strlen(err));
/* Non-zero exit tells parent this is an error string. */
exit(1);
}
/* 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 */
routes = convert_flows_to_routes(rq, rq, finalcltv, flows,
&amounts, include_fees);
assert(tal_count(routes) == tal_count(flows));
assert(tal_count(amounts) == tal_count(flows));
/* output the results */
struct json_stream *js = new_json_stream(tmpctx, NULL, NULL);
json_object_start(js, NULL);
json_add_string(js, "jsonrpc", "2.0");
json_add_id(js, cmd_id);
json_object_start(js, "result");
if (cmd_filter)
json_stream_attach_filter(js, cmd_filter);
json_add_getroutes(js, routes, amounts, probability, finalcltv);
/* Detach filter before it complains about closing object it never saw */
if (cmd_filter) {
err = json_stream_detach_filter(tmpctx, js);
if (err)
json_add_string(js, "warning_parameter_filter", err);
}
/* "result" object */
json_object_end(js);
/* Global object */
json_object_end(js);
json_stream_close(js, NULL);
p = json_out_contents(js->jout, &len);
if (!write_all(replyfds[1], p, len))
abort();
exit(0);
}
static struct command_result *do_getroutes(struct command *cmd,
struct gossmap_localmods *localmods,
struct getroutes_info *info)
{
struct askrene *askrene = get_askrene(cmd->plugin);
struct route_query *rq = tal(cmd, struct route_query);
const struct gossmap_node *me;
bool include_fees;
const char *err, *json;
struct timemono time_start, deadline;
int child_fd, child_pid, child_status;
/* 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 */
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;
rq->maxparts = info->maxparts;
/* We also eliminate any local channels we *know* are dying.
* Most channels get 12 blocks grace in case it's a splice,
* but if it's us, we know about the splice already. */
me = gossmap_find_node(rq->gossmap, &askrene->my_id);
if (me) {
for (size_t i = 0; i < me->num_chans; i++) {
struct short_channel_id_dir scidd;
const struct gossmap_chan *c = gossmap_nth_chan(rq->gossmap,
me, i, NULL);
if (!gossmap_chan_is_dying(rq->gossmap, c))
continue;
scidd.scid = gossmap_chan_scid(rq->gossmap, c);
/* Disable both directions */
for (scidd.dir = 0; scidd.dir < 2; scidd.dir++) {
bool enabled = false;
gossmap_local_updatechan(localmods,
&scidd,
&enabled,
NULL, NULL, NULL, NULL, NULL);
}
}
}
/* apply selected layers to the localmods */
apply_layers(askrene, rq, &info->source, info->amount, localmods,
info->layers, info->local_layer);
/* 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);
/* I want to be able to disable channels while working on this query.
* Layers are for user interaction and cannot be used for this purpose.
*/
rq->disabled_chans =
tal_arrz(rq, bitmap,
2 * BITMAP_NWORDS(gossmap_max_chan_idx(askrene->gossmap)));
/* 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;
}
/* auto.no_mpp_support layer overrides any choice of algorithm. */
if (have_layer(info->layers, "auto.no_mpp_support") &&
info->dev_algo != ALGO_SINGLE_PATH) {
info->dev_algo = ALGO_SINGLE_PATH;
rq_log(tmpctx, rq, LOG_DBG,
"Layer no_mpp_support is active we switch to a "
"single path algorithm.");
}
if (rq->maxparts == 1 &&
info->dev_algo != ALGO_SINGLE_PATH) {
info->dev_algo = ALGO_SINGLE_PATH;
rq_log(tmpctx, rq, LOG_DBG,
"maxparts == 1: switching to a single path algorithm.");
}
include_fees = have_layer(info->layers, "auto.include_fees");
time_start = time_mono();
deadline = timemono_add(time_start,
time_from_sec(askrene->route_seconds));
child_fd = fork_router_child(rq, info->dev_algo,
deadline, srcnode, dstnode, info->amount,
info->maxfee, info->finalcltv, info->maxdelay,
include_fees,
cmd->id, cmd->filter, &child_pid);
if (child_fd == -1) {
err = tal_fmt(tmpctx, "failed to fork: %s", strerror(errno));
goto fail_broken;
}
/* FIXME: Go async! */
json = grab_fd_str(cmd, child_fd);
close(child_fd);
waitpid(child_pid, &child_status, 0);
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",
WEXITSTATUS(child_status) != 0 ? "failed after" : "completed in",
time_to_msec(time_delta));
if (WIFSIGNALED(child_status)) {
err = tal_fmt(tmpctx, "child died with signal %u",
WTERMSIG(child_status));
goto fail_broken;
}
/* This is how it indicates an error message */
if (WEXITSTATUS(child_status) != 0 && json) {
err = json;
goto fail;
}
if (!json) {
err = tal_fmt(tmpctx, "child produced no output (exited %i)?",
WEXITSTATUS(child_status));
goto fail_broken;
}
/* At last we remove the localmods from the gossmap. */
gossmap_remove_localmods(askrene->gossmap, localmods);
/* Child already created this fully formed. We just paste it */
return command_finish_rawstr(cmd, json, strlen(json));
fail_broken:
plugin_log(cmd->plugin, LOG_BROKEN, "%s", err);
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,
const struct node_id *self,
const struct node_id *peer,
const struct short_channel_id_dir *scidd,
struct amount_msat capacity_msat,
struct amount_msat htlcmin,
struct amount_msat htlcmax,
struct amount_msat spendable,
struct amount_msat max_total_htlc,
struct amount_msat fee_base,
u32 fee_proportional,
u16 cltv_delta,
bool enabled,
const char *buf,
const jsmntok_t *chantok,
struct getroutes_info *info)
{
u32 feerate;
const char *opener;
const char *err;
/* We get called twice, once in each direction: only create once. */
if (!layer_find_local_channel(info->local_layer, scidd->scid))
layer_add_local_channel(info->local_layer,
self, peer, scidd->scid, capacity_msat);
layer_add_update_channel(info->local_layer, scidd,
&enabled,
&htlcmin, &htlcmax,
&fee_base, &fee_proportional, &cltv_delta);
/* We also need to know the feerate and opener, so we can calculate per-HTLC cost */
feerate = 0; /* Can be unset on unconfirmed channels */
err = json_scan(tmpctx, buf, chantok,
"{feerate?:{perkw:%},opener:%}",
JSON_SCAN(json_to_u32, &feerate),
JSON_SCAN_TAL(tmpctx, json_strdup, &opener));
if (err) {
plugin_log(info->cmd->plugin, LOG_BROKEN,
"Cannot scan channel for feerate and owner (%s): %.*s",
err, json_tok_full_len(chantok), json_tok_full(buf, chantok));
return;
}
if (feerate != 0 && streq(opener, "local")) {
/* BOLT #3:
* The base fee for a commitment transaction:
* - MUST be calculated to match:
* 1. Start with `weight` = 724 (1124 if `option_anchors` applies).
* 2. For each committed HTLC, if that output is not trimmed as specified in
* [Trimmed Outputs](#trimmed-outputs), add 172 to `weight`.
* 3. Multiply `feerate_per_kw` by `weight`, divide by 1000 (rounding down).
*/
struct per_htlc_cost *phc
= tal(info->additional_costs, struct per_htlc_cost);
phc->scidd = *scidd;
if (!amount_sat_to_msat(&phc->per_htlc_cost,
amount_tx_fee(feerate, 172))) {
/* Can't happen, since feerate is u32... */
abort();
}
plugin_log(info->cmd->plugin, LOG_DBG, "Per-htlc cost for %s = %s (%u x 172)",
fmt_short_channel_id_dir(tmpctx, scidd),
fmt_amount_msat(tmpctx, phc->per_htlc_cost),
feerate);
additional_cost_htable_add(info->additional_costs, phc);
}
/* can't send more than expendable and no more than max_total_htlc */
struct amount_msat max_msat = amount_msat_min(spendable, max_total_htlc);
/* Known capacity on local channels (ts = max) */
layer_add_constraint(info->local_layer, scidd, UINT64_MAX, &max_msat, &max_msat);
}
static struct command_result *
listpeerchannels_done(struct command *cmd,
const char *method UNUSED,
const char *buffer,
const jsmntok_t *toks,
struct getroutes_info *info)
{
struct askrene *askrene = get_askrene(cmd->plugin);
struct gossmap_localmods *localmods;
info->local_layer = new_temp_layer(info, askrene, "auto.localchans");
localmods = gossmods_from_listpeerchannels(cmd,
&askrene->my_id,
buffer, toks,
false,
add_localchan,
info);
return do_getroutes(cmd, localmods, info);
}
static struct command_result *json_getroutes(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
/* BOLT #4:
* ## `max_htlc_cltv` Selection
*
* This ... value is defined as 2016 blocks, based on
* historical value deployed by Lightning implementations.
*/
/* FIXME: Typo in spec for CLTV in descripton! But it breaks our spelling check, so we omit it above */
const u32 maxdelay_allowed = 2016;
const u32 default_maxparts = 100;
struct getroutes_info *info = tal(cmd, struct getroutes_info);
/* param functions require pointers */
struct node_id *source, *dest;
struct amount_msat *amount, *maxfee;
u32 *finalcltv, *maxdelay;
enum algorithm *dev_algo;
u32 *maxparts;
if (!param_check(cmd, buffer, params,
p_req("source", param_node_id, &source),
p_req("destination", param_node_id, &dest),
p_req("amount_msat", param_msat, &amount),
p_req("layers", param_layer_names, &info->layers),
p_req("maxfee_msat", param_msat, &maxfee),
p_req("final_cltv", param_u32, &finalcltv),
p_opt_def("maxdelay", param_u32, &maxdelay,
maxdelay_allowed),
p_opt_def("maxparts", param_u32, &maxparts,
default_maxparts),
p_opt_dev("dev_algorithm", param_algorithm,
&dev_algo, ALGO_DEFAULT),
NULL))
return command_param_failed();
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
json_tok_full_len(params), json_tok_full(buffer, params));
if (amount_msat_is_zero(*amount)) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"amount must be non-zero");
}
if (maxparts == 0) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"maxparts must be non-zero");
}
if (*maxdelay > maxdelay_allowed) {
return command_fail(cmd, PAY_USER_ERROR,
"maximum delay allowed is %d",
maxdelay_allowed);
}
if (command_check_only(cmd))
return command_check_done(cmd);
info->cmd = cmd;
info->source = *source;
info->dest = *dest;
info->amount = *amount;
info->maxfee = *maxfee;
info->finalcltv = *finalcltv;
info->maxdelay = *maxdelay;
info->dev_algo = *dev_algo;
info->additional_costs = tal(info, struct additional_cost_htable);
additional_cost_htable_init(info->additional_costs);
info->maxparts = *maxparts;
if (have_layer(info->layers, "auto.localchans")) {
struct out_req *req;
req = jsonrpc_request_start(cmd,
"listpeerchannels",
listpeerchannels_done,
forward_error, info);
return send_outreq(req);
} else
info->local_layer = NULL;
return do_getroutes(cmd, gossmap_localmods_new(cmd), info);
}
static struct command_result *json_askrene_reserve(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct reserve_hop *path;
struct json_stream *response;
struct askrene *askrene = get_askrene(cmd->plugin);
if (!param(cmd, buffer, params,
p_req("path", param_reserve_path, &path),
NULL))
return command_param_failed();
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
json_tok_full_len(params), json_tok_full(buffer, params));
for (size_t i = 0; i < tal_count(path); i++)
reserve_add(askrene->reserved, &path[i], cmd->id);
response = jsonrpc_stream_success(cmd);
return command_finished(cmd, response);
}
static struct command_result *json_askrene_unreserve(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct reserve_hop *path;
struct json_stream *response;
struct askrene *askrene = get_askrene(cmd->plugin);
if (!param(cmd, buffer, params,
p_req("path", param_reserve_path, &path),
NULL))
return command_param_failed();
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
json_tok_full_len(params), json_tok_full(buffer, params));
for (size_t i = 0; i < tal_count(path); i++) {
if (!reserve_remove(askrene->reserved, &path[i])) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Unknown reservation for %s%s%s",
fmt_short_channel_id_dir(tmpctx,
&path[i].scidd),
path[i].layer ? " on layer " : "",
path[i].layer ? layer_name(path[i].layer) : "");
}
}
response = jsonrpc_stream_success(cmd);
return command_finished(cmd, response);
}
static struct command_result *json_askrene_listreservations(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct askrene *askrene = get_askrene(cmd->plugin);
struct json_stream *response;
/* FIXME: We could allow layer names here? */
if (!param(cmd, buffer, params,
NULL))
return command_param_failed();
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
json_tok_full_len(params), json_tok_full(buffer, params));
response = jsonrpc_stream_success(cmd);
json_add_reservations(response, askrene->reserved, "reservations", NULL);
return command_finished(cmd, response);
}
static struct command_result *json_askrene_create_channel(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct layer *layer;
struct node_id *src, *dst;
struct short_channel_id *scid;
struct amount_msat *capacity;
struct json_stream *response;
if (!param_check(cmd, buffer, params,
p_req("layer", param_known_layer, &layer),
p_req("source", param_node_id, &src),
p_req("destination", param_node_id, &dst),
p_req("short_channel_id", param_short_channel_id, &scid),
p_req("capacity_msat", param_msat, &capacity),
NULL))
return command_param_failed();
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
json_tok_full_len(params), json_tok_full(buffer, params));
if (layer_find_local_channel(layer, *scid)) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"channel already exists");
}
if (command_check_only(cmd))
return command_check_done(cmd);
layer_add_local_channel(layer, src, dst, *scid, *capacity);
response = jsonrpc_stream_success(cmd);
return command_finished(cmd, response);
}
static struct command_result *json_askrene_update_channel(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct layer *layer;
struct short_channel_id_dir *scidd;
bool *enabled;
struct amount_msat *htlc_min, *htlc_max, *base_fee;
u32 *proportional_fee;
u16 *delay;
struct json_stream *response;
if (!param(cmd, buffer, params,
p_req("layer", param_known_layer, &layer),
p_req("short_channel_id_dir", param_short_channel_id_dir, &scidd),
p_opt("enabled", param_bool, &enabled),
p_opt("htlc_minimum_msat", param_msat, &htlc_min),
p_opt("htlc_maximum_msat", param_msat, &htlc_max),
p_opt("fee_base_msat", param_msat, &base_fee),
p_opt("fee_proportional_millionths", param_u32, &proportional_fee),
p_opt("cltv_expiry_delta", param_u16, &delay),
NULL))
return command_param_failed();
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
json_tok_full_len(params), json_tok_full(buffer, params));
layer_add_update_channel(layer, scidd,
enabled,
htlc_min, htlc_max,
base_fee, proportional_fee, delay);
response = jsonrpc_stream_success(cmd);
return command_finished(cmd, response);
}
enum inform {
INFORM_CONSTRAINED,
INFORM_UNCONSTRAINED,
INFORM_SUCCEEDED,
};
static struct command_result *param_inform(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
enum inform **inform)
{
*inform = tal(cmd, enum inform);
if (json_tok_streq(buffer, tok, "constrained"))
**inform = INFORM_CONSTRAINED;
else if (json_tok_streq(buffer, tok, "unconstrained"))
**inform = INFORM_UNCONSTRAINED;
else if (json_tok_streq(buffer, tok, "succeeded"))
**inform = INFORM_SUCCEEDED;
else
command_fail_badparam(cmd, name, buffer, tok,
"must be constrained/unconstrained/succeeded");
return NULL;
}
static struct command_result *json_askrene_inform_channel(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct askrene *askrene = get_askrene(cmd->plugin);
struct layer *layer;
struct short_channel_id_dir *scidd;
struct json_stream *response;
struct amount_msat *amount;
enum inform *inform;
const struct constraint *c;
if (!param_check(cmd, buffer, params,
p_req("layer", param_known_layer, &layer),
p_req("short_channel_id_dir", param_short_channel_id_dir, &scidd),
p_req("amount_msat", param_msat, &amount),
p_req("inform", param_inform, &inform),
NULL))
return command_param_failed();
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
json_tok_full_len(params), json_tok_full(buffer, params));
switch (*inform) {
case INFORM_CONSTRAINED:
/* It didn't pass, so minimal assumption is that reserve was all used
* then there we were one msat short. */
if (!reserve_accumulate(askrene->reserved, scidd, layer, amount))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Amount overflow with reserves");
if (!amount_msat_deduct(amount, AMOUNT_MSAT(1)))
*amount = AMOUNT_MSAT(0);
if (command_check_only(cmd))
return command_check_done(cmd);
c = layer_add_constraint(layer, scidd, clock_time().ts.tv_sec,
NULL, amount);
goto output;
case INFORM_UNCONSTRAINED:
/* It passed, so the capacity is at least this much (minimal assumption is
* that no reserves were used) */
if (command_check_only(cmd))
return command_check_done(cmd);
c = layer_add_constraint(layer, scidd, clock_time().ts.tv_sec,
amount, NULL);
goto output;
case INFORM_SUCCEEDED:
/* FIXME: We could do something useful here! */
c = NULL;
goto output;
}
abort();
output:
response = jsonrpc_stream_success(cmd);
json_array_start(response, "constraints");
if (c)
json_add_constraint(response, NULL, c, layer);
json_array_end(response);
return command_finished(cmd, response);
}
static struct command_result *param_s8_hundred(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
s8 **v)
{
s64 s64val;
if (!json_to_s64(buffer, tok, &s64val)
|| s64val < -100
|| s64val > 100)
return command_fail_badparam(cmd, name, buffer, tok,
"should be a number between -100 and 100");
*v = tal(cmd, s8);
**v = s64val;
return NULL;
}
static struct command_result *json_askrene_bias_channel(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct layer *layer;
struct short_channel_id_dir *scidd;
struct json_stream *response;
const char *description;
s8 *bias;
const struct bias *b;
bool *relative;
u64 timestamp;
if (!param(cmd, buffer, params,
p_req("layer", param_known_layer, &layer),
p_req("short_channel_id_dir", param_short_channel_id_dir, &scidd),
p_req("bias", param_s8_hundred, &bias),
p_opt("description", param_string, &description),
p_opt_def("relative", param_bool, &relative, false),
NULL))
return command_param_failed();
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
json_tok_full_len(params), json_tok_full(buffer, params));
timestamp = clock_time().ts.tv_sec;
b = layer_set_bias(layer, scidd, description, *bias, *relative,
timestamp);
response = jsonrpc_stream_success(cmd);
json_array_start(response, "biases");
if (b)
json_add_bias(response, NULL, b, layer);
json_array_end(response);
return command_finished(cmd, response);
}
static struct command_result *json_askrene_bias_node(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct layer *layer;
struct node_id *node;
struct json_stream *response;
const char *description;
s8 *bias;
const struct node_bias *b;
bool *relative;
bool *out_dir;
u64 timestamp;
if (!param(cmd, buffer, params,
p_req("layer", param_known_layer, &layer),
p_req("node", param_node_id, &node),
p_req("direction", param_direction, &out_dir),
p_req("bias", param_s8_hundred, &bias),
p_opt("description", param_string, &description),
p_opt_def("relative", param_bool, &relative, false),
NULL))
return command_param_failed();
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
json_tok_full_len(params), json_tok_full(buffer, params));
timestamp = clock_time().ts.tv_sec;
b = layer_set_node_bias(layer, node, description, *bias, *relative,
*out_dir, timestamp);
response = jsonrpc_stream_success(cmd);
json_array_start(response, "node_biases");
if (b)
json_add_node_bias(response, NULL, b, layer);
json_array_end(response);
return command_finished(cmd, response);
}
static struct command_result *json_askrene_disable_node(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct node_id *node;
struct layer *layer;
struct json_stream *response;
if (!param(cmd, buffer, params,
p_req("layer", param_known_layer, &layer),
p_req("node", param_node_id, &node),
NULL))
return command_param_failed();
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
json_tok_full_len(params), json_tok_full(buffer, params));
/* We save this in the layer, because they want us to disable all the channels
* to the node at *use* time (a new channel might be gossiped!). */
layer_add_disabled_node(layer, node);
response = jsonrpc_stream_success(cmd);
return command_finished(cmd, response);
}
static struct command_result *json_askrene_create_layer(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct askrene *askrene = get_askrene(cmd->plugin);
struct layer *layer;
const char *layername;
struct json_stream *response;
bool *persistent;
if (!param_check(cmd, buffer, params,
p_req("layer", param_string, &layername),
p_opt_def("persistent", param_bool, &persistent, false),
NULL))
return command_param_failed();
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
json_tok_full_len(params), json_tok_full(buffer, params));
if (strstarts(layername, "auto."))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Cannot create auto layer");
/* If it's persistent, creation is a noop if it already exists */
layer = find_layer(askrene, layername);
if (layer && !*persistent) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Layer already exists");
}
if (command_check_only(cmd))
return command_check_done(cmd);
if (!layer)
layer = new_layer(askrene, layername, *persistent);
response = jsonrpc_stream_success(cmd);
json_add_layers(response, askrene, "layers", layer);
return command_finished(cmd, response);
}
static struct command_result *json_askrene_remove_layer(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct layer *layer;
struct json_stream *response;
if (!param(cmd, buffer, params,
p_req("layer", param_known_layer, &layer),
NULL))
return command_param_failed();
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
json_tok_full_len(params), json_tok_full(buffer, params));
remove_layer(layer);
response = jsonrpc_stream_success(cmd);
return command_finished(cmd, response);
}
static struct command_result *json_askrene_listlayers(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct askrene *askrene = get_askrene(cmd->plugin);
struct layer *layer;
struct json_stream *response;
if (!param(cmd, buffer, params,
p_opt("layer", param_known_layer, &layer),
NULL))
return command_param_failed();
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
json_tok_full_len(params), json_tok_full(buffer, params));
response = jsonrpc_stream_success(cmd);
json_add_layers(response, askrene, "layers", layer);
return command_finished(cmd, response);
}
static struct command_result *json_askrene_age(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct layer *layer;
struct json_stream *response;
u64 *cutoff;
size_t num_removed;
if (!param(cmd, buffer, params,
p_req("layer", param_known_layer, &layer),
p_req("cutoff", param_u64, &cutoff),
NULL))
return command_param_failed();
plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__,
json_tok_full_len(params), json_tok_full(buffer, params));
num_removed = layer_trim_constraints(layer, *cutoff);
response = jsonrpc_stream_success(cmd);
json_add_string(response, "layer", layer_name(layer));
json_add_u64(response, "num_removed", num_removed);
return command_finished(cmd, response);
}
static const struct plugin_command commands[] = {
{
"getroutes",
json_getroutes,
},
{
"askrene-listreservations",
json_askrene_listreservations,
},
{
"askrene-reserve",
json_askrene_reserve,
},
{
"askrene-unreserve",
json_askrene_unreserve,
},
{
"askrene-disable-node",
json_askrene_disable_node,
},
{
"askrene-create-channel",
json_askrene_create_channel,
},
{
"askrene-update-channel",
json_askrene_update_channel,
},
{
"askrene-inform-channel",
json_askrene_inform_channel,
},
{
"askrene-bias-channel",
json_askrene_bias_channel,
},
{
"askrene-bias-node",
json_askrene_bias_node,
},
{
"askrene-create-layer",
json_askrene_create_layer,
},
{
"askrene-remove-layer",
json_askrene_remove_layer,
},
{
"askrene-listlayers",
json_askrene_listlayers,
},
{
"askrene-age",
json_askrene_age,
},
};
static const char *init(struct command *init_cmd,
const char *buf UNUSED, const jsmntok_t *config UNUSED)
{
struct plugin *plugin = init_cmd->plugin;
struct askrene *askrene = get_askrene(plugin);
askrene->plugin = plugin;
askrene->layers = new_layer_name_hash(askrene);
askrene->reserved = new_reserve_htable(askrene);
askrene->gossmap = gossmap_load(askrene, GOSSIP_STORE_FILENAME,
plugin_gossmap_logcb, plugin);
if (!askrene->gossmap)
plugin_err(plugin, "Could not load gossmap %s: %s",
GOSSIP_STORE_FILENAME, strerror(errno));
askrene->capacities = get_capacities(askrene, askrene->plugin, askrene->gossmap);
rpc_scan(init_cmd, "getinfo", take(json_out_obj(NULL, NULL, NULL)),
"{id:%}", JSON_SCAN(json_to_node_id, &askrene->my_id));
plugin_set_data(plugin, askrene);
load_layers(askrene, init_cmd);
/* Layer needs its own command to write to the datastore */
askrene->layer_cmd = aux_command(init_cmd);
return NULL;
}
int main(int argc, char *argv[])
{
struct askrene *askrene;
setup_locale();
askrene = tal(NULL, struct askrene);
askrene->route_seconds = 10;
plugin_main(argv, init, take(askrene), PLUGIN_RESTARTABLE, true, NULL, commands, ARRAY_SIZE(commands),
NULL, 0, NULL, 0, NULL, 0,
plugin_option_dynamic("askrene-timeout",
"int",
"How many seconds to try before giving up on calculating a route."
" Defaults to 10 seconds",
u32_option, u32_jsonfmt,
&askrene->route_seconds),
NULL);
}