gossmap: implement partial updates.

This is actually what we want in several places: to only override one or
two fields in a channel_update.

We add a gossmap_local_setchan() with a similar API to the old
gossmap_local_updatechan(), for the case where we want to set every
field.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-10-04 09:04:53 +09:30
parent bb3663c4a0
commit a65e325b13
11 changed files with 281 additions and 198 deletions

View file

@ -760,24 +760,34 @@ static void destroy_map(struct gossmap *map)
}
/* Local modifications. We only expect a few, so we use a simple
* array. */
* array. If this changes, use a hashtable and a storage area for all
* those pointers to avoid dynamic allocation overhead! */
struct localmod {
struct short_channel_id scid;
/* If this is an entirely-local channel, here's its offset.
* Otherwise, 0xFFFFFFFF. */
u32 local_off;
/* Are updates in either direction set? */
bool updates_set[2];
/* hc[n] defined if updates_set[n]. */
struct half_chan hc[2];
/* orig[n] defined if updates_set[n] and local_off == 0xFFFFFFFF */
/* Non-NULL values mean change existing ones */
struct localmod_changes {
const bool *enabled;
const fp16_t *htlc_min, *htlc_max;
const u32 *base_fee, *proportional_fee;
const u16 *delay;
} changes[2];
/* orig[n] defined if local_off == 0xFFFFFFFF */
struct half_chan orig[2];
/* Original update offsets */
u32 orig_cupdate_off[2];
};
static bool localmod_is_local_chan(const struct localmod *mod)
{
return mod->local_off != 0xFFFFFFFF;
}
struct gossmap_localmods {
struct localmod *mods;
/* This is the local array to be used by the gossmap */
@ -840,7 +850,7 @@ bool gossmap_local_addchan(struct gossmap_localmods *localmods,
return gossmap_local_addchan(localmods, n2, n1, scid, features);
mod.scid = scid;
mod.updates_set[0] = mod.updates_set[1] = false;
memset(&mod.changes, 0, sizeof(mod.changes));
/* We create fake local channel_announcement. */
off = insert_local_space(localmods,
@ -885,53 +895,100 @@ bool gossmap_local_addchan(struct gossmap_localmods *localmods,
return true;
};
/* Insert a local-only channel_update. */
/* Insert a local-only channel_update: false if can't represent. */
bool gossmap_local_updatechan(struct gossmap_localmods *localmods,
struct short_channel_id scid,
struct amount_msat htlc_min,
struct amount_msat htlc_max,
u32 base_fee,
u32 proportional_fee,
u16 delay,
bool enabled,
int dir)
const struct short_channel_id_dir *scidd,
const bool *enabled,
const struct amount_msat *htlc_min,
const struct amount_msat *htlc_max,
const struct amount_msat *base_fee,
const u32 *proportional_fee,
const u16 *delay)
{
struct localmod *mod;
struct localmod_changes *lc;
struct half_chan test;
mod = find_localmod(localmods, scid);
/* Check fit before making any changes. */
if (base_fee) {
test.base_fee = base_fee->millisatoshis /* Raw: localmod */;
if (!amount_msat_eq(amount_msat(test.base_fee), *base_fee))
return false;
}
if (proportional_fee) {
test.proportional_fee = *proportional_fee;
if (test.proportional_fee != *proportional_fee)
return false;
}
if (delay) {
test.delay = *delay;
if (test.delay != *delay)
return false;
}
mod = find_localmod(localmods, scidd->scid);
if (!mod) {
/* Create new reference to (presumably) existing channel. */
size_t nmods = tal_count(localmods->mods);
tal_resize(&localmods->mods, nmods + 1);
mod = &localmods->mods[nmods];
mod->scid = scid;
mod->updates_set[0] = mod->updates_set[1] = false;
mod->scid = scidd->scid;
memset(&mod->changes, 0, sizeof(mod->changes));
mod->local_off = 0xFFFFFFFF;
}
assert(dir == 0 || dir == 1);
mod->updates_set[dir] = true;
mod->hc[dir].enabled = enabled;
/* node_idx needs to be set once we're in the gossmap. */
mod->hc[dir].htlc_min
= u64_to_fp16(htlc_min.millisatoshis, /* Raw: to fp16 */
false);
mod->hc[dir].htlc_max
= u64_to_fp16(htlc_max.millisatoshis, /* Raw: to fp16 */
true);
mod->hc[dir].base_fee = base_fee;
mod->hc[dir].proportional_fee = proportional_fee;
mod->hc[dir].delay = delay;
/* Check they fit */
if (mod->hc[dir].base_fee != base_fee
|| mod->hc[dir].proportional_fee != proportional_fee
|| mod->hc[dir].delay != delay)
return false;
lc = &mod->changes[scidd->dir];
if (enabled) {
tal_free(lc->enabled);
lc->enabled = tal_dup(localmods, bool, enabled);
}
if (htlc_min) {
fp16_t min = u64_to_fp16(htlc_min->millisatoshis, /* Raw: to fp16 */
false);
tal_free(lc->htlc_min);
lc->htlc_min = tal_dup(localmods, fp16_t, &min);
}
if (htlc_max) {
fp16_t max = u64_to_fp16(htlc_max->millisatoshis, /* Raw: to fp16 */
true);
tal_free(lc->htlc_max);
lc->htlc_max = tal_dup(localmods, fp16_t, &max);
}
if (base_fee) {
u32 base_as_u32 = base_fee->millisatoshis; /* Raw: localmod */
tal_free(lc->base_fee);
lc->base_fee = tal_dup(localmods, u32, &base_as_u32);
}
if (proportional_fee) {
tal_free(lc->proportional_fee);
lc->proportional_fee = tal_dup(localmods, u32, proportional_fee);
}
if (delay) {
tal_free(lc->delay);
lc->delay = tal_dup(localmods, u16, delay);
}
return true;
}
bool gossmap_local_setchan(struct gossmap_localmods *localmods,
struct short_channel_id scid,
struct amount_msat htlc_min,
struct amount_msat htlc_max,
struct amount_msat base_fee,
u32 proportional_fee,
u16 delay,
bool enabled,
int dir)
{
struct short_channel_id_dir scidd = {scid, dir};
return gossmap_local_updatechan(localmods, &scidd,
&enabled,
&htlc_min, &htlc_max,
&base_fee, &proportional_fee,
&delay);
}
/* Apply localmods to this map */
void gossmap_apply_localmods(struct gossmap *map,
struct gossmap_localmods *localmods)
@ -949,22 +1006,56 @@ void gossmap_apply_localmods(struct gossmap *map,
chan = gossmap_find_chan(map, &mod->scid);
/* If it doesn't exist, are we supposed to create a local one? */
if (!chan) {
if (mod->local_off == 0xFFFFFFFF)
if (!localmod_is_local_chan(mod))
continue;
/* Create new channel, pointing into local. */
chan = add_channel(map, map->map_size + mod->local_off, 0);
}
/* Save old, overwrite (keep nodeidx) */
/* Save old, update any fields they wanted to change */
for (size_t h = 0; h < 2; h++) {
if (!mod->updates_set[h])
continue;
bool was_set, all_changed;
const struct localmod_changes *c = &mod->changes[h];
/* Save existing versions */
mod->orig[h] = chan->half[h];
mod->orig_cupdate_off[h] = chan->cupdate_off[h];
chan->half[h] = mod->hc[h];
chan->half[h].nodeidx = mod->orig[h].nodeidx;
chan->cupdate_off[h] = 0xFFFFFFFF;
was_set = gossmap_chan_set(chan, h);
/* Override specified fields. */
all_changed = true;
if (c->enabled)
chan->half[h].enabled = *c->enabled;
else
all_changed = false;
if (c->htlc_min)
chan->half[h].htlc_min = *c->htlc_min;
else
all_changed = false;
if (c->htlc_max)
chan->half[h].htlc_max = *c->htlc_max;
else
all_changed = false;
if (c->base_fee)
chan->half[h].base_fee = *c->base_fee;
else
all_changed = false;
if (c->proportional_fee)
chan->half[h].proportional_fee = *c->proportional_fee;
else
all_changed = false;
if (c->delay)
chan->half[h].delay = *c->delay;
else
all_changed = false;
/* Is it all defined?
* This controls gossmap_chan_set(chan, h); */
if (was_set || all_changed)
chan->cupdate_off[h] = 0xFFFFFFFF;
else
chan->cupdate_off[h] = 0;
}
}
}
@ -988,15 +1079,9 @@ void gossmap_remove_localmods(struct gossmap *map,
if (chan->cann_off >= map->map_size) {
gossmap_remove_chan(map, chan);
} else {
/* Restore (keep nodeidx). */
/* Restore. */
for (size_t h = 0; h < 2; h++) {
u32 nodeidx;
if (!mod->updates_set[h])
continue;
nodeidx = chan->half[h].nodeidx;
chan->half[h] = mod->orig[h];
chan->half[h].nodeidx = nodeidx;
chan->cupdate_off[h] = mod->orig_cupdate_off[h];
}
}

View file

@ -99,17 +99,27 @@ bool gossmap_local_addchan(struct gossmap_localmods *localmods,
/* Create a local-only channel_update: can apply to lcoal-only or
* normal channels. Returns false if amounts don't fit in our
* internal representation (implies channel unusable anyway). */
* internal representation (implies channel unusable anyway). Any
* NULL arguments mean "leave as is". */
bool gossmap_local_updatechan(struct gossmap_localmods *localmods,
struct short_channel_id scid,
struct amount_msat htlc_min,
struct amount_msat htlc_max,
u32 base_fee,
u32 proportional_fee,
u16 delay,
bool enabled,
int dir)
NO_NULL_ARGS;
const struct short_channel_id_dir *scidd,
const bool *enabled,
const struct amount_msat *htlc_min,
const struct amount_msat *htlc_max,
const struct amount_msat *base_fee,
const u32 *proportional_fee,
const u16 *delay);
/* Convenience version which sets everything (older API) */
bool gossmap_local_setchan(struct gossmap_localmods *localmods,
struct short_channel_id scid,
struct amount_msat htlc_min,
struct amount_msat htlc_max,
struct amount_msat base_fee,
u32 proportional_fee,
u16 delay,
bool enabled,
int dir);
/* Apply localmods to this map */
void gossmap_apply_localmods(struct gossmap *map,

View file

@ -28,12 +28,12 @@ void gossmod_add_localchan(struct gossmap_localmods *mods,
/* 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);
gossmap_local_updatechan(mods, scidd,
&enabled,
&min, &max,
&fee_base,
&fee_proportional,
&cltv_delta);
}
struct gossmap_localmods *

View file

@ -485,25 +485,25 @@ int main(int argc, char *argv[])
assert(!gossmap_find_node(map, &l4));
/* Now update it both local, and an existing one. */
gossmap_local_updatechan(mods, scid_local,
AMOUNT_MSAT(1),
AMOUNT_MSAT(100000),
2, 3, 4, true, 0);
gossmap_local_setchan(mods, scid_local,
AMOUNT_MSAT(1),
AMOUNT_MSAT(100000),
AMOUNT_MSAT(2), 3, 4, true, 0);
/* Adding an existing channel is a noop. */
assert(gossmap_local_addchan(mods, &l2, &l3, scid23, NULL));
gossmap_local_updatechan(mods, scid23,
AMOUNT_MSAT(99),
AMOUNT_MSAT(100),
101, 102, 103, true, 0);
gossmap_local_setchan(mods, scid23,
AMOUNT_MSAT(99),
AMOUNT_MSAT(100),
AMOUNT_MSAT(101), 102, 103, true, 0);
/* We can "update" a channel which doesn't exist, and it's a noop */
scid_nonexisting.u64 = 1;
gossmap_local_updatechan(mods, scid_nonexisting,
AMOUNT_MSAT(1),
AMOUNT_MSAT(100000),
2, 3, 4, false, 0);
gossmap_local_setchan(mods, scid_nonexisting,
AMOUNT_MSAT(1),
AMOUNT_MSAT(100000),
AMOUNT_MSAT(2), 3, 4, false, 0);
gossmap_apply_localmods(map, mods);
chan = gossmap_find_chan(map, &scid_local);

View file

@ -138,17 +138,18 @@ int main(int argc, char *argv[])
/* We overlay our own channels as zero fee & delay, since we don't pay fees */
struct gossmap_localmods *localmods = gossmap_localmods_new(gossmap);
for (size_t i = 0; i < me->num_chans; i++) {
int dir;
struct short_channel_id scid;
struct gossmap_chan *c = gossmap_nth_chan(gossmap, me, i, &dir);
struct short_channel_id_dir scidd;
const struct amount_msat base_fee = AMOUNT_MSAT(0);
const u32 proportional_fee = 0;
struct gossmap_chan *c = gossmap_nth_chan(gossmap, me, i, &scidd.dir);
if (!c->half[dir].enabled)
if (!c->half[scidd.dir].enabled)
continue;
scid = gossmap_chan_scid(gossmap, c);
assert(gossmap_local_updatechan(localmods, scid,
amount_msat(fp16_to_u64(c->half[dir].htlc_min)),
amount_msat(fp16_to_u64(c->half[dir].htlc_max)),
0, 0, 0, true, dir));
scidd.scid = gossmap_chan_scid(gossmap, c);
assert(gossmap_local_updatechan(localmods, &scidd,
NULL, NULL, NULL,
&base_fee, &proportional_fee,
NULL));
}
gossmap_apply_localmods(gossmap, localmods);

View file

@ -183,11 +183,11 @@ static void add_free_source(struct plugin *plugin,
/* 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;
struct mod {
struct short_channel_id_dir scidd;
fp16_t htlc_min, htlc_max;
bool enabled;
} *mods = tal_arr(tmpctx, struct mod, 0);
const struct amount_msat zero_base_fee = AMOUNT_MSAT(0);
const u16 zero_delay = 0;
const u32 zero_prop_fee = 0;
struct short_channel_id_dir *scidds
= tal_arr(tmpctx, struct short_channel_id_dir, 0);
gossmap_apply_localmods(gossmap, localmods);
@ -195,35 +195,24 @@ static void add_free_source(struct plugin *plugin,
srcnode = gossmap_find_node(gossmap, source);
for (size_t i = 0; srcnode && i < srcnode->num_chans; i++) {
struct short_channel_id_dir scidd;
const struct gossmap_chan *c;
const struct half_chan *h;
struct mod mod;
c = gossmap_nth_chan(gossmap, srcnode, i, &mod.scidd.dir);
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);
c = gossmap_nth_chan(gossmap, srcnode, i, &scidd.dir);
scidd.scid = gossmap_chan_scid(gossmap, c);
tal_arr_expand(&scidds, scidd);
}
gossmap_remove_localmods(gossmap, localmods);
/* Now we can update localmods */
for (size_t i = 0; i < tal_count(mods); i++) {
/* Now we can update localmods: we only change fee levels and delay */
for (size_t i = 0; i < tal_count(scidds); i++) {
if (!gossmap_local_updatechan(localmods,
mods[i].scidd.scid,
/* Keep min and max */
/* FIXME: lossy conversion! */
amount_msat(fp16_to_u64(mods[i].htlc_min)),
amount_msat(fp16_to_u64(mods[i].htlc_max)),
0, 0, 0,
/* Keep enabled flag */
mods[i].enabled,
mods[i].scidd.dir))
plugin_err(plugin, "Could not zero fee on %s",
fmt_short_channel_id_dir(tmpctx, &mods[i].scidd));
&scidds[i],
NULL, NULL, NULL,
&zero_base_fee, &zero_prop_fee,
&zero_delay))
plugin_err(plugin, "Could not zero fee/delay on %s",
fmt_short_channel_id_dir(tmpctx, &scidds[i]));
}
}

View file

@ -319,22 +319,19 @@ void layer_add_localmods(const struct layer *layer,
if (!node)
continue;
for (size_t n = 0; n < node->num_chans; n++) {
struct short_channel_id scid;
struct short_channel_id_dir scidd;
struct gossmap_chan *c;
int dir;
c = gossmap_nth_chan(gossmap, node, n, &dir);
scid = gossmap_chan_scid(gossmap, c);
bool enabled = false;
struct amount_msat zero = AMOUNT_MSAT(0);
c = gossmap_nth_chan(gossmap, node, n, &scidd.dir);
scidd.scid = gossmap_chan_scid(gossmap, c);
/* Disabled zero-capacity on incoming */
gossmap_local_updatechan(localmods,
scid,
AMOUNT_MSAT(0),
AMOUNT_MSAT(0),
0,
0,
0,
false,
!dir);
&scidd,
&enabled,
&zero, &zero,
NULL, NULL, NULL);
}
}
@ -344,30 +341,29 @@ void layer_add_localmods(const 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++) {
struct short_channel_id_dir scidd;
bool enabled = true;
if (!lc->half[i].enabled)
continue;
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,
true,
i);
scidd.scid = lc->scid;
scidd.dir = i;
gossmap_local_updatechan(localmods, &scidd,
&enabled,
&lc->half[i].htlc_min,
&lc->half[i].htlc_max,
&lc->half[i].base_fee,
&lc->half[i].proportional_fee,
&lc->half[i].delay);
}
}
/* Now disable any channels they asked us to */
for (size_t i = 0; i < tal_count(layer->disabled_channels); i++) {
bool enabled = false;
gossmap_local_updatechan(localmods,
layer->disabled_channels[i].scid,
AMOUNT_MSAT(0),
AMOUNT_MSAT(0),
0,
0,
0,
false,
layer->disabled_channels[i].dir);
&layer->disabled_channels[i],
&enabled,
NULL, NULL, NULL, NULL, NULL);
}
}

View file

@ -95,7 +95,8 @@ gossmods_from_listpeers(const tal_t *ctx,
struct node_id peer_id;
const char *err;
u8 *features = NULL;
struct short_channel_id fake_scid;
struct short_channel_id_dir fake_scidd;
bool enabled = true;
err = json_scan(tmpctx, buf, peer,
"{connected:%,"
@ -112,13 +113,12 @@ gossmods_from_listpeers(const tal_t *ctx,
continue;
/* Add a fake channel */
fake_scid.u64 = i;
fake_scidd.scid.u64 = i;
fake_scidd.dir = node_id_idx(self, &peer_id);
gossmap_local_addchan(mods, self, &peer_id, fake_scid, NULL);
gossmap_local_updatechan(mods, fake_scid,
AMOUNT_MSAT(0),
AMOUNT_MSAT(0),
0, 0, 0, true, node_id_idx(self, &peer_id));
gossmap_local_addchan(mods, self, &peer_id, fake_scidd.scid, NULL);
gossmap_local_updatechan(mods, &fake_scidd, &enabled,
NULL, NULL, NULL, NULL, NULL);
}
return mods;
}

View file

@ -409,13 +409,10 @@ static void gossmod_cb(struct gossmap_localmods *mods,
/* 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);
gossmap_local_updatechan(mods, scidd,
&enabled,
&min, &max,
&fee_base, &fee_proportional, &cltv_delta);
/* Is it disabled? */
if (!enabled)
@ -516,13 +513,19 @@ static void add_hintchan(struct payment *payment, const struct node_id *src,
assert(payment);
assert(payment->local_gossmods);
int dir = node_id_idx(src, dst);
const char *errmsg;
const struct chan_extra *ce =
uncertainty_find_channel(pay_plugin->uncertainty, scid);
if (!ce) {
struct short_channel_id_dir scidd;
/* We assume any HTLC is allowed */
struct amount_msat htlc_min = AMOUNT_MSAT(0), htlc_max = MAX_CAPACITY;
struct amount_msat fee_base = amount_msat(fee_base_msat);
bool enabled = true;
scidd.scid = scid;
scidd.dir = node_id_idx(src, dst);
/* This channel is not public, we don't know his capacity
One possible solution is set the capacity to
MAX_CAP and the state to [0,MAX_CAP]. Alternatively we could
@ -542,11 +545,10 @@ static void add_hintchan(struct payment *payment, const struct node_id *src,
if (!gossmap_local_addchan(payment->local_gossmods, src, dst,
scid, NULL) ||
!gossmap_local_updatechan(
payment->local_gossmods, scid,
/* We assume any HTLC is allowed */
AMOUNT_MSAT(0), MAX_CAPACITY, fee_base_msat,
fee_proportional_millionths, cltv_expiry_delta, true,
dir)) {
payment->local_gossmods, &scidd,
&enabled, &htlc_min, &htlc_max,
&fee_base, &fee_proportional_millionths,
&cltv_expiry_delta)) {
errmsg = tal_fmt(
tmpctx,
"Failed to update scid=%s in the local_gossmods.",

View file

@ -134,35 +134,35 @@ int main(int argc, char *argv[])
/* 1->2->4 has capacity 10k sat, 1->3->4 has capacity 5k sat (lower fee!) */
assert(gossmap_local_addchan(mods, &l1, &l2, scid12, NULL));
assert(gossmap_local_updatechan(mods, scid12,
/*htlc_min=*/ AMOUNT_MSAT(0),
/*htlc_max=*/ AMOUNT_MSAT(10000000),
/*base_fee=*/ 0,
/*ppm_fee =*/ 1001,
/* delay =*/ 5,
/* enabled=*/ true,
/* dir =*/ 0));
assert(gossmap_local_setchan(mods, scid12,
/*htlc_min=*/ AMOUNT_MSAT(0),
/*htlc_max=*/ AMOUNT_MSAT(10000000),
/*base_fee=*/ AMOUNT_MSAT(0),
/*ppm_fee =*/ 1001,
/* delay =*/ 5,
/* enabled=*/ true,
/* dir =*/ 0));
assert(gossmap_local_addchan(mods, &l2, &l4, scid24, NULL));
assert(gossmap_local_updatechan(mods, scid24,
AMOUNT_MSAT(0),
AMOUNT_MSAT(10000000),
0, 1002, 5,
true,
0));
assert(gossmap_local_setchan(mods, scid24,
AMOUNT_MSAT(0),
AMOUNT_MSAT(10000000),
AMOUNT_MSAT(0), 1002, 5,
true,
0));
assert(gossmap_local_addchan(mods, &l1, &l3, scid13, NULL));
assert(gossmap_local_updatechan(mods, scid13,
AMOUNT_MSAT(0),
AMOUNT_MSAT(5000000),
0, 503, 5,
true,
0));
assert(gossmap_local_setchan(mods, scid13,
AMOUNT_MSAT(0),
AMOUNT_MSAT(5000000),
AMOUNT_MSAT(0), 503, 5,
true,
0));
assert(gossmap_local_addchan(mods, &l3, &l4, scid34, NULL));
assert(gossmap_local_updatechan(mods, scid34,
AMOUNT_MSAT(0),
AMOUNT_MSAT(5000000),
0, 504, 5,
true,
0));
assert(gossmap_local_setchan(mods, scid34,
AMOUNT_MSAT(0),
AMOUNT_MSAT(5000000),
AMOUNT_MSAT(0), 504, 5,
true,
0));
gossmap_apply_localmods(gossmap, mods);
chan_extra_map = tal(tmpctx, struct chan_extra_map);

View file

@ -481,12 +481,12 @@ int main(int argc, char *argv[])
/* 400,000sat channel from 1->3, basefee 0, ppm 1000, delay 5 */
assert(gossmap_local_addchan(mods, &l1, &l3, scid13, NULL));
assert(gossmap_local_updatechan(mods, scid13,
AMOUNT_MSAT(0),
AMOUNT_MSAT(400000000),
0, 1000, 5,
true,
0));
assert(gossmap_local_setchan(mods, scid13,
AMOUNT_MSAT(0),
AMOUNT_MSAT(400000000),
AMOUNT_MSAT(0), 1000, 5,
true,
0));
/* Apply changes, check they work. */
gossmap_apply_localmods(gossmap, mods);