/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* "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); }