plugins/libplugin-pay: use gossmap.

This is a fairly direct translation.  Even so, it should be faster in
most cases, and and we can do more sophisticated things if we want.

This also handles disabled channels better.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Changed: plugins: `pay` will now try disabled channels as a last resort.
This commit is contained in:
Rusty Russell 2020-10-20 14:32:00 +10:30
parent eadf2c91fe
commit b470ae2c73
2 changed files with 257 additions and 123 deletions

View File

@ -8,6 +8,7 @@
#include <common/memleak.h>
#include <common/pseudorand.h>
#include <common/random_select.h>
#include <common/route.h>
#include <common/type_to_string.h>
#include <errno.h>
#include <plugins/libplugin-pay.h>
@ -505,75 +506,6 @@ static void payment_chanhints_apply_route(struct payment *p, bool remove)
}
}
static struct command_result *payment_getroute_result(struct command *cmd,
const char *buffer,
const jsmntok_t *toks,
struct payment *p)
{
const jsmntok_t *rtok = json_get_member(buffer, toks, "route");
struct amount_msat fee;
assert(rtok != NULL);
p->route = json_to_route(p, buffer, rtok);
p->step = PAYMENT_STEP_GOT_ROUTE;
fee = payment_route_fee(p);
/* Ensure that our fee and CLTV budgets are respected. */
if (amount_msat_greater(fee, p->constraints.fee_budget)) {
payment_exclude_most_expensive(p);
p->route = tal_free(p->route);
payment_fail(
p, "Fee exceeds our fee budget: %s > %s, discarding route",
type_to_string(tmpctx, struct amount_msat, &fee),
type_to_string(tmpctx, struct amount_msat,
&p->constraints.fee_budget));
return command_still_pending(cmd);
}
if (p->route[0].delay > p->constraints.cltv_budget) {
u32 delay = p->route[0].delay;
payment_exclude_longest_delay(p);
p->route = tal_free(p->route);
payment_fail(p, "CLTV delay exceeds our CLTV budget: %d > %d",
delay, p->constraints.cltv_budget);
return command_still_pending(cmd);
}
/* Now update the constraints in fee_budget and cltv_budget so
* modifiers know what constraints they need to adhere to. */
if (!payment_constraints_update(&p->constraints, fee, p->route[0].delay)) {
paymod_log(p, LOG_BROKEN,
"Could not update constraints.");
abort();
}
/* Allow modifiers to modify the route, before
* payment_compute_onion_payloads uses the route to generate the
* onion_payloads */
payment_continue(p);
return command_still_pending(cmd);
}
static struct command_result *payment_getroute_error(struct command *cmd,
const char *buffer,
const jsmntok_t *toks,
struct payment *p)
{
int code;
const jsmntok_t *codetok = json_get_member(buffer, toks, "code"),
*msgtok = json_get_member(buffer, toks, "message");
json_to_int(buffer, codetok, &code);
p->route = NULL;
payment_fail(
p, "Error computing a route to %s: %.*s (%d)",
type_to_string(tmpctx, struct node_id, p->getroute->destination),
json_tok_full_len(msgtok), json_tok_full(buffer, msgtok), code);
/* Let payment_finished_ handle this, so we mark it as pending */
return command_still_pending(cmd);
}
static const struct short_channel_id_dir *
payment_get_excluded_channels(const tal_t *ctx, struct payment *p)
{
@ -608,49 +540,256 @@ static const struct node_id *payment_get_excluded_nodes(const tal_t *ctx,
return root->excluded_nodes;
}
/* Iterate through the channel_hints and exclude any channel that we are
* confident will not be able to handle this payment. */
static void payment_getroute_add_excludes(struct payment *p,
struct json_stream *js)
/* FIXME: This is slow! */
static const struct channel_hint *find_hint(const struct channel_hint *hints,
const struct short_channel_id *scid,
int dir)
{
const struct node_id *nodes;
const struct short_channel_id_dir *chans;
json_array_start(js, "exclude");
/* Collect and exclude all channels that are disabled or we know have
* insufficient capacity. */
chans = payment_get_excluded_channels(tmpctx, p);
for (size_t i=0; i<tal_count(chans); i++)
json_add_short_channel_id_dir(js, NULL, &chans[i]);
/* Now also exclude nodes that we think have failed. */
nodes = payment_get_excluded_nodes(tmpctx, p);
for (size_t i=0; i<tal_count(nodes); i++)
json_add_node_id(js, NULL, &nodes[i]);
/* And make sure we don't route in a circle via the routehint! */
if (p->temp_exclusion)
for (size_t i = 0; i < tal_count(p->temp_exclusion); ++i)
json_add_string(js, NULL, p->temp_exclusion[i]);
json_array_end(js);
for (size_t i = 0; i < tal_count(hints); i++) {
if (short_channel_id_eq(scid, &hints[i].scid.scid)
&& dir == hints[i].scid.dir)
return &hints[i];
}
return NULL;
}
static void payment_getroute(struct payment *p)
/* FIXME: This is slow! */
static bool dst_is_excluded(const struct gossmap *gossmmap,
const struct gossmap_chan *c,
int dir,
const struct node_id *nodes)
{
struct out_req *req;
req = jsonrpc_request_start(p->plugin, NULL, "getroute",
payment_getroute_result,
payment_getroute_error, p);
json_add_node_id(req->js, "id", p->getroute->destination);
json_add_amount_msat_only(req->js, "msatoshi", p->getroute->amount);
json_add_num(req->js, "cltv", p->getroute->cltv);
json_add_num(req->js, "maxhops", p->getroute->max_hops);
json_add_member(req->js, "riskfactor", false, "%lf",
p->getroute->riskfactorppm / 1000000.0);
payment_getroute_add_excludes(p, req->js);
send_outreq(p->plugin, req);
struct node_id dstid;
/* Premature optimization */
if (!tal_count(nodes))
return false;
gossmap_node_get_id(gossmap, gossmap_nth_node(gossmap, c, !dir),
&dstid);
for (size_t i = 0; i < tal_count(nodes); i++) {
if (node_id_eq(&dstid, &nodes[i]))
return true;
}
return false;
}
static bool payment_route_check(const struct gossmap *gossmap,
const struct gossmap_chan *c,
int dir,
struct amount_msat amount,
struct payment *p)
{
struct short_channel_id scid;
const struct channel_hint *hint;
if (dst_is_excluded(gossmap, c, dir, payment_root(p)->excluded_nodes))
return false;
if (dst_is_excluded(gossmap, c, dir, p->temp_exclusion))
return false;
scid = gossmap_chan_scid(gossmap, c);
hint = find_hint(payment_root(p)->channel_hints, &scid, dir);
if (!hint)
return true;
if (!hint->enabled)
return false;
if (amount_msat_greater_eq(amount, hint->estimated_capacity))
/* We exclude on equality because we've set the
* estimate to the smallest failed attempt. */
return false;
if (hint->local && hint->htlc_budget == 0)
/* If we cannot add any HTLCs to the channel we
* shouldn't look for a route through that channel */
return false;
return true;
}
static bool payment_route_can_carry(const struct gossmap *map,
const struct gossmap_chan *c,
int dir,
struct amount_msat amount,
struct payment *p)
{
if (!route_can_carry(map, c, dir, amount, p))
return false;
return payment_route_check(map, c, dir, amount, p);
}
static bool payment_route_can_carry_even_disabled(const struct gossmap *map,
const struct gossmap_chan *c,
int dir,
struct amount_msat amount,
struct payment *p)
{
if (!route_can_carry_even_disabled(map, c, dir, amount, p))
return false;
return payment_route_check(map, c, dir, amount, p);
}
static struct route_hop *route_hops_from_route(const tal_t *ctx,
struct payment *p,
struct route **r)
{
struct route_hop *hops = tal_arr(ctx, struct route_hop, tal_count(r));
struct amount_msat amt;
u32 delay;
for (size_t i = 0; i < tal_count(hops); i++) {
const struct gossmap_node *dst;
hops[i].channel_id = gossmap_chan_scid(gossmap, r[i]->c);
hops[i].direction = r[i]->dir;
hops[i].blinding = NULL;
/* nodeid is nodeid of *dst* */
dst = gossmap_nth_node(gossmap, r[i]->c, !r[i]->dir);
gossmap_node_get_id(gossmap, dst, &hops[i].nodeid);
if (gossmap_node_has_feature(gossmap, dst, OPT_VAR_ONION) != -1)
hops[i].style = ROUTE_HOP_TLV;
else
hops[i].style = ROUTE_HOP_LEGACY;
}
/* Now iterate backwards to derive amount and delay. */
amt = p->getroute->amount;
delay = p->getroute->cltv;
for (int i = tal_count(hops) - 1; i >= 0; i--) {
const struct half_chan *h = &r[i]->c->half[r[i]->dir];
hops[i].amount = amt;
hops[i].delay = delay;
if (!amount_msat_add_fee(&amt,
h->base_fee, h->proportional_fee))
abort();
delay += h->delay;
}
return hops;
}
static struct command_result *payment_getroute(struct payment *p)
{
const struct dijkstra *dij;
const struct gossmap_node *dst, *src;
struct route **r;
struct amount_msat fee;
bool (*can_carry)(const struct gossmap *,
const struct gossmap_chan *,
int,
struct amount_msat,
struct payment *);
/* Make sure we're up-to-date with any new entries */
gossmap_refresh(gossmap);
dst = gossmap_find_node(gossmap, p->getroute->destination);
if (!dst) {
payment_fail(
p, "Unknown destination %s",
type_to_string(tmpctx, struct node_id,
p->getroute->destination));
/* Let payment_finished_ handle this, so we mark it as pending */
return command_still_pending(p->cmd);
}
/* If we don't exist in gossip, routing can't happen. */
src = gossmap_find_node(gossmap, p->local_id);
if (!src) {
payment_fail(p, "We don't have any channels");
/* Let payment_finished_ handle this, so we mark it as pending */
return command_still_pending(p->cmd);
}
can_carry = payment_route_can_carry;
dij = dijkstra(tmpctx, gossmap, dst, p->getroute->amount,
p->getroute->riskfactorppm / 1000000.0,
can_carry, route_score_cheaper, p);
r = route_from_dijkstra(tmpctx, gossmap, dij, src);
if (!r) {
/* Try using disabled channels too */
/* FIXME: is there somewhere we can annotate this for paystatus? */
can_carry = payment_route_can_carry_even_disabled;
dij = dijkstra(tmpctx, gossmap, dst, p->getroute->amount,
p->getroute->riskfactorppm / 1000000.0,
can_carry, route_score_cheaper, p);
r = route_from_dijkstra(tmpctx, gossmap, dij, src);
if (!r) {
payment_fail(p, "No path found");
return command_still_pending(p->cmd);
}
}
/* If it's too far, fall back to using shortest path. */
if (tal_count(r) > p->getroute->max_hops) {
/* FIXME: is there somewhere we can annotate this for paystatus? */
dij = dijkstra(tmpctx, gossmap, dst, p->getroute->amount,
p->getroute->riskfactorppm / 1000000.0,
can_carry, route_score_shorter, p);
r = route_from_dijkstra(tmpctx, gossmap, dij, src);
if (!r) {
payment_fail(p, "No path found");
return command_still_pending(p->cmd);
}
/* If it's still too far, fail. */
if (tal_count(r) > p->getroute->max_hops) {
payment_fail(p, "Shortest path found was length %zu",
tal_count(p->route));
return command_still_pending(p->cmd);
}
}
/* OK, now we *have* a route */
p->step = PAYMENT_STEP_GOT_ROUTE;
p->route = route_hops_from_route(p, p, r);
fee = payment_route_fee(p);
/* Ensure that our fee and CLTV budgets are respected. */
if (amount_msat_greater(fee, p->constraints.fee_budget)) {
payment_exclude_most_expensive(p);
p->route = tal_free(p->route);
payment_fail(
p, "Fee exceeds our fee budget: %s > %s, discarding route",
type_to_string(tmpctx, struct amount_msat, &fee),
type_to_string(tmpctx, struct amount_msat,
&p->constraints.fee_budget));
return command_still_pending(p->cmd);
}
if (p->route[0].delay > p->constraints.cltv_budget) {
u32 delay = p->route[0].delay;
payment_exclude_longest_delay(p);
p->route = tal_free(p->route);
payment_fail(p, "CLTV delay exceeds our CLTV budget: %d > %d",
delay, p->constraints.cltv_budget);
return command_still_pending(p->cmd);
}
/* Now update the constraints in fee_budget and cltv_budget so
* modifiers know what constraints they need to adhere to. */
if (!payment_constraints_update(&p->constraints, fee, p->route[0].delay)) {
paymod_log(p, LOG_BROKEN,
"Could not update constraints.");
abort();
}
/* Allow modifiers to modify the route, before
* payment_compute_onion_payloads uses the route to generate the
* onion_payloads */
payment_continue(p);
return command_still_pending(p->cmd);
}
static u8 *tal_towire_legacy_payload(const tal_t *ctx, const struct legacy_payload *payload)
@ -2144,30 +2283,25 @@ static u32 route_cltv(u32 cltv,
* `excludes` parameter of `getroute`.
*/
static
const char **routehint_generate_exclusion_list(const tal_t *ctx,
struct route_info *routehint,
struct payment *payment)
struct node_id *routehint_generate_exclusion_list(const tal_t *ctx,
struct route_info *routehint,
struct payment *payment)
{
const char **exc;
size_t i;
struct node_id *exc;
if (!routehint || tal_count(routehint) == 0)
/* Nothing to exclude. */
return NULL;
exc = tal_arr(ctx, const char *, 0);
exc = tal_arr(ctx, struct node_id, tal_count(routehint));
/* Exclude every node except the first, because the first is
* the entry point to the routehint. */
for (i = 1 /* Skip the first! */; i < tal_count(routehint); ++i)
tal_arr_expand(&exc,
type_to_string(exc, struct node_id,
&routehint[i].pubkey));
for (size_t i = 1 /* Skip the first! */; i < tal_count(routehint); ++i)
exc[i-1] = routehint[i].pubkey;
/* Also exclude the destination, because it would be foolish to
* pass through it and *then* go to the routehint entry point. */
tal_arr_expand(&exc,
type_to_string(exc, struct node_id,
payment->destination));
exc[tal_count(routehint)-1] = *payment->destination;
return exc;
}

View File

@ -235,7 +235,7 @@ struct payment {
struct node_id *excluded_nodes;
/* Optional temporarily excluded channels/nodes (i.e. this routehint) */
const char **temp_exclusion;
struct node_id *temp_exclusion;
struct payment_result *result;