askrene: move fork() entry point into its own file.

Now there's only one file clearly shared by both parent and child.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2026-02-16 17:31:33 +10:30
parent 395261fc30
commit 33e2f0a47b
5 changed files with 311 additions and 271 deletions

View File

@@ -5,6 +5,7 @@ PLUGIN_ASKRENE_PARENT_SRC := \
plugins/askrene/reserve.c \
PLUGIN_ASKRENE_CHILD_SRC := \
plugins/askrene/child/entry.c \
plugins/askrene/child/mcf.c \
plugins/askrene/child/dijkstra.c \
plugins/askrene/child/flow.c \

View File

@@ -8,9 +8,6 @@
*/
#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>
@@ -26,17 +23,12 @@
#include <math.h>
#include <plugins/askrene/askrene.h>
#include <plugins/askrene/child/child_log.h>
#include <plugins/askrene/child/flow.h>
#include <plugins/askrene/child/mcf.h>
#include <plugins/askrene/child/entry.h>
#include <plugins/askrene/layer.h>
#include <plugins/askrene/reserve.h>
#include <sys/wait.h>
#include <unistd.h>
#include <wire/wire_sync.h>
/* Temporary hack */
static bool am_child = false;
/* "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 {
@@ -340,27 +332,6 @@ const char *rq_log(const tal_t *ctx,
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;
}
enum algorithm {
/* Min. Cost Flow by successive shortests paths. */
ALGO_DEFAULT,
@@ -439,133 +410,6 @@ static void apply_layers(struct askrene *askrene, struct route_query *rq,
}
}
/* 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,
@@ -602,111 +446,6 @@ void get_constraints(const struct route_query *rq,
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 *log_fd,
int *child_pid)
{
int replyfds[2], logfds[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;
if (pipe(logfds) != 0) {
close_noerr(replyfds[0]);
close_noerr(replyfds[1]);
return -1;
}
*child_pid = fork();
if (*child_pid < 0) {
close_noerr(replyfds[0]);
close_noerr(replyfds[1]);
close_noerr(logfds[0]);
close_noerr(logfds[1]);
return -1;
}
if (*child_pid != 0) {
close(logfds[1]);
close(replyfds[1]);
*log_fd = logfds[0];
return replyfds[0];
}
/* We are the child. Run the algo */
close(logfds[0]);
close(replyfds[0]);
set_child_log_fd(logfds[1]);
am_child = true;
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 void process_child_logs(struct route_query *rq, int log_fd)
{
u8 *msg;
@@ -841,7 +580,7 @@ static struct command_result *do_getroutes(struct command *cmd,
time_start = time_mono();
deadline = timemono_add(time_start,
time_from_sec(askrene->route_seconds));
child_fd = fork_router_child(rq, info->dev_algo,
child_fd = fork_router_child(rq, info->dev_algo == ALGO_SINGLE_PATH,
deadline, srcnode, dstnode, info->amount,
info->maxfee, info->finalcltv, info->maxdelay,
include_fees,

View File

@@ -12,14 +12,6 @@
struct gossmap_chan;
/* A single route. */
struct route {
/* Actual path to take */
struct route_hop *hops;
/* Probability estimate (0-1) */
double success_prob;
};
/* Grab-bag of "globals" for this plugin */
struct askrene {
struct plugin *plugin;

View File

@@ -0,0 +1,279 @@
#include "config.h"
#include <assert.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/str/str.h>
#include <common/json_stream.h>
#include <common/route.h>
#include <common/utils.h>
#include <plugins/askrene/askrene.h>
#include <plugins/askrene/child/child_log.h>
#include <plugins/askrene/child/entry.h>
#include <plugins/askrene/child/flow.h>
#include <plugins/askrene/child/mcf.h>
#include <unistd.h>
/* Temporary hack */
bool am_child = false;
/* A single route. */
struct route {
/* Actual path to take */
struct route_hop *hops;
/* Probability estimate (0-1) */
double success_prob;
};
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;
}
/* 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;
}
child_log(tmpctx, 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);
}
/* Returns fd to child */
int fork_router_child(struct route_query *rq,
bool single_path,
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 *log_fd,
int *child_pid)
{
int replyfds[2], logfds[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;
if (pipe(logfds) != 0) {
close_noerr(replyfds[0]);
close_noerr(replyfds[1]);
return -1;
}
*child_pid = fork();
if (*child_pid < 0) {
close_noerr(replyfds[0]);
close_noerr(replyfds[1]);
close_noerr(logfds[0]);
close_noerr(logfds[1]);
return -1;
}
if (*child_pid != 0) {
close(logfds[1]);
close(replyfds[1]);
*log_fd = logfds[0];
return replyfds[0];
}
/* We are the child. Run the algo */
close(logfds[0]);
close(replyfds[0]);
set_child_log_fd(logfds[1]);
am_child = true;
if (single_path) {
err = single_path_routes(rq, rq, deadline, srcnode, dstnode,
amount, maxfee, finalcltv,
maxdelay, &flows, &probability);
} else {
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);
child_log(tmpctx, 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);
}

View File

@@ -0,0 +1,29 @@
#ifndef LIGHTNING_PLUGINS_ASKRENE_CHILD_ENTRY_H
#define LIGHTNING_PLUGINS_ASKRENE_CHILD_ENTRY_H
#include "config.h"
#include <ccan/short_types/short_types.h>
#include <ccan/time/time.h>
#include <common/amount.h>
#include <stdbool.h>
struct route_query;
struct gossmap_node;
struct json_filter;
/* Entry point to the child process. */
int fork_router_child(struct route_query *rq,
bool single_path,
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 *log_fd,
int *child_pid);
/* FIXME: Remove this */
extern bool am_child;
#endif /* LIGHTNING_PLUGINS_ASKRENE_CHILD_ENTRY_H */