askrene: apply "auto.sourcefree" to channels created in layers, too.

We had a workaround for channels added by "auto.local", but instead we
should make it work properly.

I didn't do this before because we can't manipulate the localmods while
they're applied, but it's simple to do it in two stages.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-09-18 17:09:27 +09:30
parent 06957dc832
commit 1b8a64f5e6
2 changed files with 75 additions and 33 deletions

View file

@ -185,32 +185,50 @@ static void add_free_source(struct plugin *plugin,
struct gossmap_localmods *localmods, struct gossmap_localmods *localmods,
const struct node_id *source) const struct node_id *source)
{ {
/* We apply existing localmods, save up mods we want, then append
* them: it's not safe to modify localmods while they are applied! */
const struct gossmap_node *srcnode; const struct gossmap_node *srcnode;
struct mod {
struct short_channel_id_dir scidd;
fp16_t htlc_min, htlc_max;
bool enabled;
} *mods = tal_arr(tmpctx, struct mod, 0);
/* If we're not in map, we complain later (unless we're purely gossmap_apply_localmods(gossmap, localmods);
* using local channels) */
/* If we're not in map, we complain later */
srcnode = gossmap_find_node(gossmap, source); srcnode = gossmap_find_node(gossmap, source);
if (!srcnode)
return;
for (size_t i = 0; i < srcnode->num_chans; i++) { for (size_t i = 0; srcnode && i < srcnode->num_chans; i++) {
struct gossmap_chan *c; const struct gossmap_chan *c;
int dir; const struct half_chan *h;
struct short_channel_id scid; struct mod mod;
c = gossmap_nth_chan(gossmap, srcnode, i, &dir); c = gossmap_nth_chan(gossmap, srcnode, i, &mod.scidd.dir);
scid = gossmap_chan_scid(gossmap, c); h = &c->half[mod.scidd.dir];
mod.scidd.scid = gossmap_chan_scid(gossmap, c);
mod.htlc_min = h->htlc_min;
mod.htlc_max = h->htlc_max;
mod.enabled = h->enabled;
tal_arr_expand(&mods, mod);
}
gossmap_remove_localmods(gossmap, localmods);
/* Now we can update localmods */
for (size_t i = 0; i < tal_count(mods); i++) {
if (!gossmap_local_updatechan(localmods, if (!gossmap_local_updatechan(localmods,
scid, mods[i].scidd.scid,
/* Keep min and max */ /* Keep min and max */
gossmap_chan_htlc_min(c, dir), /* FIXME: lossy conversion! */
gossmap_chan_htlc_max(c, dir), amount_msat(fp16_to_u64(mods[i].htlc_min)),
amount_msat(fp16_to_u64(mods[i].htlc_max)),
0, 0, 0, 0, 0, 0,
/* Keep enabled flag */ /* Keep enabled flag */
c->half[dir].enabled, mods[i].enabled,
dir)) mods[i].scidd.dir))
plugin_err(plugin, "Could not zero fee on local %s", plugin_err(plugin, "Could not zero fee on %s",
fmt_short_channel_id(tmpctx, scid)); fmt_short_channel_id_dir(tmpctx, &mods[i].scidd));
} }
} }
@ -237,7 +255,6 @@ 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 */
@ -251,11 +268,6 @@ 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++) {
const struct layer *l = find_layer(askrene, layers[i]); const struct layer *l = find_layer(askrene, layers[i]);
@ -269,15 +281,14 @@ static const char *get_routes(const tal_t *ctx,
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, zero_cost, localmods); layer_add_localmods(l, rq->gossmap, false, 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 /* This also looks into localmods, to zero them */
* 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);
@ -552,17 +563,11 @@ listpeerchannels_done(struct command *cmd,
{ {
struct layer *local_layer = new_temp_layer(info, "auto.localchans"); struct layer *local_layer = new_temp_layer(info, "auto.localchans");
struct gossmap_localmods *localmods; 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, localmods = gossmods_from_listpeerchannels(cmd,
&get_askrene(cmd->plugin)->my_id, &get_askrene(cmd->plugin)->my_id,
buffer, toks, buffer, toks,
zero_cost, false,
add_localchan, add_localchan,
local_layer); local_layer);

View file

@ -442,6 +442,43 @@ def test_fees_dont_exceed_constraints(node_factory):
assert amount <= max_msat assert amount <= max_msat
def test_sourcefree_on_mods(node_factory, bitcoind):
"""auto.sourcefree should also apply to layer-created channels"""
gsfile, nodemap = generate_gossip_store([GenChannel(0, 1, forward=GenChannel.Half(propfee=10000)),
GenChannel(0, 2, forward=GenChannel.Half(propfee=10000))])
l1 = node_factory.get_node(gossip_store_file=gsfile.name)
# Add a local channel from 0->l1 (we just needed a nodeid).
l1.rpc.askrene_create_channel('test_layers',
nodemap[0],
l1.info['id'],
'0x3x3',
'1000000sat',
100, '900000sat',
1000, 2000, 18)
routes = l1.rpc.getroutes(source=nodemap[0],
destination=l1.info['id'],
amount_msat=1000000,
layers=['test_layers', 'auto.sourcefree'],
maxfee_msat=100000,
final_cltv=99)['routes']
# Expect no fee.
check_route_as_expected(routes, [[{'short_channel_id': '0x3x3',
'amount_msat': 1000000, 'delay': 99}]])
# Same if we specify layers in the other order!
routes = l1.rpc.getroutes(source=nodemap[0],
destination=l1.info['id'],
amount_msat=1000000,
layers=['auto.sourcefree', 'test_layers'],
maxfee_msat=100000,
final_cltv=99)['routes']
# Expect no fee.
check_route_as_expected(routes, [[{'short_channel_id': '0x3x3',
'amount_msat': 1000000, 'delay': 99}]])
def test_live_spendable(node_factory, bitcoind): def test_live_spendable(node_factory, bitcoind):
"""Test we don't exceed spendable limits on a real network on nodes""" """Test we don't exceed spendable limits on a real network on nodes"""
l1, l2, l3 = node_factory.get_nodes(3) l1, l2, l3 = node_factory.get_nodes(3)