common: generic routine to turn listpeerchannels into gossmap local updates.

This is more thorough than the minimal one required for getroute(), including the feerates
and cltv deltas.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2023-12-07 06:44:05 +10:30
parent f2fff4de55
commit cdaad1bf2a
4 changed files with 238 additions and 83 deletions

View file

@ -39,6 +39,7 @@ COMMON_SRC_NOGEN := \
common/fp16.c \
common/gossip_store.c \
common/gossmap.c \
common/gossmods_listpeerchannels.c \
common/hash_u5.c \
common/hmac.c \
common/hsm_encryption.c \

View file

@ -0,0 +1,158 @@
#include "config.h"
#include <ccan/err/err.h>
#include <common/gossmap.h>
#include <common/gossmods_listpeerchannels.h>
#include <common/node_id.h>
#include <plugins/libplugin.h>
void gossmod_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 min,
struct amount_msat max,
struct amount_msat fee_base,
u32 fee_proportional,
u32 cltv_delta,
bool enabled,
const char *buf UNUSED,
const jsmntok_t *chantok UNUSED,
void *cbarg UNUSED)
{
/* FIXME: features? */
gossmap_local_addchan(mods, self, peer, &scidd->scid, NULL);
gossmap_local_updatechan(mods, &scidd->scid, min, max,
fee_base.millisatoshis, /* Raw: gossmap */
fee_proportional,
cltv_delta,
enabled,
scidd->dir);
}
struct gossmap_localmods *
gossmods_from_listpeerchannels_(const tal_t *ctx,
const struct node_id *self,
const char *buf,
const jsmntok_t *toks,
void (*cb)(struct gossmap_localmods *mods,
const struct node_id *self,
const struct node_id *peer,
const struct short_channel_id_dir *scidd,
struct amount_msat min,
struct amount_msat max,
struct amount_msat fee_base,
u32 fee_proportional,
u32 cltv_delta,
bool enabled,
const char *buf,
const jsmntok_t *chantok,
void *cbarg),
void *cbarg)
{
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_dir scidd;
struct short_channel_id alias;
bool enabled;
struct node_id dst;
struct amount_msat spendable, receivable, fee_base[NUM_SIDES], htlc_min[NUM_SIDES], htlc_max[NUM_SIDES];
u32 fee_proportional[NUM_SIDES], cltv_delta[NUM_SIDES];
const char *state, *err;
/* scid/direction and alias may not exist. */
scidd.scid.u64 = 0;
alias.u64 = 0;
/* We do this to note if we have no remote update. */
fee_proportional[REMOTE] = -1U;
err = json_scan(tmpctx, buf, channel,
"{short_channel_id?:%,"
"direction?:%,"
"spendable_msat?:%,"
"receivable_msat?:%,"
"peer_connected:%,"
"state:%,"
"peer_id:%,"
"updates?:{"
"local"
":{fee_base_msat:%,"
"fee_proportional_millionths:%,"
"htlc_minimum_msat:%,"
"htlc_maximum_msat:%,"
"cltv_expiry_delta:%},"
"remote?"
":{fee_base_msat:%,"
"fee_proportional_millionths:%,"
"htlc_minimum_msat:%,"
"htlc_maximum_msat:%,"
"cltv_expiry_delta:%}},"
"alias?:{local:%}}",
JSON_SCAN(json_to_short_channel_id, &scidd.scid),
JSON_SCAN(json_to_int, &scidd.dir),
JSON_SCAN(json_to_msat, &spendable),
JSON_SCAN(json_to_msat, &receivable),
JSON_SCAN(json_to_bool, &enabled),
JSON_SCAN_TAL(tmpctx, json_strdup, &state),
JSON_SCAN(json_to_node_id, &dst),
JSON_SCAN(json_to_msat, &fee_base[LOCAL]),
JSON_SCAN(json_to_u32, &fee_proportional[LOCAL]),
JSON_SCAN(json_to_msat, &htlc_min[LOCAL]),
JSON_SCAN(json_to_msat, &htlc_max[LOCAL]),
JSON_SCAN(json_to_u32, &cltv_delta[LOCAL]),
JSON_SCAN(json_to_msat, &fee_base[REMOTE]),
JSON_SCAN(json_to_u32, &fee_proportional[REMOTE]),
JSON_SCAN(json_to_msat, &htlc_min[REMOTE]),
JSON_SCAN(json_to_msat, &htlc_max[REMOTE]),
JSON_SCAN(json_to_u32, &cltv_delta[REMOTE]),
JSON_SCAN(json_to_short_channel_id, &alias));
if (err) {
errx(1, "Bad listpeerchannels.channels %zu: %s",
i, err);
}
/* Use alias if no scid. Note: if alias is set, direction is present */
if (scidd.scid.u64 == 0 && alias.u64 != 0)
scidd.scid = alias;
/* Unusable if no scid (yet) */
if (scidd.scid.u64 == 0)
continue;
/* Disable if in bad state (it's already false if not connected) */
if (!streq(state, "CHANNELD_NORMAL")
&& !streq(state, "CHANNELD_AWAITING_SPLICE"))
enabled = false;
/* Cut htlc max to spendable. */
if (amount_msat_less(spendable, htlc_max[LOCAL]))
htlc_max[LOCAL] = spendable;
/* We add both directions */
cb(mods, self, &dst, &scidd, htlc_min[LOCAL], htlc_max[LOCAL],
fee_base[LOCAL], fee_proportional[LOCAL], cltv_delta[LOCAL],
enabled, buf, channel, cbarg);
/* If we didn't have a remote update, it's not usable yet */
if (fee_proportional[REMOTE] == -1U)
continue;
scidd.dir = !scidd.dir;
/* Cut htlc max to receivable. */
if (amount_msat_less(receivable, htlc_max[REMOTE]))
htlc_max[REMOTE] = receivable;
cb(mods, self, &dst, &scidd, htlc_min[REMOTE], htlc_max[REMOTE],
fee_base[REMOTE], fee_proportional[REMOTE], cltv_delta[REMOTE],
enabled, buf, channel, cbarg);
}
return mods;
}

View file

@ -0,0 +1,74 @@
#ifndef LIGHTNING_COMMON_GOSSMODS_LISTPEERCHANNELS_H
#define LIGHTNING_COMMON_GOSSMODS_LISTPEERCHANNELS_H
#include "config.h"
#include <bitcoin/short_channel_id.h>
#include <ccan/typesafe_cb/typesafe_cb.h>
#include <common/amount.h>
#include <common/json_parse_simple.h>
struct node_id;
/**
* gossmods_from_listpeerchannels: create gossmap_localmods from `listpeerchannels`
* @ctx: context to allocate return from
* @buf: the JSON buffer from listpeerchannels
* @toks: the JSON tokens
* @cb: optional per-channel callback.
* @cbarg: arg for @cb.
*
* This constructs a set of modifications you can apply to your gossmap to include
* local (esp. private) channels. You can also have an optional per-channel callback
* for special effects.
*/
struct gossmap_localmods *gossmods_from_listpeerchannels_(const tal_t *ctx,
const struct node_id *self,
const char *buf,
const jsmntok_t *toks,
void (*cb)(struct gossmap_localmods *mods,
const struct node_id *self_,
const struct node_id *peer,
const struct short_channel_id_dir *scidd,
struct amount_msat min,
struct amount_msat max,
struct amount_msat fee_base,
u32 fee_proportional,
u32 cltv_delta,
bool enabled,
const char *buf_,
const jsmntok_t *chantok,
void *cbarg_),
void *cbarg);
#define gossmods_from_listpeerchannels(ctx, self, buf, toks, cb, cbarg) \
gossmods_from_listpeerchannels_((ctx), (self), (buf), (toks), \
typesafe_cb_preargs(void, void *, (cb), (cbarg), \
struct gossmap_localmods *, \
const struct node_id *, \
const struct node_id *, \
const struct short_channel_id_dir *, \
struct amount_msat, \
struct amount_msat, \
struct amount_msat, \
u32, \
u32, \
bool, \
const char *, \
const jsmntok_t *), \
(cbarg))
/* Callback which simply adds to gossmap. */
void gossmod_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 min,
struct amount_msat max,
struct amount_msat fee_base,
u32 fee_proportional,
u32 cltv_delta,
bool enabled,
const char *buf UNUSED,
const jsmntok_t *chantok UNUSED,
void *cbarg UNUSED);
#endif /* LIGHTNING_COMMON_GOSSMODS_LISTPEERCHANNELS_H */

View file

@ -6,6 +6,7 @@
#include <ccan/tal/str/str.h>
#include <common/dijkstra.h>
#include <common/gossmap.h>
#include <common/gossmods_listpeerchannels.h>
#include <common/json_param.h>
#include <common/json_stream.h>
#include <common/memleak.h>
@ -148,86 +149,6 @@ static struct command_result *try_route(struct command *cmd,
return command_finished(cmd, js);
}
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,
@ -239,11 +160,12 @@ listpeerchannels_getroute_done(struct command *cmd,
struct command_result *res;
/* Get local knowledge */
gossmap = get_gossmap();
mods = gossmods_from_listpeerchannels(tmpctx, cmd->plugin,
gossmap, buf, result);
mods = gossmods_from_listpeerchannels(tmpctx, &local_id,
buf, result,
gossmod_add_localchan, NULL);
/* Overlay local knowledge for dijkstra */
gossmap = get_gossmap();
gossmap_apply_localmods(gossmap, mods);
res = try_route(cmd, gossmap, info);
gossmap_remove_localmods(gossmap, mods);