mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-21 14:24:09 +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/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>
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -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. */
|
||||
|
|
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -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. */
|
||||
|
|
|
@ -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}]])
|
||||
|
|
Loading…
Add table
Reference in a new issue