Skip to content

Commit

Permalink
askrene: add "auto.localchans" layer.
Browse files Browse the repository at this point in the history
This populates information on both topology (i.e. unannounced channels) and capacity for the local node using `listpeerchannels`.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
  • Loading branch information
rustyrussell committed Aug 4, 2024
1 parent b514559 commit 4fe57e4
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 16 deletions.
92 changes: 85 additions & 7 deletions plugins/askrene/askrene.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <ccan/array_size/array_size.h>
#include <ccan/tal/str/str.h>
#include <common/gossmap.h>
#include <common/gossmods_listpeerchannels.h>
#include <common/json_param.h>
#include <common/json_stream.h>
#include <common/route.h>
Expand Down Expand Up @@ -185,7 +186,8 @@ static void add_free_source(struct plugin *plugin,
{
const struct gossmap_node *srcnode;

/* If we're not in map, we complain later */
/* If we're not in map, we complain later (unless we're purely
* using local channels) */
srcnode = gossmap_find_node(gossmap, source);
if (!srcnode)
return;
Expand Down Expand Up @@ -221,6 +223,7 @@ static const char *get_routes(const tal_t *ctx,
u32 finalcltv,
const char **layers,
struct gossmap_localmods *localmods,
const struct layer *local_layer,
struct route ***routes,
struct amount_msat **amounts,
double *probability)
Expand All @@ -233,6 +236,7 @@ static const char *get_routes(const tal_t *ctx,
double base_fee_penalty;
u32 prob_cost_factor, mu;
const char *ret;
bool zero_cost;

if (gossmap_refresh(askrene->gossmap, NULL)) {
/* FIXME: gossmap_refresh callbacks to we can update in place */
Expand All @@ -246,21 +250,33 @@ static const char *get_routes(const tal_t *ctx,
rq->layers = tal_arr(rq, const struct layer *, 0);
rq->capacities = tal_dup_talarr(rq, fp16_t, askrene->capacities);

/* If we're told to zerocost local channels, then make sure that's done
* in local mods as well. */
zero_cost = have_layer(layers, "auto.sourcefree")
&& node_id_eq(source, &askrene->my_id);

/* Layers don't have to exist: they might be empty! */
for (size_t i = 0; i < tal_count(layers); i++) {
struct layer *l = find_layer(askrene, layers[i]);
if (!l)
continue;
const struct layer *l = find_layer(askrene, layers[i]);
if (!l) {
if (local_layer && streq(layers[i], "auto.localchans")) {
plugin_log(plugin, LOG_DBG, "Adding auto.localchans");
l = local_layer;
} else
continue;
}

tal_arr_expand(&rq->layers, l);
/* FIXME: Implement localmods_merge, and cache this in layer? */
layer_add_localmods(l, rq->gossmap, localmods);
layer_add_localmods(l, rq->gossmap, zero_cost, localmods);

/* Clear any entries in capacities array if we
* override them (incl local channels) */
layer_clear_overridden_capacities(l, askrene->gossmap, rq->capacities);
}

/* This does not see local mods! If you add local channel in a layer, it won't
* have costs zeroed out here. */
if (have_layer(layers, "auto.sourcefree"))
add_free_source(plugin, askrene->gossmap, localmods, source);

Expand Down Expand Up @@ -460,6 +476,7 @@ struct getroutes_info {

static struct command_result *do_getroutes(struct command *cmd,
struct gossmap_localmods *localmods,
const struct layer *local_layer,
const struct getroutes_info *info)
{
const char *err;
Expand All @@ -471,7 +488,7 @@ static struct command_result *do_getroutes(struct command *cmd,
err = get_routes(cmd, cmd->plugin,
info->source, info->dest,
*info->amount, *info->maxfee, *info->finalcltv,
info->layers, localmods,
info->layers, localmods, local_layer,
&routes, &amounts, &probability);
if (err)
return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "%s", err);
Expand Down Expand Up @@ -501,6 +518,55 @@ static struct command_result *do_getroutes(struct command *cmd,
return command_finished(cmd, response);
}

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 htlcmin,
struct amount_msat htlcmax,
struct amount_msat spendable,
struct amount_msat fee_base,
u32 fee_proportional,
u32 cltv_delta,
bool enabled,
const char *buf UNUSED,
const jsmntok_t *chantok UNUSED,
struct layer *local_layer)
{
gossmod_add_localchan(mods, self, peer, scidd, htlcmin, htlcmax,
spendable, fee_base, fee_proportional, cltv_delta, enabled,
buf, chantok, local_layer);

/* Known capacity on local channels (ts = max) */
layer_update_constraint(local_layer, scidd, CONSTRAINT_MIN, UINT64_MAX, spendable);
layer_update_constraint(local_layer, scidd, CONSTRAINT_MAX, UINT64_MAX, spendable);
}

static struct command_result *
listpeerchannels_done(struct command *cmd,
const char *buffer,
const jsmntok_t *toks,
struct getroutes_info *info)
{
struct layer *local_layer = new_temp_layer(info, "auto.localchans");
struct gossmap_localmods *localmods;
bool zero_cost;

/* If we're told to zerocost local channels, then make sure that's done
* in local mods as well. */
zero_cost = have_layer(info->layers, "auto.sourcefree")
&& node_id_eq(info->source, &get_askrene(cmd->plugin)->my_id);

localmods = gossmods_from_listpeerchannels(cmd,
&get_askrene(cmd->plugin)->my_id,
buffer, toks,
zero_cost,
add_localchan,
local_layer);

return do_getroutes(cmd, localmods, local_layer, info);
}

static struct command_result *json_getroutes(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
Expand All @@ -517,7 +583,17 @@ static struct command_result *json_getroutes(struct command *cmd,
NULL))
return command_param_failed();

return do_getroutes(cmd, gossmap_localmods_new(cmd), info);
if (have_layer(info->layers, "auto.localchans")) {
struct out_req *req;

req = jsonrpc_request_start(cmd->plugin, cmd,
"listpeerchannels",
listpeerchannels_done,
forward_error, info);
return send_outreq(cmd->plugin, req);
}

return do_getroutes(cmd, gossmap_localmods_new(cmd), NULL, info);
}

static struct command_result *json_askrene_reserve(struct command *cmd,
Expand Down Expand Up @@ -823,6 +899,8 @@ static const char *init(struct plugin *plugin,
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(plugin, "getinfo", take(json_out_obj(NULL, NULL, NULL)),
"{id:%}", JSON_SCAN(json_to_node_id, &askrene->my_id));

plugin_set_data(plugin, askrene);
plugin_set_memleak_handler(plugin, askrene_markmem);
Expand Down
3 changes: 3 additions & 0 deletions plugins/askrene/askrene.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <ccan/list/list.h>
#include <common/amount.h>
#include <common/fp16.h>
#include <common/node_id.h>

struct gossmap_chan;

Expand All @@ -26,6 +27,8 @@ struct askrene {
struct reserve_hash *reserved;
/* Compact cache of gossmap capacities */
fp16_t *capacities;
/* My own id */
struct node_id my_id;
};

/* Information for a single route query. */
Expand Down
27 changes: 20 additions & 7 deletions plugins/askrene/layer.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ struct layer {
struct node_id *disabled_nodes;
};

struct layer *new_layer(struct askrene *askrene, const char *name)
struct layer *new_temp_layer(const tal_t *ctx, const char *name)
{
struct layer *l = tal(askrene, struct layer);
struct layer *l = tal(ctx, struct layer);

l->name = tal_strdup(l, name);
l->local_channels = tal(l, struct local_channel_hash);
Expand All @@ -96,6 +96,12 @@ struct layer *new_layer(struct askrene *askrene, const char *name)
constraint_hash_init(l->constraints);
l->disabled_nodes = tal_arr(l, struct node_id, 0);

return l;
}

struct layer *new_layer(struct askrene *askrene, const char *name)
{
struct layer *l = new_temp_layer(askrene, name);
list_add(&askrene->layers, &l->list);
return l;
}
Expand Down Expand Up @@ -296,11 +302,12 @@ void layer_add_disabled_node(struct layer *layer, const struct node_id *node)
tal_arr_expand(&layer->disabled_nodes, *node);
}

void layer_add_localmods(struct layer *layer,
void layer_add_localmods(const struct layer *layer,
const struct gossmap *gossmap,
bool zero_cost,
struct gossmap_localmods *localmods)
{
struct local_channel *lc;
const struct local_channel *lc;
struct local_channel_hash_iter lcit;

/* First, disable all channels into blocked nodes (local updates
Expand Down Expand Up @@ -337,14 +344,20 @@ void layer_add_localmods(struct layer *layer,
gossmap_local_addchan(localmods,
&lc->n1, &lc->n2, lc->scid, NULL);
for (size_t i = 0; i < ARRAY_SIZE(lc->half); i++) {
u64 base, propfee, delay;
if (!lc->half[i].enabled)
continue;
if (zero_cost) {
base = propfee = delay = 0;
} else {
base = lc->half[i].base_fee.millisatoshis; /* Raw: gossmap */
propfee = lc->half[i].proportional_fee;
delay = lc->half[i].delay;
}
gossmap_local_updatechan(localmods, lc->scid,
lc->half[i].htlc_min,
lc->half[i].htlc_max,
lc->half[i].base_fee.millisatoshis /* Raw: gossmap */,
lc->half[i].proportional_fee,
lc->half[i].delay,
base, propfee, delay,
true,
i);
}
Expand Down
8 changes: 6 additions & 2 deletions plugins/askrene/layer.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ struct layer *find_layer(struct askrene *askrene, const char *name);
/* Create new layer by name. */
struct layer *new_layer(struct askrene *askrene, const char *name);

/* New temporary layer (not in askrene's hash table) */
struct layer *new_temp_layer(const tal_t *ctx, const char *name);

/* Get the name of the layer */
const char *layer_name(const struct layer *layer);

Expand Down Expand Up @@ -87,9 +90,10 @@ const struct constraint *layer_update_constraint(struct layer *layer,
u64 timestamp,
struct amount_msat limit);

/* Add local channels from this layer */
void layer_add_localmods(struct layer *layer,
/* Add local channels from this layer. zero_cost means set fees and delay to 0. */
void layer_add_localmods(const struct layer *layer,
const struct gossmap *gossmap,
bool zero_cost,
struct gossmap_localmods *localmods);

/* Remove constraints older then cutoff: returns num removed. */
Expand Down
48 changes: 48 additions & 0 deletions tests/test_askrene.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,3 +362,51 @@ def test_getroutes_auto_sourcefree(node_factory):
layers=[],
maxfee_msat=100,
finalcltv=99)


def test_getroutes_auto_localchans(node_factory):
"""Test getroutes call with auto.localchans layer"""
# We get bad signature warnings, since our gossip is made up!
l1, l2 = node_factory.get_nodes(2, opts={'allow_warning': True})
gsfile, nodemap = generate_gossip_store([GenChannel(0, 1, forward=GenChannel.Half(propfee=10000)),
GenChannel(1, 2, forward=GenChannel.Half(propfee=10000))],
nodeids=[l2.info['id']])

# Set up l1 with this as the gossip_store
l1.stop()
shutil.copy(gsfile.name, os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store'))
l1.start()

# Now l1 beleives l2 has an entire network behind it.
scid12, _ = l1.fundchannel(l2, 10**6, announce_channel=False)

# Cannot find a route unless we use local hints.
with pytest.raises(RpcError, match="Unknown source node {}".format(l1.info['id'])):
l1.rpc.getroutes(source=l1.info['id'],
destination=nodemap[2],
amount_msat=100000,
layers=[],
maxfee_msat=100000,
finalcltv=99)

# This should work
check_getroute_paths(l1,
l1.info['id'],
nodemap[2],
100000,
maxfee_msat=100000,
layers=['auto.localchans'],
paths=[[{'short_channel_id': scid12, 'amount_msat': 102012, 'delay': 99 + 6 + 6 + 6},
{'short_channel_id': '0x1x0', 'amount_msat': 102010, 'delay': 99 + 6 + 6},
{'short_channel_id': '1x2x1', 'amount_msat': 101000, 'delay': 99 + 6}]])

# This should get self-discount correct
check_getroute_paths(l1,
l1.info['id'],
nodemap[2],
100000,
maxfee_msat=100000,
layers=['auto.localchans', 'auto.sourcefree'],
paths=[[{'short_channel_id': scid12, 'amount_msat': 102010, 'delay': 99 + 6 + 6},
{'short_channel_id': '0x1x0', 'amount_msat': 102010, 'delay': 99 + 6 + 6},
{'short_channel_id': '1x2x1', 'amount_msat': 101000, 'delay': 99 + 6}]])

0 comments on commit 4fe57e4

Please sign in to comment.