Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add exclude option to pay #4906

Merged
merged 4 commits into from
Dec 4, 2021
Merged
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
59 changes: 59 additions & 0 deletions common/json_tok.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <common/json_command.h>
#include <common/json_helpers.h>
#include <common/json_tok.h>
#include <common/route.h>

struct command_result *param_array(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
Expand Down Expand Up @@ -649,6 +650,64 @@ param_routehint_array(struct command *cmd, const char *name, const char *buffer,
return NULL;
}

struct command_result *param_route_exclusion(struct command *cmd,
const char *name, const char *buffer, const jsmntok_t *tok,
struct route_exclusion **re)
{
*re = tal(cmd, struct route_exclusion);
struct short_channel_id_dir *chan_id =
tal(tmpctx, struct short_channel_id_dir);
if (!short_channel_id_dir_from_str(buffer + tok->start,
tok->end - tok->start,
chan_id)) {
struct node_id *node_id = tal(tmpctx, struct node_id);

if (!json_to_node_id(buffer, tok, node_id))
return command_fail_badparam(cmd, "exclude",
buffer, tok,
"should be short_channel_id_dir or node_id");

(*re)->type = EXCLUDE_NODE;
(*re)->u.node_id = *node_id;
} else {
(*re)->type = EXCLUDE_CHANNEL;
(*re)->u.chan_id = *chan_id;
}

return NULL;
}

struct command_result *
param_route_exclusion_array(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
struct route_exclusion ***res)
{
size_t i;
const jsmntok_t *curr;
char *element_name;
struct command_result *err;
if (tok->type != JSMN_ARRAY) {
return command_fail(
cmd, JSONRPC2_INVALID_PARAMS,
"Exclude array %s (\"%s\") is not an array",
name, json_strdup(tmpctx, buffer, tok));
}

*res = tal_arr(cmd, struct route_exclusion *, 0);
json_for_each_arr(i, curr, tok) {
struct route_exclusion *element;
element_name = tal_fmt(cmd, "%s[%zu]", name, i);
err = param_route_exclusion(cmd, element_name, buffer, curr, &element);
if (err != NULL) {
return err;
}
tal_arr_expand(res, element);

tal_free(element_name);
}
return NULL;
}

struct command_result *param_lease_hex(struct command *cmd,
const char *name,
const char *buffer,
Expand Down
10 changes: 10 additions & 0 deletions common/json_tok.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct channel_id;
struct command;
struct command_result;
struct json_escape;
struct route_exclusion;
struct sha256;
struct wally_psbt;

Expand Down Expand Up @@ -205,6 +206,15 @@ struct command_result *
param_routehint_array(struct command *cmd, const char *name, const char *buffer,
const jsmntok_t *tok, struct route_info ***ris);

struct command_result *param_route_exclusion(struct command *cmd,
const char *name, const char *buffer, const jsmntok_t *tok,
struct route_exclusion **re);

struct command_result *
param_route_exclusion_array(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
struct route_exclusion ***res);

/**
* Parse a 'compact-lease' (serialized lease_rates) back into lease_rates
*/
Expand Down
18 changes: 18 additions & 0 deletions common/route.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,22 @@ struct route_hop *route_from_dijkstra(const tal_t *ctx,
const struct gossmap_node *src,
struct amount_msat final_amount,
u32 final_cltv);

/*
* Manually exlude nodes or channels from a route.
* Used with `getroute` and `pay` commands
*/
enum route_exclusion_type {
EXCLUDE_CHANNEL = 1,
EXCLUDE_NODE = 2
};

struct route_exclusion {
enum route_exclusion_type type;
union {
struct short_channel_id_dir chan_id;
struct node_id node_id;
} u;
};

#endif /* LIGHTNING_COMMON_ROUTE_H */
6 changes: 4 additions & 2 deletions contrib/pyln-client/pyln/client/lightning.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ def dev_memleak(self):

def dev_pay(self, bolt11, msatoshi=None, label=None, riskfactor=None,
maxfeepercent=None, retry_for=None,
maxdelay=None, exemptfee=None, use_shadow=True):
maxdelay=None, exemptfee=None, use_shadow=True, exclude=[]):
"""
A developer version of `pay`, with the possibility to deactivate
shadow routing (used for testing).
Expand All @@ -631,6 +631,7 @@ def dev_pay(self, bolt11, msatoshi=None, label=None, riskfactor=None,
"maxdelay": maxdelay,
"exemptfee": exemptfee,
"use_shadow": use_shadow,
"exclude": exclude,
}
return self.call("pay", payload)

Expand Down Expand Up @@ -989,7 +990,7 @@ def newaddr(self, addresstype=None):

def pay(self, bolt11, msatoshi=None, label=None, riskfactor=None,
maxfeepercent=None, retry_for=None,
maxdelay=None, exemptfee=None):
maxdelay=None, exemptfee=None, exclude=[]):
"""
Send payment specified by {bolt11} with {msatoshi}
(ignored if {bolt11} has an amount), optional {label}
Expand All @@ -1004,6 +1005,7 @@ def pay(self, bolt11, msatoshi=None, label=None, riskfactor=None,
"retry_for": retry_for,
"maxdelay": maxdelay,
"exemptfee": exemptfee,
"exclude": exclude,
}
return self.call("pay", payload)

Expand Down
6 changes: 6 additions & 0 deletions doc/lightning-pay.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ SYNOPSIS

**pay** *bolt11* \[*msatoshi*\] \[*label*\] \[*riskfactor*\]
\[*maxfeepercent*\] \[*retry\_for*\] \[*maxdelay*\] \[*exemptfee*\]
\[*exclude*\]

DESCRIPTION
-----------
Expand Down Expand Up @@ -40,6 +41,11 @@ finding routes and retrying the payment. However, a payment may be
delayed for up to `maxdelay` blocks by another node; clients should be
prepared for this worst case.

*exclude* is a JSON array of short-channel-id/direction (e.g. \[
"564334x877x1/0", "564195x1292x0/1" \]) or node-id which should be excluded
from consideration for routing. The default is not to exclude any channels
or nodes.

When using *lightning-cli*, you may skip optional parameters by using
*null*. Alternatively, use **-k** option to provide parameters by name.

Expand Down
42 changes: 42 additions & 0 deletions plugins/libplugin-pay.c
Original file line number Diff line number Diff line change
Expand Up @@ -3885,3 +3885,45 @@ static void payee_incoming_limit_step_cb(void *d UNUSED, struct payment *p)

REGISTER_PAYMENT_MODIFIER(payee_incoming_limit, void *, NULL,
payee_incoming_limit_step_cb);

static struct route_exclusions_data *
route_exclusions_data_init(struct payment *p)
{
struct route_exclusions_data *d;
if (p->parent != NULL) {
return payment_mod_route_exclusions_get_data(p->parent);
} else {
d = tal(p, struct route_exclusions_data);
d->exclusions = NULL;
}
return d;
}

static void route_exclusions_step_cb(struct route_exclusions_data *d,
struct payment *p)
{
if (p->parent)
return payment_continue(p);
struct route_exclusion **exclusions = d->exclusions;
for (size_t i = 0; i < tal_count(exclusions); i++) {
struct route_exclusion *e = exclusions[i];
if (e->type == EXCLUDE_CHANNEL) {
channel_hints_update(p, e->u.chan_id.scid, e->u.chan_id.dir,
false, false, NULL, NULL);
} else {
if (node_id_eq(&e->u.node_id, p->destination)) {
payment_abort(p, "Payee is manually excluded");
return;
} else if (node_id_eq(&e->u.node_id, p->local_id)) {
payment_abort(p, "Payer is manually excluded");
return;
}

tal_arr_expand(&p->excluded_nodes, e->u.node_id);
}
}
payment_continue(p);
}

REGISTER_PAYMENT_MODIFIER(route_exclusions, struct route_exclusions_data *,
route_exclusions_data_init, route_exclusions_step_cb);
6 changes: 6 additions & 0 deletions plugins/libplugin-pay.h
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,10 @@ struct adaptive_split_mod_data {
u32 htlc_budget;
};

struct route_exclusions_data {
struct route_exclusion **exclusions;
};

/* List of globally available payment modifiers. */
REGISTER_PAYMENT_MODIFIER_HEADER(retry, struct retry_mod_data);
REGISTER_PAYMENT_MODIFIER_HEADER(routehints, struct routehints_data);
Expand All @@ -426,6 +430,8 @@ REGISTER_PAYMENT_MODIFIER_HEADER(local_channel_hints, void);
* we detect the payee to have, in order to not exhaust the number of HTLCs
* each of those channels can bear. */
REGISTER_PAYMENT_MODIFIER_HEADER(payee_incoming_limit, void);
REGISTER_PAYMENT_MODIFIER_HEADER(route_exclusions, struct route_exclusions_data);


struct payment *payment_new(tal_t *ctx, struct command *cmd,
struct payment *parent,
Expand Down
10 changes: 10 additions & 0 deletions plugins/pay.c
Original file line number Diff line number Diff line change
Expand Up @@ -2255,7 +2255,14 @@ payment_listsendpays_previous(struct command *cmd, const char *buf,
}

struct payment_modifier *paymod_mods[] = {
/* NOTE: The order in which these four paymods are executed is
* significant!
* local_channel_hints *must* execute first before route_exclusions
* which *must* execute before directpay.
* exemptfee *must* also execute before directpay.
*/
&local_channel_hints_pay_mod,
&route_exclusions_pay_mod,
&exemptfee_pay_mod,
&directpay_pay_mod,
&shadowroute_pay_mod,
Expand Down Expand Up @@ -2305,6 +2312,7 @@ static struct command_result *json_paymod(struct command *cmd,
struct sha256 *local_offer_id;
const struct tlv_invoice *b12;
struct out_req *req;
struct route_exclusion **exclusions;
#if DEVELOPER
bool *use_shadow;
#endif
Expand All @@ -2326,6 +2334,7 @@ static struct command_result *json_paymod(struct command *cmd,
maxdelay_default),
p_opt_def("exemptfee", param_msat, &exemptfee, AMOUNT_MSAT(5000)),
p_opt("localofferid", param_sha256, &local_offer_id),
p_opt("exclude", param_route_exclusion_array, &exclusions),
#if DEVELOPER
p_opt_def("use_shadow", param_bool, &use_shadow, true),
#endif
Expand Down Expand Up @@ -2479,6 +2488,7 @@ static struct command_result *json_paymod(struct command *cmd,
shadow_route = payment_mod_shadowroute_get_data(p);
payment_mod_presplit_get_data(p)->disable = disablempp;
payment_mod_adaptive_splitter_get_data(p)->disable = disablempp;
payment_mod_route_exclusions_get_data(p)->exclusions = exclusions;

/* This is an MPP enabled pay command, disable amount fuzzing. */
shadow_route->fuzz_amount = false;
Expand Down
52 changes: 3 additions & 49 deletions plugins/topology.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,6 @@ static struct gossmap *get_gossmap(void)
/* Convenience global since route_score_fuzz doesn't take args. 0 to 1. */
static double fuzz;

enum exclude_entry_type {
EXCLUDE_CHANNEL = 1,
EXCLUDE_NODE = 2
};

struct exclude_entry {
enum exclude_entry_type type;
union {
struct short_channel_id_dir chan_id;
struct node_id node_id;
} u;
};

/* Prioritize costs over distance, but with fuzz. Cost must be
* the same when the same channel queried, so we base it on that. */
static u64 route_score_fuzz(u32 distance,
Expand All @@ -68,7 +55,7 @@ static bool can_carry(const struct gossmap *map,
const struct gossmap_chan *c,
int dir,
struct amount_msat amount,
const struct exclude_entry **excludes)
struct route_exclusion **excludes)
{
struct node_id dstid;

Expand Down Expand Up @@ -143,12 +130,11 @@ static struct command_result *json_getroute(struct command *cmd,
{
struct node_id *destination;
struct node_id *source;
const jsmntok_t *excludetok;
struct amount_msat *msat;
u32 *cltv;
/* risk factor 12.345% -> riskfactor_millionths = 12345000 */
u64 *riskfactor_millionths, *fuzz_millionths;
const struct exclude_entry **excluded;
struct route_exclusion **excluded;
u32 *max_hops;
const struct dijkstra *dij;
struct route_hop *route;
Expand All @@ -164,7 +150,7 @@ static struct command_result *json_getroute(struct command *cmd,
p_opt_def("fromid", param_node_id, &source, local_id),
p_opt_def("fuzzpercent", param_millionths, &fuzz_millionths,
5000000),
p_opt("exclude", param_array, &excludetok),
p_opt("exclude", param_route_exclusion_array, &excluded),
p_opt_def("maxhops", param_number, &max_hops, ROUTING_MAX_HOPS),
NULL))
return command_param_failed();
Expand All @@ -176,38 +162,6 @@ static struct command_result *json_getroute(struct command *cmd,
buffer, params,
"should be <= 100");

if (excludetok) {
const jsmntok_t *t;
size_t i;

excluded = tal_arr(cmd, const struct exclude_entry *, 0);

json_for_each_arr(i, t, excludetok) {
struct exclude_entry *entry = tal(excluded, struct exclude_entry);
struct short_channel_id_dir *chan_id = tal(tmpctx, struct short_channel_id_dir);
if (!short_channel_id_dir_from_str(buffer + t->start,
t->end - t->start,
chan_id)) {
struct node_id *node_id = tal(tmpctx, struct node_id);

if (!json_to_node_id(buffer, t, node_id))
return command_fail_badparam(cmd, "exclude",
buffer, t,
"should be short_channel_id or node_id");

entry->type = EXCLUDE_NODE;
entry->u.node_id = *node_id;
} else {
entry->type = EXCLUDE_CHANNEL;
entry->u.chan_id = *chan_id;
}

tal_arr_expand(&excluded, entry);
}
} else {
excluded = NULL;
}

gossmap = get_gossmap();
src = gossmap_find_node(gossmap, source);
if (!src)
Expand Down
Loading