2021-12-04 12:23:56 +01:00
|
|
|
#include "config.h"
|
2021-06-14 23:07:38 +02:00
|
|
|
#include <ccan/array_size/array_size.h>
|
|
|
|
#include <ccan/crypto/siphash24/siphash24.h>
|
2021-06-14 23:07:38 +02:00
|
|
|
#include <ccan/htable/htable_type.h>
|
2021-06-14 23:07:38 +02:00
|
|
|
#include <ccan/json_escape/json_escape.h>
|
2021-06-14 23:07:38 +02:00
|
|
|
#include <ccan/tal/str/str.h>
|
|
|
|
#include <common/dijkstra.h>
|
|
|
|
#include <common/gossmap.h>
|
2022-07-04 05:49:38 +02:00
|
|
|
#include <common/json_param.h>
|
2021-06-14 23:07:38 +02:00
|
|
|
#include <common/json_stream.h>
|
|
|
|
#include <common/memleak.h>
|
|
|
|
#include <common/pseudorand.h>
|
|
|
|
#include <common/route.h>
|
|
|
|
#include <common/type_to_string.h>
|
2021-06-14 23:07:38 +02:00
|
|
|
#include <common/wireaddr.h>
|
2021-06-14 23:07:38 +02:00
|
|
|
#include <errno.h>
|
|
|
|
#include <plugins/libplugin.h>
|
|
|
|
|
|
|
|
/* Access via get_gossmap() */
|
2021-06-14 23:07:39 +02:00
|
|
|
static struct gossmap *global_gossmap;
|
2021-06-14 23:07:38 +02:00
|
|
|
static struct node_id local_id;
|
|
|
|
static struct plugin *plugin;
|
|
|
|
|
|
|
|
/* We load this on demand, since we can start before gossipd. */
|
|
|
|
static struct gossmap *get_gossmap(void)
|
|
|
|
{
|
2021-08-20 06:12:27 +02:00
|
|
|
gossmap_refresh(global_gossmap, NULL);
|
2021-06-14 23:07:39 +02:00
|
|
|
return global_gossmap;
|
2021-06-14 23:07:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool can_carry(const struct gossmap *map,
|
|
|
|
const struct gossmap_chan *c,
|
|
|
|
int dir,
|
|
|
|
struct amount_msat amount,
|
2021-11-04 22:06:01 +01:00
|
|
|
struct route_exclusion **excludes)
|
2021-06-14 23:07:38 +02:00
|
|
|
{
|
|
|
|
struct node_id dstid;
|
|
|
|
|
|
|
|
/* First do generic check */
|
|
|
|
if (!route_can_carry(map, c, dir, amount, NULL)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now check exclusions. Premature optimization: */
|
|
|
|
if (!tal_count(excludes)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
gossmap_node_get_id(map, gossmap_nth_node(map, c, !dir), &dstid);
|
|
|
|
for (size_t i = 0; i < tal_count(excludes); i++) {
|
|
|
|
struct short_channel_id scid;
|
|
|
|
|
|
|
|
switch (excludes[i]->type) {
|
|
|
|
case EXCLUDE_CHANNEL:
|
|
|
|
scid = gossmap_chan_scid(map, c);
|
|
|
|
if (short_channel_id_eq(&excludes[i]->u.chan_id.scid, &scid)
|
|
|
|
&& dir == excludes[i]->u.chan_id.dir)
|
|
|
|
return false;
|
|
|
|
continue;
|
|
|
|
|
|
|
|
case EXCLUDE_NODE:
|
|
|
|
if (node_id_eq(&dstid, &excludes[i]->u.node_id))
|
|
|
|
return false;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* No other cases should be possible! */
|
|
|
|
plugin_err(plugin, "Invalid type %i in exclusion[%zu]",
|
|
|
|
excludes[i]->type, i);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Output a route hop */
|
|
|
|
static void json_add_route_hop(struct json_stream *js,
|
|
|
|
const char *fieldname,
|
|
|
|
const struct route_hop *r)
|
|
|
|
{
|
|
|
|
/* Imitate what getroute/sendpay use */
|
|
|
|
json_object_start(js, fieldname);
|
|
|
|
json_add_node_id(js, "id", &r->node_id);
|
|
|
|
json_add_short_channel_id(js, "channel", &r->scid);
|
|
|
|
json_add_num(js, "direction", r->direction);
|
2023-03-14 06:19:50 +01:00
|
|
|
json_add_amount_msat(js, "amount_msat", r->amount);
|
2021-06-14 23:07:38 +02:00
|
|
|
json_add_num(js, "delay", r->delay);
|
2022-03-08 21:22:22 +01:00
|
|
|
json_add_string(js, "style", "tlv");
|
2021-06-14 23:07:38 +02:00
|
|
|
json_object_end(js);
|
|
|
|
}
|
|
|
|
|
2023-10-02 03:13:19 +02:00
|
|
|
struct getroute_info {
|
2021-06-14 23:07:38 +02:00
|
|
|
struct node_id *destination;
|
|
|
|
struct node_id *source;
|
|
|
|
struct amount_msat *msat;
|
|
|
|
u32 *cltv;
|
|
|
|
/* risk factor 12.345% -> riskfactor_millionths = 12345000 */
|
2023-10-02 03:13:19 +02:00
|
|
|
u64 *riskfactor_millionths;
|
2021-11-04 22:06:01 +01:00
|
|
|
struct route_exclusion **excluded;
|
2021-06-14 23:07:38 +02:00
|
|
|
u32 *max_hops;
|
2023-10-02 03:13:19 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct command_result *try_route(struct command *cmd,
|
|
|
|
struct gossmap *gossmap,
|
|
|
|
struct getroute_info *info)
|
|
|
|
{
|
2021-06-14 23:07:38 +02:00
|
|
|
const struct dijkstra *dij;
|
|
|
|
struct route_hop *route;
|
|
|
|
struct gossmap_node *src, *dst;
|
|
|
|
struct json_stream *js;
|
2023-10-02 03:13:19 +02:00
|
|
|
src = gossmap_find_node(gossmap, info->source);
|
2021-06-14 23:07:38 +02:00
|
|
|
if (!src)
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"%s: unknown source node_id (no public channels?)",
|
2023-10-02 03:13:19 +02:00
|
|
|
type_to_string(tmpctx, struct node_id, info->source));
|
2021-06-14 23:07:38 +02:00
|
|
|
|
2023-10-02 03:13:19 +02:00
|
|
|
dst = gossmap_find_node(gossmap, info->destination);
|
2021-06-14 23:07:38 +02:00
|
|
|
if (!dst)
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"%s: unknown destination node_id (no public channels?)",
|
2023-10-02 03:13:19 +02:00
|
|
|
type_to_string(tmpctx, struct node_id, info->destination));
|
2021-06-14 23:07:38 +02:00
|
|
|
|
2023-10-02 03:13:19 +02:00
|
|
|
dij = dijkstra(tmpctx, gossmap, dst, *info->msat,
|
|
|
|
*info->riskfactor_millionths / 1000000.0,
|
|
|
|
can_carry, route_score_cheaper, info->excluded);
|
|
|
|
route = route_from_dijkstra(dij, gossmap, dij, src,
|
|
|
|
*info->msat, *info->cltv);
|
2021-06-14 23:07:38 +02:00
|
|
|
if (!route)
|
|
|
|
return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "Could not find a route");
|
|
|
|
|
|
|
|
/* If it's too far, fall back to using shortest path. */
|
2023-10-02 03:13:19 +02:00
|
|
|
if (tal_count(route) > *info->max_hops) {
|
|
|
|
plugin_notify_message(cmd, LOG_INFORM, "Cheapest route %zu hops: seeking shorter",
|
2021-06-14 23:07:38 +02:00
|
|
|
tal_count(route));
|
2023-10-02 03:13:19 +02:00
|
|
|
dij = dijkstra(tmpctx, gossmap, dst, *info->msat,
|
|
|
|
*info->riskfactor_millionths / 1000000.0,
|
|
|
|
can_carry, route_score_shorter, info->excluded);
|
|
|
|
route = route_from_dijkstra(dij, gossmap, dij, src, *info->msat, *info->cltv);
|
|
|
|
if (tal_count(route) > *info->max_hops)
|
2021-06-14 23:07:38 +02:00
|
|
|
return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "Shortest route was %zu",
|
|
|
|
tal_count(route));
|
|
|
|
}
|
|
|
|
|
|
|
|
js = jsonrpc_stream_success(cmd);
|
|
|
|
json_array_start(js, "route");
|
|
|
|
for (size_t i = 0; i < tal_count(route); i++) {
|
|
|
|
json_add_route_hop(js, NULL, &route[i]);
|
|
|
|
}
|
|
|
|
json_array_end(js);
|
|
|
|
|
|
|
|
return command_finished(cmd, js);
|
|
|
|
}
|
|
|
|
|
2023-10-02 03:14:19 +02:00
|
|
|
static struct gossmap_localmods *
|
|
|
|
gossmods_from_listpeerchannels(const tal_t *ctx,
|
|
|
|
struct plugin *plugin,
|
|
|
|
struct gossmap *gossmap,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *toks)
|
|
|
|
{
|
|
|
|
struct gossmap_localmods *mods = gossmap_localmods_new(ctx);
|
|
|
|
const jsmntok_t *channels, *channel;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
channels = json_get_member(buf, toks, "channels");
|
|
|
|
json_for_each_arr(i, channel, channels) {
|
|
|
|
struct short_channel_id scid;
|
|
|
|
int dir;
|
|
|
|
bool connected;
|
|
|
|
struct node_id dst;
|
|
|
|
struct amount_msat capacity;
|
|
|
|
const char *state, *err;
|
|
|
|
|
|
|
|
/* scid/direction may not exist. */
|
|
|
|
scid.u64 = 0;
|
|
|
|
capacity = AMOUNT_MSAT(0);
|
|
|
|
err = json_scan(tmpctx, buf, channel,
|
|
|
|
"{short_channel_id?:%,"
|
|
|
|
"direction?:%,"
|
|
|
|
"spendable_msat?:%,"
|
|
|
|
"peer_connected:%,"
|
|
|
|
"state:%,"
|
|
|
|
"peer_id:%}",
|
|
|
|
JSON_SCAN(json_to_short_channel_id, &scid),
|
|
|
|
JSON_SCAN(json_to_int, &dir),
|
|
|
|
JSON_SCAN(json_to_msat, &capacity),
|
|
|
|
JSON_SCAN(json_to_bool, &connected),
|
|
|
|
JSON_SCAN_TAL(tmpctx, json_strdup, &state),
|
|
|
|
JSON_SCAN(json_to_node_id, &dst));
|
|
|
|
if (err) {
|
|
|
|
plugin_err(plugin,
|
|
|
|
"Bad listpeerchannels.channels %zu: %s",
|
|
|
|
i, err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Unusable if no scid (yet) */
|
|
|
|
if (scid.u64 == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Disable if in bad state, or disconnected */
|
|
|
|
if (!streq(state, "CHANNELD_NORMAL")
|
|
|
|
&& !streq(state, "CHANNELD_AWAITING_SPLICE")) {
|
|
|
|
goto disable;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!connected) {
|
|
|
|
goto disable;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: features? */
|
|
|
|
gossmap_local_addchan(mods, &local_id, &dst, &scid, NULL);
|
|
|
|
gossmap_local_updatechan(mods, &scid,
|
|
|
|
AMOUNT_MSAT(0), capacity,
|
|
|
|
/* We don't charge ourselves fees */
|
|
|
|
0, 0, 0,
|
|
|
|
true,
|
|
|
|
dir);
|
|
|
|
continue;
|
|
|
|
|
|
|
|
disable:
|
|
|
|
/* Only apply fake "disabled" if channel exists */
|
|
|
|
if (gossmap_find_chan(gossmap, &scid)) {
|
|
|
|
gossmap_local_updatechan(mods, &scid,
|
|
|
|
AMOUNT_MSAT(0), AMOUNT_MSAT(0),
|
|
|
|
0, 0, 0,
|
|
|
|
false,
|
|
|
|
dir);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return mods;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct command_result *
|
|
|
|
listpeerchannels_getroute_done(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result,
|
|
|
|
struct getroute_info *info)
|
|
|
|
{
|
|
|
|
struct gossmap *gossmap;
|
|
|
|
struct gossmap_localmods *mods;
|
|
|
|
struct command_result *res;
|
|
|
|
|
|
|
|
/* Get local knowledge */
|
|
|
|
gossmap = get_gossmap();
|
|
|
|
mods = gossmods_from_listpeerchannels(tmpctx, cmd->plugin,
|
|
|
|
gossmap, buf, result);
|
|
|
|
|
|
|
|
/* Overlay local knowledge for dijkstra */
|
|
|
|
gossmap_apply_localmods(gossmap, mods);
|
|
|
|
res = try_route(cmd, gossmap, info);
|
|
|
|
gossmap_remove_localmods(gossmap, mods);
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct command_result *listpeerchannels_err(struct command *cmd,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result,
|
|
|
|
struct getroute_info *info)
|
|
|
|
{
|
|
|
|
plugin_err(cmd->plugin,
|
|
|
|
"Bad listpeerchannels: %.*s",
|
|
|
|
json_tok_full_len(result),
|
|
|
|
json_tok_full(buf, result));
|
|
|
|
}
|
|
|
|
|
2023-10-02 03:13:19 +02:00
|
|
|
static struct command_result *json_getroute(struct command *cmd,
|
|
|
|
const char *buffer,
|
|
|
|
const jsmntok_t *params)
|
|
|
|
{
|
|
|
|
struct getroute_info *info = tal(cmd, struct getroute_info);
|
2023-10-02 03:14:19 +02:00
|
|
|
struct out_req *req;
|
2023-10-02 03:13:19 +02:00
|
|
|
u64 *fuzz_ignored;
|
|
|
|
|
|
|
|
if (!param(cmd, buffer, params,
|
|
|
|
p_req("id", param_node_id, &info->destination),
|
|
|
|
p_req("amount_msat|msatoshi", param_msat, &info->msat),
|
|
|
|
p_req("riskfactor", param_millionths, &info->riskfactor_millionths),
|
|
|
|
p_opt_def("cltv", param_number, &info->cltv, 9),
|
|
|
|
p_opt_def("fromid", param_node_id, &info->source, local_id),
|
|
|
|
p_opt("fuzzpercent", param_millionths, &fuzz_ignored),
|
|
|
|
p_opt("exclude", param_route_exclusion_array, &info->excluded),
|
|
|
|
p_opt_def("maxhops", param_number, &info->max_hops, ROUTING_MAX_HOPS),
|
|
|
|
NULL))
|
|
|
|
return command_param_failed();
|
|
|
|
|
2023-10-02 03:14:19 +02:00
|
|
|
/* Add local info */
|
|
|
|
req = jsonrpc_request_start(cmd->plugin, cmd, "listpeerchannels",
|
|
|
|
listpeerchannels_getroute_done,
|
|
|
|
listpeerchannels_err, info);
|
|
|
|
return send_outreq(cmd->plugin, req);
|
2023-10-02 03:13:19 +02:00
|
|
|
}
|
|
|
|
|
2021-06-14 23:07:38 +02:00
|
|
|
HTABLE_DEFINE_TYPE(struct node_id, node_id_keyof, node_id_hash, node_id_eq,
|
|
|
|
node_map);
|
|
|
|
|
2021-06-14 23:07:38 +02:00
|
|
|
/* To avoid multiple fetches, we represent directions as a bitmap
|
|
|
|
* so we can do two at once. */
|
|
|
|
static void json_add_halfchan(struct json_stream *response,
|
|
|
|
struct gossmap *gossmap,
|
2021-06-14 23:07:38 +02:00
|
|
|
const struct node_map *connected,
|
2021-06-14 23:07:38 +02:00
|
|
|
const struct gossmap_chan *c,
|
|
|
|
int dirbits)
|
|
|
|
{
|
|
|
|
struct short_channel_id scid;
|
|
|
|
struct node_id node_id[2];
|
|
|
|
const u8 *chanfeatures;
|
|
|
|
struct amount_sat capacity;
|
2021-06-14 23:07:38 +02:00
|
|
|
bool local_disable;
|
2021-06-14 23:07:38 +02:00
|
|
|
|
|
|
|
/* These are channel (not per-direction) properties */
|
|
|
|
chanfeatures = gossmap_chan_get_features(tmpctx, gossmap, c);
|
|
|
|
scid = gossmap_chan_scid(gossmap, c);
|
|
|
|
for (size_t i = 0; i < 2; i++)
|
|
|
|
gossmap_node_get_id(gossmap, gossmap_nth_node(gossmap, c, i),
|
|
|
|
&node_id[i]);
|
|
|
|
|
|
|
|
/* This can theoretically happen on partial write races. */
|
|
|
|
if (!gossmap_chan_get_capacity(gossmap, c, &capacity))
|
|
|
|
capacity = AMOUNT_SAT(0);
|
|
|
|
|
2021-06-14 23:07:38 +02:00
|
|
|
/* Local channels are not "active" unless peer is connected. */
|
|
|
|
if (node_id_eq(&node_id[0], &local_id))
|
|
|
|
local_disable = !node_map_get(connected, &node_id[1]);
|
|
|
|
else if (node_id_eq(&node_id[1], &local_id))
|
|
|
|
local_disable = !node_map_get(connected, &node_id[0]);
|
|
|
|
else
|
|
|
|
local_disable = false;
|
|
|
|
|
2021-06-14 23:07:38 +02:00
|
|
|
for (int dir = 0; dir < 2; dir++) {
|
|
|
|
u32 timestamp;
|
|
|
|
u8 message_flags, channel_flags;
|
|
|
|
u32 fee_base_msat, fee_proportional_millionths;
|
|
|
|
struct amount_msat htlc_minimum_msat, htlc_maximum_msat;
|
|
|
|
|
|
|
|
if (!((1 << dir) & dirbits))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!gossmap_chan_set(c, dir))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
json_object_start(response, NULL);
|
|
|
|
json_add_node_id(response, "source", &node_id[dir]);
|
|
|
|
json_add_node_id(response, "destination", &node_id[!dir]);
|
|
|
|
json_add_short_channel_id(response, "short_channel_id", &scid);
|
2023-01-30 07:07:03 +01:00
|
|
|
json_add_num(response, "direction", dir);
|
2021-06-14 23:07:38 +02:00
|
|
|
json_add_bool(response, "public", !c->private);
|
|
|
|
|
|
|
|
gossmap_chan_get_update_details(gossmap, c, dir,
|
|
|
|
×tamp,
|
|
|
|
&message_flags,
|
|
|
|
&channel_flags,
|
|
|
|
&fee_base_msat,
|
|
|
|
&fee_proportional_millionths,
|
|
|
|
&htlc_minimum_msat,
|
|
|
|
&htlc_maximum_msat);
|
|
|
|
|
2023-03-14 06:19:50 +01:00
|
|
|
json_add_amount_sat_msat(response, "amount_msat", capacity);
|
2021-06-14 23:07:38 +02:00
|
|
|
json_add_num(response, "message_flags", message_flags);
|
|
|
|
json_add_num(response, "channel_flags", channel_flags);
|
2021-06-14 23:07:38 +02:00
|
|
|
|
|
|
|
json_add_bool(response, "active",
|
|
|
|
c->half[dir].enabled && !local_disable);
|
2021-06-14 23:07:38 +02:00
|
|
|
json_add_num(response, "last_update", timestamp);
|
|
|
|
json_add_num(response, "base_fee_millisatoshi", fee_base_msat);
|
|
|
|
json_add_num(response, "fee_per_millionth",
|
|
|
|
fee_proportional_millionths);
|
|
|
|
json_add_num(response, "delay", c->half[dir].delay);
|
2023-03-14 06:21:50 +01:00
|
|
|
json_add_amount_msat(response, "htlc_minimum_msat",
|
|
|
|
htlc_minimum_msat);
|
|
|
|
json_add_amount_msat(response, "htlc_maximum_msat",
|
|
|
|
htlc_maximum_msat);
|
2021-06-14 23:07:38 +02:00
|
|
|
json_add_hex_talarr(response, "features", chanfeatures);
|
|
|
|
json_object_end(response);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-14 23:07:38 +02:00
|
|
|
struct listchannels_opts {
|
2021-06-14 23:07:38 +02:00
|
|
|
struct node_id *source;
|
2021-06-22 15:41:25 +02:00
|
|
|
struct node_id *destination;
|
2021-06-14 23:07:38 +02:00
|
|
|
struct short_channel_id *scid;
|
2021-06-14 23:07:38 +02:00
|
|
|
};
|
2021-06-14 23:07:38 +02:00
|
|
|
|
2021-06-14 23:07:38 +02:00
|
|
|
/* We record which local channels are valid; we could record which are
|
|
|
|
* invalid, but our testsuite has some weirdness where it has local
|
|
|
|
* channels in the store it knows nothing about. */
|
|
|
|
static struct node_map *local_connected(const tal_t *ctx,
|
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result)
|
|
|
|
{
|
|
|
|
size_t i;
|
2023-01-12 02:22:10 +01:00
|
|
|
const jsmntok_t *channel, *channels = json_get_member(buf, result, "channels");
|
2021-06-14 23:07:38 +02:00
|
|
|
struct node_map *connected = tal(ctx, struct node_map);
|
|
|
|
|
|
|
|
node_map_init(connected);
|
2023-01-02 02:53:24 +01:00
|
|
|
tal_add_destructor(connected, node_map_clear);
|
2021-06-14 23:07:38 +02:00
|
|
|
|
2023-01-12 02:22:10 +01:00
|
|
|
json_for_each_arr(i, channel, channels) {
|
2021-06-14 23:07:38 +02:00
|
|
|
struct node_id id;
|
2023-08-18 07:11:44 +02:00
|
|
|
bool is_connected;
|
|
|
|
const char *err, *state;
|
2021-06-14 23:07:38 +02:00
|
|
|
|
2023-01-12 02:22:10 +01:00
|
|
|
err = json_scan(tmpctx, buf, channel,
|
2023-08-18 07:11:44 +02:00
|
|
|
"{peer_id:%,peer_connected:%,state:%}",
|
2021-06-14 23:07:38 +02:00
|
|
|
JSON_SCAN(json_to_node_id, &id),
|
2023-08-18 07:11:44 +02:00
|
|
|
JSON_SCAN(json_to_bool, &is_connected),
|
|
|
|
JSON_SCAN_TAL(tmpctx, json_strdup, &state));
|
2021-06-14 23:07:38 +02:00
|
|
|
if (err)
|
2023-01-12 02:22:10 +01:00
|
|
|
plugin_err(plugin, "Bad listpeerchannels response (%s): %.*s",
|
2021-06-14 23:07:38 +02:00
|
|
|
err,
|
|
|
|
json_tok_full_len(result),
|
|
|
|
json_tok_full(buf, result));
|
|
|
|
|
|
|
|
if (!is_connected)
|
|
|
|
continue;
|
2021-06-14 23:07:38 +02:00
|
|
|
|
2023-08-18 07:11:44 +02:00
|
|
|
/* Must also have a channel in CHANNELD_NORMAL/splice */
|
|
|
|
if (streq(state, "CHANNELD_NORMAL")
|
|
|
|
|| streq(state, "CHANNELD_AWAITING_SPLICE")) {
|
2021-06-14 23:07:38 +02:00
|
|
|
node_map_add(connected,
|
|
|
|
tal_dup(connected, struct node_id, &id));
|
2023-08-18 07:11:44 +02:00
|
|
|
}
|
2021-06-14 23:07:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return connected;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We want to combine local knowledge to we know which are actually inactive! */
|
2023-01-12 02:22:10 +01:00
|
|
|
static struct command_result *listpeerchannels_done(struct command *cmd,
|
2021-06-14 23:07:38 +02:00
|
|
|
const char *buf,
|
|
|
|
const jsmntok_t *result,
|
|
|
|
struct listchannels_opts *opts)
|
|
|
|
{
|
|
|
|
struct node_map *connected;
|
|
|
|
struct gossmap_chan *c;
|
|
|
|
struct json_stream *js;
|
|
|
|
struct gossmap *gossmap = get_gossmap();
|
|
|
|
|
|
|
|
connected = local_connected(opts, buf, result);
|
2021-06-14 23:07:38 +02:00
|
|
|
|
|
|
|
js = jsonrpc_stream_success(cmd);
|
|
|
|
json_array_start(js, "channels");
|
2021-06-14 23:07:38 +02:00
|
|
|
if (opts->scid) {
|
|
|
|
c = gossmap_find_chan(gossmap, opts->scid);
|
2021-06-14 23:07:38 +02:00
|
|
|
if (c)
|
2021-06-14 23:07:38 +02:00
|
|
|
json_add_halfchan(js, gossmap, connected, c, 3);
|
|
|
|
} else if (opts->source) {
|
2021-06-14 23:07:38 +02:00
|
|
|
struct gossmap_node *src;
|
|
|
|
|
2021-06-14 23:07:38 +02:00
|
|
|
src = gossmap_find_node(gossmap, opts->source);
|
2021-06-14 23:07:38 +02:00
|
|
|
if (src) {
|
|
|
|
for (size_t i = 0; i < src->num_chans; i++) {
|
|
|
|
int dir;
|
|
|
|
c = gossmap_nth_chan(gossmap, src, i, &dir);
|
2021-06-14 23:07:38 +02:00
|
|
|
json_add_halfchan(js, gossmap, connected,
|
|
|
|
c, 1 << dir);
|
2021-06-14 23:07:38 +02:00
|
|
|
}
|
|
|
|
}
|
2021-06-22 15:41:25 +02:00
|
|
|
} else if (opts->destination) {
|
|
|
|
struct gossmap_node *dst;
|
|
|
|
|
|
|
|
dst = gossmap_find_node(gossmap, opts->destination);
|
|
|
|
if (dst) {
|
|
|
|
for (size_t i = 0; i < dst->num_chans; i++) {
|
|
|
|
int dir;
|
|
|
|
c = gossmap_nth_chan(gossmap, dst, i, &dir);
|
|
|
|
json_add_halfchan(js, gossmap, connected,
|
|
|
|
c, 1 << !dir);
|
|
|
|
}
|
|
|
|
}
|
2021-06-14 23:07:38 +02:00
|
|
|
} else {
|
|
|
|
for (c = gossmap_first_chan(gossmap);
|
|
|
|
c;
|
|
|
|
c = gossmap_next_chan(gossmap, c)) {
|
2021-06-14 23:07:38 +02:00
|
|
|
json_add_halfchan(js, gossmap, connected, c, 3);
|
2021-06-14 23:07:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
json_array_end(js);
|
|
|
|
|
|
|
|
return command_finished(cmd, js);
|
|
|
|
}
|
|
|
|
|
2021-06-14 23:07:38 +02:00
|
|
|
static struct command_result *json_listchannels(struct command *cmd,
|
|
|
|
const char *buffer,
|
|
|
|
const jsmntok_t *params)
|
|
|
|
{
|
|
|
|
struct listchannels_opts *opts = tal(cmd, struct listchannels_opts);
|
|
|
|
struct out_req *req;
|
|
|
|
|
|
|
|
if (!param(cmd, buffer, params,
|
|
|
|
p_opt("short_channel_id", param_short_channel_id,
|
|
|
|
&opts->scid),
|
|
|
|
p_opt("source", param_node_id, &opts->source),
|
2021-06-22 15:41:25 +02:00
|
|
|
p_opt("destination", param_node_id, &opts->destination),
|
2021-06-14 23:07:38 +02:00
|
|
|
NULL))
|
|
|
|
return command_param_failed();
|
|
|
|
|
2021-06-22 15:41:25 +02:00
|
|
|
if (!!opts->scid + !!opts->source + !!opts->destination > 1)
|
2021-06-14 23:07:38 +02:00
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
2021-06-22 15:41:25 +02:00
|
|
|
"Can only specify one of "
|
|
|
|
"`short_channel_id`, "
|
|
|
|
"`source` or `destination`");
|
2023-01-12 02:22:10 +01:00
|
|
|
req = jsonrpc_request_start(cmd->plugin, cmd, "listpeerchannels",
|
|
|
|
listpeerchannels_done, forward_error, opts);
|
2021-06-14 23:07:38 +02:00
|
|
|
return send_outreq(cmd->plugin, req);
|
|
|
|
}
|
|
|
|
|
2021-06-14 23:07:38 +02:00
|
|
|
static void json_add_node(struct json_stream *js,
|
|
|
|
const struct gossmap *gossmap,
|
|
|
|
const struct gossmap_node *n)
|
|
|
|
{
|
|
|
|
struct node_id node_id;
|
|
|
|
u8 *nannounce;
|
|
|
|
|
|
|
|
json_object_start(js, NULL);
|
|
|
|
gossmap_node_get_id(gossmap, n, &node_id);
|
|
|
|
json_add_node_id(js, "nodeid", &node_id);
|
|
|
|
nannounce = gossmap_node_get_announce(tmpctx, gossmap, n);
|
|
|
|
if (nannounce) {
|
|
|
|
secp256k1_ecdsa_signature signature;
|
|
|
|
u8 *features;
|
|
|
|
u32 timestamp;
|
|
|
|
u8 rgb_color[3], alias[32];
|
|
|
|
u8 *addresses;
|
|
|
|
struct node_id nid;
|
|
|
|
struct wireaddr *addrs;
|
|
|
|
struct json_escape *esc;
|
2021-06-10 19:54:08 +02:00
|
|
|
struct tlv_node_ann_tlvs *na_tlvs;
|
2021-06-14 23:07:38 +02:00
|
|
|
|
|
|
|
if (!fromwire_node_announcement(nannounce, nannounce,
|
|
|
|
&signature,
|
|
|
|
&features,
|
|
|
|
×tamp,
|
|
|
|
&nid,
|
|
|
|
rgb_color,
|
|
|
|
alias,
|
2021-06-10 19:54:08 +02:00
|
|
|
&addresses,
|
2022-03-23 00:31:14 +01:00
|
|
|
&na_tlvs)) {
|
2021-06-14 23:07:38 +02:00
|
|
|
plugin_log(plugin, LOG_BROKEN,
|
|
|
|
"Cannot parse stored node_announcement"
|
|
|
|
" for %s at %u: %s",
|
|
|
|
type_to_string(tmpctx, struct node_id,
|
|
|
|
&node_id),
|
|
|
|
n->nann_off,
|
|
|
|
tal_hex(tmpctx, nannounce));
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
esc = json_escape(NULL,
|
|
|
|
take(tal_strndup(NULL,
|
|
|
|
(const char *)alias,
|
|
|
|
ARRAY_SIZE(alias))));
|
|
|
|
json_add_escaped_string(js, "alias", take(esc));
|
|
|
|
json_add_hex(js, "color", rgb_color, ARRAY_SIZE(rgb_color));
|
|
|
|
json_add_u64(js, "last_timestamp", timestamp);
|
|
|
|
json_add_hex_talarr(js, "features", features);
|
|
|
|
|
|
|
|
json_array_start(js, "addresses");
|
|
|
|
addrs = fromwire_wireaddr_array(nannounce, addresses);
|
|
|
|
for (size_t i = 0; i < tal_count(addrs); i++)
|
|
|
|
json_add_address(js, NULL, &addrs[i]);
|
|
|
|
json_array_end(js);
|
2021-05-27 01:04:07 +02:00
|
|
|
|
|
|
|
if (na_tlvs->option_will_fund) {
|
|
|
|
json_object_start(js, "option_will_fund");
|
|
|
|
json_add_lease_rates(js, na_tlvs->option_will_fund);
|
2021-07-02 23:19:47 +02:00
|
|
|
/* As a convenience, add a hexstring version
|
|
|
|
* of this info */
|
|
|
|
json_add_string(js, "compact_lease",
|
|
|
|
lease_rates_tohex(tmpctx,
|
|
|
|
na_tlvs->option_will_fund));
|
2021-05-27 01:04:07 +02:00
|
|
|
json_object_end(js);
|
|
|
|
}
|
2021-06-14 23:07:38 +02:00
|
|
|
}
|
|
|
|
out:
|
|
|
|
json_object_end(js);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct command_result *json_listnodes(struct command *cmd,
|
|
|
|
const char *buffer,
|
|
|
|
const jsmntok_t *params)
|
|
|
|
{
|
|
|
|
struct node_id *id;
|
|
|
|
struct json_stream *js;
|
|
|
|
struct gossmap *gossmap;
|
|
|
|
|
|
|
|
if (!param(cmd, buffer, params,
|
|
|
|
p_opt("id", param_node_id, &id),
|
|
|
|
NULL))
|
|
|
|
return command_param_failed();
|
|
|
|
|
|
|
|
gossmap = get_gossmap();
|
|
|
|
js = jsonrpc_stream_success(cmd);
|
|
|
|
json_array_start(js, "nodes");
|
|
|
|
if (id) {
|
|
|
|
struct gossmap_node *n = gossmap_find_node(gossmap, id);
|
|
|
|
if (n)
|
|
|
|
json_add_node(js, gossmap, n);
|
|
|
|
} else {
|
|
|
|
for (struct gossmap_node *n = gossmap_first_node(gossmap);
|
|
|
|
n;
|
|
|
|
n = gossmap_next_node(gossmap, n)) {
|
|
|
|
json_add_node(js, gossmap, n);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
json_array_end(js);
|
|
|
|
|
|
|
|
return command_finished(cmd, js);
|
|
|
|
}
|
|
|
|
|
2021-06-14 23:07:38 +02:00
|
|
|
/* What is capacity of peer attached to chan #n? */
|
2021-11-10 18:39:07 +01:00
|
|
|
static struct amount_msat peer_capacity(const struct gossmap *gossmap,
|
2021-06-14 23:07:38 +02:00
|
|
|
const struct gossmap_node *me,
|
|
|
|
const struct gossmap_node *peer,
|
|
|
|
const struct gossmap_chan *ourchan)
|
|
|
|
{
|
2021-11-10 18:39:07 +01:00
|
|
|
struct amount_msat capacity = AMOUNT_MSAT(0);
|
2021-06-14 23:07:38 +02:00
|
|
|
|
|
|
|
for (size_t i = 0; i < peer->num_chans; i++) {
|
|
|
|
int dir;
|
|
|
|
struct gossmap_chan *c;
|
|
|
|
c = gossmap_nth_chan(gossmap, peer, i, &dir);
|
|
|
|
if (c == ourchan)
|
|
|
|
continue;
|
|
|
|
if (!c->half[!dir].enabled)
|
|
|
|
continue;
|
2022-04-27 18:35:21 +02:00
|
|
|
if (!amount_msat_add(
|
|
|
|
&capacity, capacity,
|
|
|
|
amount_msat(fp16_to_u64(c->half[!dir].htlc_max))))
|
2021-06-14 23:07:38 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
return capacity;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct command_result *json_listincoming(struct command *cmd,
|
|
|
|
const char *buffer,
|
|
|
|
const jsmntok_t *params)
|
|
|
|
{
|
|
|
|
struct json_stream *js;
|
|
|
|
struct gossmap_node *me;
|
|
|
|
struct gossmap *gossmap;
|
|
|
|
|
|
|
|
if (!param(cmd, buffer, params, NULL))
|
|
|
|
return command_param_failed();
|
|
|
|
|
|
|
|
gossmap = get_gossmap();
|
|
|
|
|
|
|
|
js = jsonrpc_stream_success(cmd);
|
|
|
|
json_array_start(js, "incoming");
|
|
|
|
me = gossmap_find_node(gossmap, &local_id);
|
|
|
|
if (!me)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < me->num_chans; i++) {
|
|
|
|
struct node_id peer_id;
|
|
|
|
int dir;
|
|
|
|
struct gossmap_chan *ourchan;
|
|
|
|
struct gossmap_node *peer;
|
|
|
|
struct short_channel_id scid;
|
2022-11-09 03:31:59 +01:00
|
|
|
const u8 *peer_features;
|
2021-06-14 23:07:38 +02:00
|
|
|
|
|
|
|
ourchan = gossmap_nth_chan(gossmap, me, i, &dir);
|
2023-08-13 05:11:12 +02:00
|
|
|
/* Entirely missing? Ignore. */
|
|
|
|
if (ourchan->cupdate_off[!dir] == 0)
|
2021-06-14 23:07:38 +02:00
|
|
|
continue;
|
2023-08-13 05:11:12 +02:00
|
|
|
/* We used to ignore if the peer said it was disabled,
|
|
|
|
* but we have a report of LND telling us our unannounced
|
|
|
|
* channel is disabled, so we still use them. */
|
2021-06-14 23:07:38 +02:00
|
|
|
peer = gossmap_nth_node(gossmap, ourchan, !dir);
|
|
|
|
scid = gossmap_chan_scid(gossmap, ourchan);
|
|
|
|
|
|
|
|
json_object_start(js, NULL);
|
|
|
|
gossmap_node_get_id(gossmap, peer, &peer_id);
|
|
|
|
json_add_node_id(js, "id", &peer_id);
|
|
|
|
json_add_short_channel_id(js, "short_channel_id", &scid);
|
2023-03-14 06:21:50 +01:00
|
|
|
json_add_amount_msat(js, "fee_base_msat",
|
|
|
|
amount_msat(ourchan->half[!dir].base_fee));
|
|
|
|
json_add_amount_msat(js, "htlc_min_msat",
|
|
|
|
amount_msat(fp16_to_u64(ourchan->half[!dir]
|
|
|
|
.htlc_min)));
|
|
|
|
json_add_amount_msat(js, "htlc_max_msat",
|
|
|
|
amount_msat(fp16_to_u64(ourchan->half[!dir]
|
|
|
|
.htlc_max)));
|
2021-06-14 23:07:38 +02:00
|
|
|
json_add_u32(js, "fee_proportional_millionths",
|
|
|
|
ourchan->half[!dir].proportional_fee);
|
|
|
|
json_add_u32(js, "cltv_expiry_delta", ourchan->half[!dir].delay);
|
2023-03-14 06:21:50 +01:00
|
|
|
json_add_amount_msat(js, "incoming_capacity_msat",
|
|
|
|
peer_capacity(gossmap, me, peer, ourchan));
|
2022-11-09 03:31:59 +01:00
|
|
|
json_add_bool(js, "public", !ourchan->private);
|
|
|
|
peer_features = gossmap_node_get_features(tmpctx, gossmap, peer);
|
|
|
|
if (peer_features)
|
|
|
|
json_add_hex_talarr(js, "peer_features", peer_features);
|
2021-06-14 23:07:38 +02:00
|
|
|
json_object_end(js);
|
|
|
|
}
|
|
|
|
done:
|
|
|
|
json_array_end(js);
|
|
|
|
|
|
|
|
return command_finished(cmd, js);
|
|
|
|
}
|
|
|
|
|
2021-09-07 06:06:06 +02:00
|
|
|
static void memleak_mark(struct plugin *p, struct htable *memtable)
|
|
|
|
{
|
2022-09-16 05:14:39 +02:00
|
|
|
memleak_scan_obj(memtable, global_gossmap);
|
2021-09-07 06:06:06 +02:00
|
|
|
}
|
|
|
|
|
2021-06-14 23:07:38 +02:00
|
|
|
static const char *init(struct plugin *p,
|
|
|
|
const char *buf UNUSED, const jsmntok_t *config UNUSED)
|
|
|
|
{
|
2021-08-20 06:12:27 +02:00
|
|
|
size_t num_cupdates_rejected;
|
|
|
|
|
2021-06-14 23:07:38 +02:00
|
|
|
plugin = p;
|
|
|
|
rpc_scan(p, "getinfo",
|
|
|
|
take(json_out_obj(NULL, NULL, NULL)),
|
|
|
|
"{id:%}", JSON_SCAN(json_to_node_id, &local_id));
|
|
|
|
|
2021-09-07 06:06:06 +02:00
|
|
|
global_gossmap = gossmap_load(NULL,
|
|
|
|
GOSSIP_STORE_FILENAME,
|
|
|
|
&num_cupdates_rejected);
|
2021-06-14 23:07:39 +02:00
|
|
|
if (!global_gossmap)
|
|
|
|
plugin_err(plugin, "Could not load gossmap %s: %s",
|
|
|
|
GOSSIP_STORE_FILENAME, strerror(errno));
|
|
|
|
|
2021-08-20 06:12:27 +02:00
|
|
|
if (num_cupdates_rejected)
|
|
|
|
plugin_log(plugin, LOG_DBG,
|
|
|
|
"gossmap ignored %zu channel updates",
|
|
|
|
num_cupdates_rejected);
|
2023-09-21 07:36:27 +02:00
|
|
|
plugin_set_memleak_handler(p, memleak_mark);
|
2021-06-14 23:07:38 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct plugin_command commands[] = {
|
|
|
|
{
|
|
|
|
"getroute",
|
|
|
|
"channels",
|
|
|
|
"Primitive route command",
|
|
|
|
"Show route to {id} for {msatoshi}, using {riskfactor} and optional {cltv} (default 9). "
|
|
|
|
"If specified search from {fromid} otherwise use this node as source. "
|
2023-10-02 03:12:19 +02:00
|
|
|
"Randomize the route with up to {fuzzpercent} (ignored)). "
|
2021-06-14 23:07:38 +02:00
|
|
|
"{exclude} an array of short-channel-id/direction (e.g. [ '564334x877x1/0', '564195x1292x0/1' ]) "
|
|
|
|
"or node-id from consideration. "
|
|
|
|
"Set the {maxhops} the route can take (default 20).",
|
|
|
|
json_getroute,
|
|
|
|
},
|
2021-06-14 23:07:38 +02:00
|
|
|
{
|
|
|
|
"listchannels",
|
|
|
|
"channels",
|
|
|
|
"List all known channels in the network",
|
2021-06-22 15:41:25 +02:00
|
|
|
"Show channels for {short_channel_id}, {source} or {destination} "
|
|
|
|
"(or all known channels, if not specified)",
|
2021-06-14 23:07:38 +02:00
|
|
|
json_listchannels,
|
|
|
|
},
|
2021-06-14 23:07:38 +02:00
|
|
|
{
|
|
|
|
"listnodes",
|
|
|
|
"network",
|
|
|
|
"List all known nodes in the network",
|
|
|
|
"Show node {id} (or all known nods, if not specified)",
|
|
|
|
json_listnodes,
|
|
|
|
},
|
2021-06-14 23:07:38 +02:00
|
|
|
{
|
|
|
|
"listincoming",
|
|
|
|
"network",
|
|
|
|
"List the channels incoming from our direct peers",
|
|
|
|
"Used by invoice code to select peers for routehints",
|
|
|
|
json_listincoming,
|
|
|
|
},
|
2021-06-14 23:07:38 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
setup_locale();
|
|
|
|
plugin_main(argv, init, PLUGIN_STATIC, true, NULL, commands, ARRAY_SIZE(commands),
|
|
|
|
NULL, 0, NULL, 0, NULL, 0, NULL);
|
|
|
|
}
|