Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contrib/msggen/msggen/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -16117,7 +16117,7 @@
"",
"Layers are generally maintained by plugins, either to contain persistent information about capacities which have been discovered, or to contain transient information for this particular payment (such as blinded paths or routehints).",
"",
"There are three automatic layers: *auto.localchans* contains information on local channels from this node (including non-public ones), and their exact current spendable capacities. *auto.sourcefree* overrides all channels (including those from previous layers) leading out of the *source* to be zero fee and zero delay. These are both useful in the case where the source is the current node. And *auto.no_mpp_support* forces getroutes to return a single flow, though only basic checks are done that the result is useful."
"There are four automatic layers: *auto.localchans* contains information on local channels from this node (including non-public ones), and their exact current spendable capacities. *auto.sourcefree* overrides all channels (including those from previous layers) leading out of the *source* to be zero fee and zero delay. These are both useful in the case where the source is the current node. *auto.no_mpp_support* forces getroutes to return a single flow, though only basic checks are done that the result is useful. And *auto.ignorelocalhtlclimits* that overrides the HTLC maximum and minimum limits on local channels."
],
"categories": [
"readonly"
Expand Down
2 changes: 1 addition & 1 deletion doc/schemas/lightning-getroutes.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"",
"Layers are generally maintained by plugins, either to contain persistent information about capacities which have been discovered, or to contain transient information for this particular payment (such as blinded paths or routehints).",
"",
"There are three automatic layers: *auto.localchans* contains information on local channels from this node (including non-public ones), and their exact current spendable capacities. *auto.sourcefree* overrides all channels (including those from previous layers) leading out of the *source* to be zero fee and zero delay. These are both useful in the case where the source is the current node. And *auto.no_mpp_support* forces getroutes to return a single flow, though only basic checks are done that the result is useful."
"There are four automatic layers: *auto.localchans* contains information on local channels from this node (including non-public ones), and their exact current spendable capacities. *auto.sourcefree* overrides all channels (including those from previous layers) leading out of the *source* to be zero fee and zero delay. These are both useful in the case where the source is the current node. *auto.no_mpp_support* forces getroutes to return a single flow, though only basic checks are done that the result is useful. And *auto.ignorelocalhtlclimits* that overrides the HTLC maximum and minimum limits on local channels."
],
"categories": [
"readonly"
Expand Down
42 changes: 41 additions & 1 deletion plugins/askrene/askrene.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ static struct command_result *param_layer_names(struct command *cmd,
/* Must be a known layer name */
if (streq((*arr)[i], "auto.localchans")
|| streq((*arr)[i], "auto.no_mpp_support")
|| streq((*arr)[i], "auto.sourcefree"))
|| streq((*arr)[i], "auto.sourcefree")
|| streq((*arr)[i], "auto.ignorelocalhtlclimits"))
continue;
if (!find_layer(get_askrene(cmd->plugin), (*arr)[i])) {
return command_fail_badparam(cmd, name, buffer, t,
Expand Down Expand Up @@ -210,6 +211,39 @@ static struct layer *source_free_layer(const tal_t *ctx,
return layer;
}

/* If we're the payer, the HTLC min/max limits do not apply. */
static struct layer *
source_ignorelimits_layer(const tal_t *ctx, struct askrene *askrene,
const struct node_id *source,
struct gossmap_localmods *localmods)
{
struct gossmap *gossmap = askrene->gossmap;
const struct gossmap_node *srcnode;
const struct amount_msat htlc_min = AMOUNT_MSAT(0),
htlc_max = AMOUNT_MSAT(UINT64_MAX);
struct layer *layer =
new_temp_layer(ctx, askrene, "auto.ignorelocalhtlclimits");

/* We apply this so we see any created channels */
gossmap_apply_localmods(gossmap, localmods);

/* If we're not in map, we complain later */
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;

c = gossmap_nth_chan(gossmap, srcnode, i, &scidd.dir);
scidd.scid = gossmap_chan_scid(gossmap, c);
layer_add_update_channel(layer, &scidd, NULL, &htlc_min,
&htlc_max, NULL, NULL, NULL);
}
gossmap_remove_localmods(gossmap, localmods);

return layer;
}

/* We're going to abuse MCF, and take the largest flow it gives and ram everything
* through it. This is more effective if there's at least a *chance* that can handle
* the full amount.
Expand Down Expand Up @@ -398,6 +432,12 @@ static const char *get_routes(const tal_t *ctx,
} else if (streq(layers[i], "auto.no_mpp_support")) {
plugin_log(rq->plugin, LOG_DBG, "Adding auto.no_mpp_support, sorry");
l = remove_small_channel_layer(layers, askrene, amount, localmods);
} else if (streq(layers[i],
"auto.ignorelocalhtlclimits")) {
plugin_log(rq->plugin, LOG_DBG,
"Adding auto.ignorelocalhtlclimits");
l = source_ignorelimits_layer(
layers, askrene, source, localmods);
} else {
assert(streq(layers[i], "auto.sourcefree"));
plugin_log(rq->plugin, LOG_DBG, "Adding auto.sourcefree");
Expand Down
37 changes: 37 additions & 0 deletions tests/test_askrene.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,43 @@ def test_getroutes_auto_localchans(node_factory):
{'short_channel_id_dir': '1x2x1/1', 'amount_msat': 101000, 'delay': 99 + 6}]])


def test_getroutes_auto_ignorelocalhtlclimits(node_factory):
"""Test getroutes call with auto.ignorelocalhtlclimits layer"""
l1 = node_factory.get_node()
gsfile, nodemap = generate_gossip_store([GenChannel(0, 1, forward=GenChannel.Half(propfee=10000)),
GenChannel(1, 2, forward=GenChannel.Half(propfee=10000))],
nodemap={0: l1.info['id']})

# We get bad signature warnings, since our gossip is made up!
l2 = node_factory.get_node(allow_warning=True, gossip_store_file=gsfile.name)

# Now l2 believes l1 has an entire network behind it.
scid12, _ = l2.fundchannel(l1, 10**6, announce_channel=True)
# Set l2->l1 channel HTCL_max = 0
l2.rpc.setchannel(l1.info["id"], htlcmax=0)

# Cannot find a route unless we use auto.ignorelocalhtlclimits.
with pytest.raises(RpcError, match="We could not find a usable set of paths"):
l2.rpc.getroutes(source=l2.info['id'],
destination=nodemap[2],
amount_msat=100000,
layers=["auto.localchans"],
maxfee_msat=100000,
final_cltv=99)

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


def test_fees_dont_exceed_constraints(node_factory):
msat = 100000000
max_msat = int(msat * 0.45)
Expand Down