mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-22 14:42:40 +01:00
askrene: add "auto.localchans" layer.
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>
This commit is contained in:
parent
b8b8a40e9e
commit
f8b259d5e9
5 changed files with 162 additions and 16 deletions
|
@ -10,6 +10,7 @@
|
||||||
#include <ccan/array_size/array_size.h>
|
#include <ccan/array_size/array_size.h>
|
||||||
#include <ccan/tal/str/str.h>
|
#include <ccan/tal/str/str.h>
|
||||||
#include <common/gossmap.h>
|
#include <common/gossmap.h>
|
||||||
|
#include <common/gossmods_listpeerchannels.h>
|
||||||
#include <common/json_param.h>
|
#include <common/json_param.h>
|
||||||
#include <common/json_stream.h>
|
#include <common/json_stream.h>
|
||||||
#include <common/route.h>
|
#include <common/route.h>
|
||||||
|
@ -185,7 +186,8 @@ static void add_free_source(struct plugin *plugin,
|
||||||
{
|
{
|
||||||
const struct gossmap_node *srcnode;
|
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);
|
srcnode = gossmap_find_node(gossmap, source);
|
||||||
if (!srcnode)
|
if (!srcnode)
|
||||||
return;
|
return;
|
||||||
|
@ -221,6 +223,7 @@ static const char *get_routes(const tal_t *ctx,
|
||||||
u32 finalcltv,
|
u32 finalcltv,
|
||||||
const char **layers,
|
const char **layers,
|
||||||
struct gossmap_localmods *localmods,
|
struct gossmap_localmods *localmods,
|
||||||
|
const struct layer *local_layer,
|
||||||
struct route ***routes,
|
struct route ***routes,
|
||||||
struct amount_msat **amounts,
|
struct amount_msat **amounts,
|
||||||
double *probability)
|
double *probability)
|
||||||
|
@ -233,6 +236,7 @@ static const char *get_routes(const tal_t *ctx,
|
||||||
double base_fee_penalty;
|
double base_fee_penalty;
|
||||||
u32 prob_cost_factor, mu;
|
u32 prob_cost_factor, mu;
|
||||||
const char *ret;
|
const char *ret;
|
||||||
|
bool zero_cost;
|
||||||
|
|
||||||
if (gossmap_refresh(askrene->gossmap, NULL)) {
|
if (gossmap_refresh(askrene->gossmap, NULL)) {
|
||||||
/* FIXME: gossmap_refresh callbacks to we can update in place */
|
/* FIXME: gossmap_refresh callbacks to we can update in place */
|
||||||
|
@ -246,21 +250,33 @@ static const char *get_routes(const tal_t *ctx,
|
||||||
rq->layers = tal_arr(rq, const struct layer *, 0);
|
rq->layers = tal_arr(rq, const struct layer *, 0);
|
||||||
rq->capacities = tal_dup_talarr(rq, fp16_t, askrene->capacities);
|
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! */
|
/* Layers don't have to exist: they might be empty! */
|
||||||
for (size_t i = 0; i < tal_count(layers); i++) {
|
for (size_t i = 0; i < tal_count(layers); i++) {
|
||||||
struct layer *l = find_layer(askrene, layers[i]);
|
const struct layer *l = find_layer(askrene, layers[i]);
|
||||||
if (!l)
|
if (!l) {
|
||||||
continue;
|
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);
|
tal_arr_expand(&rq->layers, l);
|
||||||
/* FIXME: Implement localmods_merge, and cache this in layer? */
|
/* 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
|
/* Clear any entries in capacities array if we
|
||||||
* override them (incl local channels) */
|
* override them (incl local channels) */
|
||||||
layer_clear_overridden_capacities(l, askrene->gossmap, rq->capacities);
|
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"))
|
if (have_layer(layers, "auto.sourcefree"))
|
||||||
add_free_source(plugin, askrene->gossmap, localmods, source);
|
add_free_source(plugin, askrene->gossmap, localmods, source);
|
||||||
|
|
||||||
|
@ -460,6 +476,7 @@ struct getroutes_info {
|
||||||
|
|
||||||
static struct command_result *do_getroutes(struct command *cmd,
|
static struct command_result *do_getroutes(struct command *cmd,
|
||||||
struct gossmap_localmods *localmods,
|
struct gossmap_localmods *localmods,
|
||||||
|
const struct layer *local_layer,
|
||||||
const struct getroutes_info *info)
|
const struct getroutes_info *info)
|
||||||
{
|
{
|
||||||
const char *err;
|
const char *err;
|
||||||
|
@ -471,7 +488,7 @@ static struct command_result *do_getroutes(struct command *cmd,
|
||||||
err = get_routes(cmd, cmd->plugin,
|
err = get_routes(cmd, cmd->plugin,
|
||||||
info->source, info->dest,
|
info->source, info->dest,
|
||||||
*info->amount, *info->maxfee, *info->finalcltv,
|
*info->amount, *info->maxfee, *info->finalcltv,
|
||||||
info->layers, localmods,
|
info->layers, localmods, local_layer,
|
||||||
&routes, &amounts, &probability);
|
&routes, &amounts, &probability);
|
||||||
if (err)
|
if (err)
|
||||||
return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "%s", err);
|
return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "%s", err);
|
||||||
|
@ -501,6 +518,55 @@ static struct command_result *do_getroutes(struct command *cmd,
|
||||||
return command_finished(cmd, response);
|
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,
|
static struct command_result *json_getroutes(struct command *cmd,
|
||||||
const char *buffer,
|
const char *buffer,
|
||||||
const jsmntok_t *params)
|
const jsmntok_t *params)
|
||||||
|
@ -517,7 +583,17 @@ static struct command_result *json_getroutes(struct command *cmd,
|
||||||
NULL))
|
NULL))
|
||||||
return command_param_failed();
|
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,
|
static struct command_result *json_askrene_reserve(struct command *cmd,
|
||||||
|
@ -823,6 +899,8 @@ static const char *init(struct plugin *plugin,
|
||||||
plugin_err(plugin, "Could not load gossmap %s: %s",
|
plugin_err(plugin, "Could not load gossmap %s: %s",
|
||||||
GOSSIP_STORE_FILENAME, strerror(errno));
|
GOSSIP_STORE_FILENAME, strerror(errno));
|
||||||
askrene->capacities = get_capacities(askrene, askrene->plugin, askrene->gossmap);
|
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_data(plugin, askrene);
|
||||||
plugin_set_memleak_handler(plugin, askrene_markmem);
|
plugin_set_memleak_handler(plugin, askrene_markmem);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <ccan/list/list.h>
|
#include <ccan/list/list.h>
|
||||||
#include <common/amount.h>
|
#include <common/amount.h>
|
||||||
#include <common/fp16.h>
|
#include <common/fp16.h>
|
||||||
|
#include <common/node_id.h>
|
||||||
|
|
||||||
struct gossmap_chan;
|
struct gossmap_chan;
|
||||||
|
|
||||||
|
@ -26,6 +27,8 @@ struct askrene {
|
||||||
struct reserve_hash *reserved;
|
struct reserve_hash *reserved;
|
||||||
/* Compact cache of gossmap capacities */
|
/* Compact cache of gossmap capacities */
|
||||||
fp16_t *capacities;
|
fp16_t *capacities;
|
||||||
|
/* My own id */
|
||||||
|
struct node_id my_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Information for a single route query. */
|
/* Information for a single route query. */
|
||||||
|
|
|
@ -85,9 +85,9 @@ struct layer {
|
||||||
struct node_id *disabled_nodes;
|
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->name = tal_strdup(l, name);
|
||||||
l->local_channels = tal(l, struct local_channel_hash);
|
l->local_channels = tal(l, struct local_channel_hash);
|
||||||
|
@ -96,6 +96,12 @@ struct layer *new_layer(struct askrene *askrene, const char *name)
|
||||||
constraint_hash_init(l->constraints);
|
constraint_hash_init(l->constraints);
|
||||||
l->disabled_nodes = tal_arr(l, struct node_id, 0);
|
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);
|
list_add(&askrene->layers, &l->list);
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
@ -296,11 +302,12 @@ void layer_add_disabled_node(struct layer *layer, const struct node_id *node)
|
||||||
tal_arr_expand(&layer->disabled_nodes, *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,
|
const struct gossmap *gossmap,
|
||||||
|
bool zero_cost,
|
||||||
struct gossmap_localmods *localmods)
|
struct gossmap_localmods *localmods)
|
||||||
{
|
{
|
||||||
struct local_channel *lc;
|
const struct local_channel *lc;
|
||||||
struct local_channel_hash_iter lcit;
|
struct local_channel_hash_iter lcit;
|
||||||
|
|
||||||
/* First, disable all channels into blocked nodes (local updates
|
/* First, disable all channels into blocked nodes (local updates
|
||||||
|
@ -337,14 +344,20 @@ void layer_add_localmods(struct layer *layer,
|
||||||
gossmap_local_addchan(localmods,
|
gossmap_local_addchan(localmods,
|
||||||
&lc->n1, &lc->n2, lc->scid, NULL);
|
&lc->n1, &lc->n2, lc->scid, NULL);
|
||||||
for (size_t i = 0; i < ARRAY_SIZE(lc->half); i++) {
|
for (size_t i = 0; i < ARRAY_SIZE(lc->half); i++) {
|
||||||
|
u64 base, propfee, delay;
|
||||||
if (!lc->half[i].enabled)
|
if (!lc->half[i].enabled)
|
||||||
continue;
|
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,
|
gossmap_local_updatechan(localmods, lc->scid,
|
||||||
lc->half[i].htlc_min,
|
lc->half[i].htlc_min,
|
||||||
lc->half[i].htlc_max,
|
lc->half[i].htlc_max,
|
||||||
lc->half[i].base_fee.millisatoshis /* Raw: gossmap */,
|
base, propfee, delay,
|
||||||
lc->half[i].proportional_fee,
|
|
||||||
lc->half[i].delay,
|
|
||||||
true,
|
true,
|
||||||
i);
|
i);
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,9 @@ struct layer *find_layer(struct askrene *askrene, const char *name);
|
||||||
/* Create new layer by name. */
|
/* Create new layer by name. */
|
||||||
struct layer *new_layer(struct askrene *askrene, const char *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 */
|
/* Get the name of the layer */
|
||||||
const char *layer_name(const struct layer *layer);
|
const char *layer_name(const struct layer *layer);
|
||||||
|
|
||||||
|
@ -87,9 +90,10 @@ const struct constraint *layer_update_constraint(struct layer *layer,
|
||||||
u64 timestamp,
|
u64 timestamp,
|
||||||
struct amount_msat limit);
|
struct amount_msat limit);
|
||||||
|
|
||||||
/* Add local channels from this layer */
|
/* Add local channels from this layer. zero_cost means set fees and delay to 0. */
|
||||||
void layer_add_localmods(struct layer *layer,
|
void layer_add_localmods(const struct layer *layer,
|
||||||
const struct gossmap *gossmap,
|
const struct gossmap *gossmap,
|
||||||
|
bool zero_cost,
|
||||||
struct gossmap_localmods *localmods);
|
struct gossmap_localmods *localmods);
|
||||||
|
|
||||||
/* Remove constraints older then cutoff: returns num removed. */
|
/* Remove constraints older then cutoff: returns num removed. */
|
||||||
|
|
|
@ -362,3 +362,51 @@ def test_getroutes_auto_sourcefree(node_factory):
|
||||||
layers=[],
|
layers=[],
|
||||||
maxfee_msat=100,
|
maxfee_msat=100,
|
||||||
finalcltv=99)
|
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}]])
|
||||||
|
|
Loading…
Add table
Reference in a new issue