From e4379d0788d641766547127e316368fd98fdc780 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 11:00:33 +0100 Subject: [PATCH 01/31] renepay: separate flow and chan_extra Flow and chan_extra are two different concepts, we keep their definitions and APIs separate. --- plugins/renepay/chan_extra.c | 678 ++++++++++++++++ plugins/renepay/chan_extra.h | 239 ++++++ plugins/renepay/flow.c | 1466 ++++++---------------------------- plugins/renepay/flow.h | 274 +------ plugins/renepay/mcf.c | 23 +- 5 files changed, 1216 insertions(+), 1464 deletions(-) create mode 100644 plugins/renepay/chan_extra.c create mode 100644 plugins/renepay/chan_extra.h diff --git a/plugins/renepay/chan_extra.c b/plugins/renepay/chan_extra.c new file mode 100644 index 000000000000..c5120431f2f2 --- /dev/null +++ b/plugins/renepay/chan_extra.c @@ -0,0 +1,678 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include + +bool chan_extra_is_busy(const struct chan_extra *const ce) +{ + if (ce == NULL) + return false; + return ce->half[0].num_htlcs || ce->half[1].num_htlcs; +} + +const char *fmt_chan_extra_map(const tal_t *ctx, + struct chan_extra_map *chan_extra_map) +{ + tal_t *this_ctx = tal(ctx, tal_t); + char *buff = tal_fmt(ctx, "Uncertainty network:\n"); + struct chan_extra_map_iter it; + for (struct chan_extra *ch = chan_extra_map_first(chan_extra_map, &it); + ch; ch = chan_extra_map_next(chan_extra_map, &it)) { + const char *scid_str = fmt_short_channel_id(this_ctx, ch->scid); + for (int dir = 0; dir < 2; ++dir) { + tal_append_fmt( + &buff, "%s[%d]:(%s,%s)\n", scid_str, dir, + fmt_amount_msat(this_ctx, ch->half[dir].known_min), + fmt_amount_msat(this_ctx, ch->half[dir].known_max)); + } + } + tal_free(this_ctx); + return buff; +} + +const char *fmt_chan_extra_details(const tal_t *ctx, + const struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd) +{ + const tal_t *this_ctx = tal(ctx, tal_t); + const struct chan_extra *ce = + chan_extra_map_get(chan_extra_map, scidd->scid); + const struct chan_extra_half *ch; + char *str = tal_strdup(ctx, ""); + char sep = '('; + + if (!ce) { + // we have no information on this channel + tal_append_fmt(&str, "()"); + goto finished; + } + + ch = &ce->half[scidd->dir]; + if (ch->num_htlcs != 0) { + tal_append_fmt(&str, "%c%s in %zu htlcs", sep, + fmt_amount_msat(this_ctx, ch->htlc_total), + ch->num_htlcs); + sep = ','; + } + /* Happens with local channels, where we're certain. */ + if (amount_msat_eq(ch->known_min, ch->known_max)) { + tal_append_fmt(&str, "%cmin=max=%s", sep, + fmt_amount_msat(this_ctx, ch->known_min)); + sep = ','; + } else { + if (amount_msat_greater(ch->known_min, AMOUNT_MSAT(0))) { + tal_append_fmt( + &str, "%cmin=%s", sep, + fmt_amount_msat(this_ctx, ch->known_min)); + sep = ','; + } + if (!amount_msat_eq(ch->known_max, ce->capacity)) { + tal_append_fmt( + &str, "%cmax=%s", sep, + fmt_amount_msat(this_ctx, ch->known_max)); + sep = ','; + } + } + if (!streq(str, "")) + tal_append_fmt(&str, ")"); + +finished: + tal_free(this_ctx); + return str; +} + +struct chan_extra *new_chan_extra(struct chan_extra_map *chan_extra_map, + const struct short_channel_id scid, + struct amount_msat capacity) +{ + assert(chan_extra_map); + struct chan_extra *ce = tal(chan_extra_map, struct chan_extra); + if (!ce) + return ce; + + ce->scid = scid; + ce->capacity = capacity; + for (size_t i = 0; i <= 1; i++) { + ce->half[i].num_htlcs = 0; + ce->half[i].htlc_total = AMOUNT_MSAT(0); + ce->half[i].known_min = AMOUNT_MSAT(0); + ce->half[i].known_max = capacity; + } + if (!chan_extra_map_add(chan_extra_map, ce)) { + return tal_free(ce); + } + + /* Remove self from map when done */ + // TODO(eduardo): + // Is this desctructor really necessary? the chan_extra will deallocated + // when the chan_extra_map is freed. Anyways valgrind complains that the + // hash table is removing the element with a freed pointer. + // tal_add_destructor2(ce, destroy_chan_extra, chan_extra_map); + return ce; +} + +/* Based on the knowledge that we have and HTLCs, returns the greatest + * amount that we can send through this channel. */ +enum renepay_errorcode channel_liquidity(struct amount_msat *liquidity, + const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + const struct gossmap_chan *chan, + const int dir) +{ + const struct chan_extra_half *h = + get_chan_extra_half_by_chan(gossmap, chan_extra_map, chan, dir); + if (!h) + return RENEPAY_CHANNEL_NOT_FOUND; + struct amount_msat value_liquidity = h->known_max; + if (!amount_msat_sub(&value_liquidity, value_liquidity, h->htlc_total)) + return RENEPAY_AMOUNT_OVERFLOW; + *liquidity = value_liquidity; + return RENEPAY_NOERROR; +} + +/* Checks BOLT 7 HTLC fee condition: + * recv >= base_fee + (send*proportional_fee)/1000000 */ +bool check_fee_inequality(struct amount_msat recv, struct amount_msat send, + u64 base_fee, u64 proportional_fee) +{ + // nothing to forward, any incoming amount is good + if (amount_msat_zero(send)) + return true; + // FIXME If this addition fails we return false. The caller will not be + // able to know that there was an addition overflow, he will just assume + // that the fee inequality was not satisfied. + if (!amount_msat_add_fee(&send, base_fee, proportional_fee)) + return false; + return amount_msat_greater_eq(recv, send); +} + +/* Let `recv` be the maximum amount this channel can receive, this function + * computes the maximum amount this channel can forward `send`. + * From BOLT7 specification wee need to satisfy the following inequality: + * + * recv-send >= base_fee + floor(send*proportional_fee/1000000) + * + * That is equivalent to have + * + * send <= Bound(recv,send) + * + * where + * + * Bound(recv, send) = ((recv - base_fee)*1000000 + (send*proportional_fee) + *% 1000000)/(proportional_fee+1000000) + * + * However the quantity we want to determine, `send`, appears on both sides of + * the equation. However the term `send*proportional_fee) % 1000000` only + * contributes by increasing the bound by at most one so that we can neglect + * the extra term and use instead + * + * Bound_simple(recv) = ((recv - + *base_fee)*1000000)/(proportional_fee+1000000) + * + * as the upper bound for `send`. Formally one can check that + * + * Bound_simple(recv) <= Bound(recv, send) < Bound_simple(recv) + 2 + * + * So that if one wishes to find the very highest value of `send` that + * satisfies + * + * send <= Bound(recv, send) + * + * it is enough to compute + * + * send = Bound_simple(recv) + * + * which already satisfies the fee equation and then try to go higher + * with send+1, send+2, etc. But we know that it is enough to try up to + * send+1 because Bound(recv, send) < Bound_simple(recv) + 2. + * */ +enum renepay_errorcode channel_maximum_forward(struct amount_msat *max_forward, + const struct gossmap_chan *chan, + const int dir, + struct amount_msat recv) +{ + const u64 b = chan->half[dir].base_fee, + p = chan->half[dir].proportional_fee; + + const u64 one_million = 1000000; + u64 x_msat = + recv.millisatoshis; /* Raw: need to invert the fee equation */ + + // special case, when recv - base_fee <= 0, we cannot forward anything + if (x_msat <= b) { + *max_forward = amount_msat(0); + return RENEPAY_NOERROR; + } + + x_msat -= b; + + if (mul_overflows_u64(one_million, x_msat)) + return RENEPAY_AMOUNT_OVERFLOW; + + struct amount_msat best_send = + AMOUNT_MSAT_INIT((one_million * x_msat) / (one_million + p)); + + /* Try to increase the value we send (up tp the last millisat) until we + * fail to fulfill the fee inequality. It takes only one iteration + * though. */ + for (size_t i = 0; i < 10; ++i) { + struct amount_msat next_send; + if (!amount_msat_add(&next_send, best_send, amount_msat(1))) + return RENEPAY_AMOUNT_OVERFLOW; + + if (check_fee_inequality(recv, next_send, b, p)) + best_send = next_send; + else + break; + } + *max_forward = best_send; + return RENEPAY_NOERROR; +} + +/* This helper function preserves the uncertainty network invariant after the + * knowledge is updated. It assumes that the (channel,!dir) knowledge is + * correct. */ +static enum renepay_errorcode chan_extra_adjust_half(struct chan_extra *ce, + int dir) +{ + assert(ce); + assert(dir == 0 || dir == 1); + + struct amount_msat new_known_max, new_known_min; + + if (!amount_msat_sub(&new_known_max, ce->capacity, + ce->half[!dir].known_min) || + !amount_msat_sub(&new_known_min, ce->capacity, + ce->half[!dir].known_max)) + return RENEPAY_AMOUNT_OVERFLOW; + + ce->half[dir].known_max = new_known_max; + ce->half[dir].known_min = new_known_min; + return RENEPAY_NOERROR; +} + +/* Update the knowledge that this (channel,direction) can send x msat.*/ +static enum renepay_errorcode +chan_extra_can_send_(struct chan_extra *ce, int dir, struct amount_msat x) +{ + assert(ce); + assert(dir == 0 || dir == 1); + enum renepay_errorcode err; + + if (amount_msat_greater(x, ce->capacity)) + return RENEPAY_PRECONDITION_ERROR; + + struct amount_msat known_min, known_max; + + // in case we fail, let's remember the original state + known_min = ce->half[dir].known_min; + known_max = ce->half[dir].known_max; + + ce->half[dir].known_min = amount_msat_max(ce->half[dir].known_min, x); + ce->half[dir].known_max = amount_msat_max(ce->half[dir].known_max, x); + + err = chan_extra_adjust_half(ce, !dir); + if (err != RENEPAY_NOERROR) + goto restore_and_fail; + + return RENEPAY_NOERROR; + +restore_and_fail: + // we fail, thus restore the original state + ce->half[dir].known_min = known_min; + ce->half[dir].known_max = known_max; + return err; +} + +enum renepay_errorcode +chan_extra_can_send(struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd) +{ + assert(scidd); + assert(chan_extra_map); + struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scidd->scid); + if (!ce) + return RENEPAY_CHANNEL_NOT_FOUND; + return chan_extra_can_send_(ce, scidd->dir, + ce->half[scidd->dir].htlc_total); +} + +/* Update the knowledge that this (channel,direction) cannot send.*/ +enum renepay_errorcode +chan_extra_cannot_send(struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd) +{ + assert(scidd); + assert(chan_extra_map); + struct amount_msat x; + enum renepay_errorcode err; + struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scidd->scid); + if (!ce) + return RENEPAY_CHANNEL_NOT_FOUND; + + /* Note: sent is already included in htlc_total! */ + if (!amount_msat_sub(&x, ce->half[scidd->dir].htlc_total, + AMOUNT_MSAT(1))) + return RENEPAY_AMOUNT_OVERFLOW; + + struct amount_msat known_min, known_max; + // in case we fail, let's remember the original state + known_min = ce->half[scidd->dir].known_min; + known_max = ce->half[scidd->dir].known_max; + + /* If we "knew" the capacity was at least this, we just showed we're + * wrong! */ + if (amount_msat_less(x, ce->half[scidd->dir].known_min)) { + /* Skip to half of x, since we don't know (rounds down) */ + ce->half[scidd->dir].known_min = amount_msat_div(x, 2); + } + + ce->half[scidd->dir].known_max = + amount_msat_min(ce->half[scidd->dir].known_max, x); + + err = chan_extra_adjust_half(ce, !scidd->dir); + if (err != RENEPAY_NOERROR) + goto restore_and_fail; + return err; + +restore_and_fail: + // we fail, thus restore the original state + ce->half[scidd->dir].known_min = known_min; + ce->half[scidd->dir].known_max = known_max; + return err; +} + +/* Update the knowledge that this (channel,direction) has liquidity x.*/ +// FIXME for being this low level API, I thinkg it's too much to have verbose +// error messages +static enum renepay_errorcode +chan_extra_set_liquidity_(struct chan_extra *ce, int dir, struct amount_msat x) +{ + assert(ce); + assert(dir == 0 || dir == 1); + enum renepay_errorcode err; + + if (amount_msat_greater(x, ce->capacity)) + return RENEPAY_PRECONDITION_ERROR; + + // in case we fail, let's remember the original state + struct amount_msat known_min, known_max; + known_min = ce->half[dir].known_min; + known_max = ce->half[dir].known_max; + + ce->half[dir].known_min = x; + ce->half[dir].known_max = x; + + err = chan_extra_adjust_half(ce, !dir); + if (err != RENEPAY_NOERROR) + goto restore_and_fail; + return err; + +restore_and_fail: + // we fail, thus restore the original state + ce->half[dir].known_min = known_min; + ce->half[dir].known_max = known_max; + return err; +} + +enum renepay_errorcode +chan_extra_set_liquidity(struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd, + struct amount_msat x) +{ + assert(scidd); + assert(chan_extra_map); + struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scidd->scid); + if (!ce) + return RENEPAY_CHANNEL_NOT_FOUND; + + return chan_extra_set_liquidity_(ce, scidd->dir, x); +} + +/* Update the knowledge that this (channel,direction) has sent x msat.*/ +enum renepay_errorcode +chan_extra_sent_success(struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd, + struct amount_msat x) +{ + assert(scidd); + assert(chan_extra_map); + + struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scidd->scid); + if (!ce) + return RENEPAY_CHANNEL_NOT_FOUND; + + // if we sent amount x, it first means that all htlcs on this channel + // fit in the liquidity + enum renepay_errorcode err; + err = chan_extra_can_send(chan_extra_map, scidd); + if (err != RENEPAY_NOERROR) + return err; + + if (amount_msat_greater(x, ce->capacity)) + return RENEPAY_PRECONDITION_ERROR; + + // in case we fail, let's remember the original state + struct amount_msat known_min, known_max; + known_min = ce->half[scidd->dir].known_min; + known_max = ce->half[scidd->dir].known_max; + + struct amount_msat new_a, new_b; + + if (!amount_msat_sub(&new_a, ce->half[scidd->dir].known_min, x)) + new_a = AMOUNT_MSAT(0); + if (!amount_msat_sub(&new_b, ce->half[scidd->dir].known_max, x)) + new_b = AMOUNT_MSAT(0); + + ce->half[scidd->dir].known_min = new_a; + ce->half[scidd->dir].known_max = new_b; + + err = chan_extra_adjust_half(ce, !scidd->dir); + if (err != RENEPAY_NOERROR) + goto restore_and_fail; + + return err; + +// we fail, thus restore the original state +restore_and_fail: + ce->half[scidd->dir].known_min = known_min; + ce->half[scidd->dir].known_max = known_max; + return err; +} + +/* Forget a bit about this (channel,direction) state. */ +static enum renepay_errorcode chan_extra_relax(struct chan_extra *ce, int dir, + struct amount_msat down, + struct amount_msat up) +{ + assert(ce); + assert(dir == 0 || dir == 1); + struct amount_msat new_a, new_b; + enum renepay_errorcode err; + + if (!amount_msat_sub(&new_a, ce->half[dir].known_min, down)) + new_a = AMOUNT_MSAT(0); + if (!amount_msat_add(&new_b, ce->half[dir].known_max, up)) + new_b = ce->capacity; + new_b = amount_msat_min(new_b, ce->capacity); + + // in case we fail, let's remember the original state + struct amount_msat known_min, known_max; + known_min = ce->half[dir].known_min; + known_max = ce->half[dir].known_max; + + ce->half[dir].known_min = new_a; + ce->half[dir].known_max = new_b; + + err = chan_extra_adjust_half(ce, !dir); + if (err != RENEPAY_NOERROR) + goto restore_and_fail; + return err; + +// we fail, thus restore the original state +restore_and_fail: + ce->half[dir].known_min = known_min; + ce->half[dir].known_max = known_max; + return err; +} + +/* Forget the channel information by a fraction of the capacity. */ +enum renepay_errorcode chan_extra_relax_fraction(struct chan_extra *ce, + double fraction) +{ + assert(ce); + assert(fraction >= 0); + /* Allow to have values greater than 1 to indicate full relax. */ + // assert(fraction<=1); + fraction = fabs(fraction); // this number is always non-negative + fraction = MIN(1.0, fraction); // this number cannot be greater than 1. + struct amount_msat delta = + amount_msat(ce->capacity.millisatoshis * + fraction); /* Raw: get a fraction of the capacity */ + + /* The direction here is not important because the 'down' and the 'up' + * limits are changed by the same amount. + * Notice that if chan[0] with capacity C changes from (a,b) to + * (a-d,b+d) then its counterpart chan[1] changes from (C-b,C-a) to + * (C-b-d,C-a+d), hence both dirs are applied the same transformation. + */ + return chan_extra_relax(ce, /*dir=*/0, delta, delta); +} + +/* Returns either NULL, or an entry from the hash */ +struct chan_extra_half * +get_chan_extra_half_by_scid(struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd) +{ + assert(scidd); + assert(chan_extra_map); + struct chan_extra *ce; + + ce = chan_extra_map_get(chan_extra_map, scidd->scid); + if (!ce) + return NULL; + return &ce->half[scidd->dir]; +} +/* Helper if we have a gossmap_chan */ +struct chan_extra_half * +get_chan_extra_half_by_chan(const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + const struct gossmap_chan *chan, int dir) +{ + assert(chan); + assert(dir == 0 || dir == 1); + assert(gossmap); + assert(chan_extra_map); + struct short_channel_id_dir scidd; + + scidd.scid = gossmap_chan_scid(gossmap, chan); + scidd.dir = dir; + return get_chan_extra_half_by_scid(chan_extra_map, &scidd); +} + +// static void destroy_chan_extra(struct chan_extra *ce, +// struct chan_extra_map *chan_extra_map) +// { +// chan_extra_map_del(chan_extra_map, ce); +// } +/* Helper to get the chan_extra_half. If it doesn't exist create a new one. */ +struct chan_extra_half * +get_chan_extra_half_by_chan_verify(const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + const struct gossmap_chan *chan, int dir) +{ + assert(chan); + assert(dir == 0 || dir == 1); + assert(gossmap); + assert(chan_extra_map); + struct short_channel_id_dir scidd; + + scidd.scid = gossmap_chan_scid(gossmap, chan); + scidd.dir = dir; + struct chan_extra_half *h = + get_chan_extra_half_by_scid(chan_extra_map, &scidd); + if (!h) { + struct amount_sat cap; + struct amount_msat cap_msat; + + if (!gossmap_chan_get_capacity(gossmap, chan, &cap) || + !amount_sat_to_msat(&cap_msat, cap)) { + return NULL; + } + h = &new_chan_extra(chan_extra_map, scidd.scid, cap_msat) + ->half[scidd.dir]; + } + return h; +} + +/* Assuming a uniform distribution, what is the chance this f gets through? + * Here we compute the conditional probability of success for a flow f, given + * the knowledge that the liquidity is in the range [a,b) and some amount + * x is already committed on another part of the payment. + * + * The probability equation for x=0 is: + * + * prob(f) = + * + * for f=f>=a: (b-f)/(b-a) + * for b0 the prob. of success for passing x and f is: + * + * prob(f and x) = prob(x) * prob(f|x) + * + * and it can be shown to be equal to + * + * prob(f and x) = prob(f+x) + * + * The purpose of this function is to obtain prob(f|x), i.e. the probability of + * getting f through provided that we already succeeded in getting x. + * This conditional probability comes with 4 cases: + * + * prob(f|x) = + * + * for x=a-x: (b-x-f)/(b-a) + * for x>=a: (b-x-f)/(b-x) + * for f>b-x: 0. + * + * This is the same as the probability of success of f when the bounds are + * shifted by x amount, the new bounds be [MAX(0,a-x),b-x). + */ +double edge_probability(struct amount_msat min, struct amount_msat max, + struct amount_msat in_flight, struct amount_msat f) +{ + assert(amount_msat_less_eq(min, max)); + assert(amount_msat_less_eq(in_flight, max)); + + const struct amount_msat one = AMOUNT_MSAT(1); + struct amount_msat B = max; // = max +1 - in_flight + + // one past the last known value, makes computations simpler + if (!amount_msat_add(&B, B, one)) + goto function_fail; + + // in_flight cannot be greater than max + if (!amount_msat_sub(&B, B, in_flight)) + goto function_fail; + + struct amount_msat A = min; // = MAX(0,min-in_flight); + + if (!amount_msat_sub(&A, A, in_flight)) + A = AMOUNT_MSAT(0); + + struct amount_msat denominator; // = B-A + + // B cannot be smaller than or equal A + if (!amount_msat_sub(&denominator, B, A) || amount_msat_less_eq(B, A)) + goto function_fail; + + struct amount_msat numerator; // MAX(0,B-f) + + if (!amount_msat_sub(&numerator, B, f)) + numerator = AMOUNT_MSAT(0); + + return amount_msat_less_eq(f, A) + ? 1.0 + : amount_msat_ratio(numerator, denominator); + +function_fail: + return -1; +} + +enum renepay_errorcode +chan_extra_remove_htlc(struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd, + struct amount_msat amount) +{ + struct chan_extra_half *h = + get_chan_extra_half_by_scid(chan_extra_map, scidd); + if (!h) + return RENEPAY_CHANNEL_NOT_FOUND; + if (h->num_htlcs <= 0) + return RENEPAY_PRECONDITION_ERROR; + + if (!amount_msat_sub(&h->htlc_total, h->htlc_total, amount)) + return RENEPAY_AMOUNT_OVERFLOW; + h->num_htlcs--; + return RENEPAY_NOERROR; +} + +enum renepay_errorcode +chan_extra_commit_htlc(struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd, + struct amount_msat amount) +{ + struct chan_extra_half *h = + get_chan_extra_half_by_scid(chan_extra_map, scidd); + if (!h) + return RENEPAY_CHANNEL_NOT_FOUND; + if (!amount_msat_add(&h->htlc_total, h->htlc_total, amount)) + return RENEPAY_AMOUNT_OVERFLOW; + h->num_htlcs++; + return RENEPAY_NOERROR; +} diff --git a/plugins/renepay/chan_extra.h b/plugins/renepay/chan_extra.h new file mode 100644 index 000000000000..478e5904f3da --- /dev/null +++ b/plugins/renepay/chan_extra.h @@ -0,0 +1,239 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_CHAN_EXTRA_H +#define LIGHTNING_PLUGINS_RENEPAY_CHAN_EXTRA_H + +#include "config.h" +#include +#include +#include +#include +#include + +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) + +/* Any implementation needs to keep some data on channels which are + * in-use (or about which we have extra information). We use a hash + * table here, since most channels are not in use. */ +// TODO(eduardo): if we know the liquidity of channel (X,dir) is [A,B] +// then we also know that the liquidity of channel (X,!dir) is [Cap-B,Cap-A]. +// This means that it is redundant to store known_min and known_max for both +// halves of the channel and it also means that once we update the knowledge of +// (X,dir) the knowledge of (X,!dir) is updated as well. +struct chan_extra { + struct short_channel_id scid; + struct amount_msat capacity; + + struct chan_extra_half { + /* How many htlcs we've directed through it */ + size_t num_htlcs; + + /* The total size of those HTLCs */ + struct amount_msat htlc_total; + + /* The known minimum / maximum capacity (if nothing known, + * 0/capacity */ + struct amount_msat known_min, known_max; + } half[2]; +}; + +bool chan_extra_is_busy(const struct chan_extra *const ce); + +static inline const struct short_channel_id +chan_extra_scid(const struct chan_extra *cd) +{ + return cd->scid; +} + +static inline size_t hash_scid(const struct short_channel_id scid) +{ + /* scids cost money to generate, so simple hash works here */ + return (scid.u64 >> 32) ^ (scid.u64 >> 16) ^ scid.u64; +} + +static inline bool chan_extra_eq_scid(const struct chan_extra *cd, + const struct short_channel_id scid) +{ + return short_channel_id_eq(scid, cd->scid); +} + +HTABLE_DEFINE_TYPE(struct chan_extra, chan_extra_scid, hash_scid, + chan_extra_eq_scid, chan_extra_map); + +/* Helpers for chan_extra_map */ +/* Channel knowledge invariants: + * + * 0<=a<=b<=capacity + * + * a_inv = capacity-b + * b_inv = capacity-a + * + * where a,b are the known minimum and maximum liquidities, and a_inv and b_inv + * are the known minimum and maximum liquidities for the channel in the opposite + * direction. + * + * Knowledge update operations can be: + * + * 1. set liquidity (x) + * (a,b) -> (x,x) + * + * The entropy is minimum here (=0). + * + * 2. can send (x): + * xb = min(x,capacity) + * (a,b) -> (max(a,xb),max(b,xb)) + * + * If x<=a then there is no new knowledge and the entropy remains + * the same. + * If x>a the entropy decreases. + * + * + * 3. can't send (x): + * xb = max(0,x-1) + * (a,b) -> (min(a,xb),min(b,xb)) + * + * If x>b there is no new knowledge and the entropy remains. + * If x<=b then the entropy decreases. + * + * 4. sent success (x): + * (a,b) -> (max(0,a-x),max(0,b-x)) + * + * If x<=a there is no new knowledge and the entropy remains. + * If a (max(0,a-x),min(capacity,b+y)) + * + * Entropy increases unless it is already maximum. + * */ + +const char *fmt_chan_extra_map(const tal_t *ctx, + struct chan_extra_map *chan_extra_map); + +/* Returns "" if nothing useful known about channel, otherwise + * "(details)" */ +const char *fmt_chan_extra_details(const tal_t *ctx, + const struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd); + +/* Creates a new chan_extra and adds it to the chan_extra_map. */ +struct chan_extra *new_chan_extra(struct chan_extra_map *chan_extra_map, + const struct short_channel_id scid, + struct amount_msat capacity); + +/* Helper to find the min of two amounts */ +static inline struct amount_msat amount_msat_min(struct amount_msat a, + struct amount_msat b) +{ + return amount_msat_less(a, b) ? a : b; +} +/* Helper to find the max of two amounts */ +static inline struct amount_msat amount_msat_max(struct amount_msat a, + struct amount_msat b) +{ + return amount_msat_greater(a, b) ? a : b; +} + +/* Update the knowledge that this (channel,direction) can send x msat.*/ +enum renepay_errorcode +chan_extra_can_send(struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd); + +/* Update the knowledge that this (channel,direction) cannot send x msat.*/ +enum renepay_errorcode +chan_extra_cannot_send(struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd); + +enum renepay_errorcode +chan_extra_remove_htlc(struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd, + struct amount_msat amount); + +enum renepay_errorcode +chan_extra_commit_htlc(struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd, + struct amount_msat amount); + + +/* Update the knowledge that this (channel,direction) has liquidity x.*/ +enum renepay_errorcode +chan_extra_set_liquidity(struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd, + struct amount_msat x); + +/* Update the knowledge that this (channel,direction) has sent x msat.*/ +enum renepay_errorcode +chan_extra_sent_success(struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd, + struct amount_msat x); + +/* Forget the channel information by a fraction of the capacity. */ +enum renepay_errorcode chan_extra_relax_fraction(struct chan_extra *ce, + double fraction); + +/* Returns either NULL, or an entry from the hash */ +struct chan_extra_half * +get_chan_extra_half_by_scid(struct chan_extra_map *chan_extra_map, + const struct short_channel_id_dir *scidd); +/* If the channel is not registered, then a new entry is created. scid must be + * present in the gossmap. */ +struct chan_extra_half * +get_chan_extra_half_by_chan_verify(const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + const struct gossmap_chan *chan, int dir); + +/* Helper if we have a gossmap_chan */ +struct chan_extra_half * +get_chan_extra_half_by_chan(const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + const struct gossmap_chan *chan, int dir); + +/* Based on the knowledge that we have and HTLCs, returns the greatest + * amount that we can send through this channel. */ +enum renepay_errorcode channel_liquidity(struct amount_msat *liquidity, + const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + const struct gossmap_chan *chan, + const int dir); + +/* Helpers to get the htlc_max and htlc_min of a channel. */ +static inline struct amount_msat +channel_htlc_max(const struct gossmap_chan *chan, const int dir) +{ + return amount_msat(fp16_to_u64(chan->half[dir].htlc_max)); +} +static inline struct amount_msat +channel_htlc_min(const struct gossmap_chan *chan, const int dir) +{ + return amount_msat(fp16_to_u64(chan->half[dir].htlc_min)); +} + +/* inputs + * @chan: a channel + * @recv: how much can we send to this channels + * + * output + * @max_forward: how much can we ask this channel to forward to the next hop + * */ +enum renepay_errorcode channel_maximum_forward(struct amount_msat *max_forward, + const struct gossmap_chan *chan, + const int dir, + struct amount_msat recv); + +/* Assume a uniform distribution: + * @min, @max: the bounds of liquidity + * @in_flight: htlcs + * + * @f: the amount we want to forward + * + * returns the probability that this forward request gets through. + * */ +double edge_probability(struct amount_msat min, struct amount_msat max, + struct amount_msat in_flight, struct amount_msat f); + +/* Checks BOLT 7 HTLC fee condition: + * recv >= base_fee + (send*proportional_fee)/1000000 */ +bool check_fee_inequality(struct amount_msat recv, struct amount_msat send, + u64 base_fee, u64 proportional_fee); + +#endif /* LIGHTNING_PLUGINS_RENEPAY_CHAN_EXTRA_H */ diff --git a/plugins/renepay/flow.c b/plugins/renepay/flow.c index a608559e1475..757c07af2446 100644 --- a/plugins/renepay/flow.c +++ b/plugins/renepay/flow.c @@ -14,1240 +14,160 @@ #define SUPERVERBOSE_ENABLED 1 #endif -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) -#define MIN(x, y) (((x) < (y)) ? (x) : (y)) - -static char *chan_extra_not_found_error(const tal_t *ctx, - const struct short_channel_id *scid) -{ - return tal_fmt(ctx, - "chan_extra for scid=%s not found in chan_extra_map", - fmt_short_channel_id(ctx, *scid)); -} - -bool chan_extra_is_busy(const struct chan_extra *const ce) -{ - if(ce==NULL)return false; - return ce->half[0].num_htlcs || ce->half[1].num_htlcs; -} - -const char *fmt_chan_extra_map(const tal_t *ctx, - struct chan_extra_map *chan_extra_map) -{ - tal_t *this_ctx = tal(ctx,tal_t); - char *buff = tal_fmt(ctx,"Uncertainty network:\n"); - struct chan_extra_map_iter it; - for(struct chan_extra *ch = chan_extra_map_first(chan_extra_map,&it); - ch; - ch=chan_extra_map_next(chan_extra_map,&it)) - { - const char *scid_str = - fmt_short_channel_id(this_ctx, ch->scid); - for(int dir=0;dir<2;++dir) - { - tal_append_fmt(&buff,"%s[%d]:(%s,%s)\n",scid_str,dir, - fmt_amount_msat(this_ctx, ch->half[dir].known_min), - fmt_amount_msat(this_ctx, ch->half[dir].known_max)); - } - } - tal_free(this_ctx); - return buff; -} - -const char *fmt_chan_extra_details(const tal_t *ctx, - const struct chan_extra_map* chan_extra_map, - const struct short_channel_id_dir *scidd) -{ - const tal_t *this_ctx = tal(ctx,tal_t); - const struct chan_extra *ce = chan_extra_map_get(chan_extra_map, - scidd->scid); - const struct chan_extra_half *ch; - char *str = tal_strdup(ctx, ""); - char sep = '('; - - if (!ce) { - // we have no information on this channel - tal_append_fmt(&str, "()"); - goto finished; - } - - ch = &ce->half[scidd->dir]; - if (ch->num_htlcs != 0) { - tal_append_fmt(&str, "%c%s in %zu htlcs", - sep, - fmt_amount_msat(this_ctx, ch->htlc_total), - ch->num_htlcs); - sep = ','; - } - /* Happens with local channels, where we're certain. */ - if (amount_msat_eq(ch->known_min, ch->known_max)) { - tal_append_fmt(&str, "%cmin=max=%s", - sep, - fmt_amount_msat(this_ctx, ch->known_min)); - sep = ','; - } else { - if (amount_msat_greater(ch->known_min, AMOUNT_MSAT(0))) { - tal_append_fmt(&str, "%cmin=%s", - sep, - fmt_amount_msat(this_ctx, ch->known_min)); - sep = ','; - } - if (!amount_msat_eq(ch->known_max, ce->capacity)) { - tal_append_fmt(&str, "%cmax=%s", - sep, - fmt_amount_msat(this_ctx, ch->known_max)); - sep = ','; - } - } - if (!streq(str, "")) - tal_append_fmt(&str, ")"); - - finished: - tal_free(this_ctx); - return str; -} - -struct chan_extra *new_chan_extra(struct chan_extra_map *chan_extra_map, - const struct short_channel_id scid, - struct amount_msat capacity) -{ - assert(chan_extra_map); - struct chan_extra *ce = tal(chan_extra_map, struct chan_extra); - if (!ce) - return ce; - - ce->scid = scid; - ce->capacity=capacity; - for (size_t i = 0; i <= 1; i++) { - ce->half[i].num_htlcs = 0; - ce->half[i].htlc_total = AMOUNT_MSAT(0); - ce->half[i].known_min = AMOUNT_MSAT(0); - ce->half[i].known_max = capacity; - } - if (!chan_extra_map_add(chan_extra_map, ce)) { - return tal_free(ce); - } - - /* Remove self from map when done */ - // TODO(eduardo): - // Is this desctructor really necessary? the chan_extra will deallocated - // when the chan_extra_map is freed. Anyways valgrind complains that the - // hash table is removing the element with a freed pointer. - // tal_add_destructor2(ce, destroy_chan_extra, chan_extra_map); - return ce; -} - -static struct amount_msat channel_max_htlc(const struct gossmap_chan *chan, - const int dir) -{ - return amount_msat(fp16_to_u64(chan->half[dir].htlc_max)); -} - -/* Based on the knowledge that we have and HTLCs, returns the greatest - * amount that we can send through this channel. */ -static bool channel_liquidity(struct amount_msat *liquidity, - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - const struct gossmap_chan *chan, const int dir) -{ - const struct chan_extra_half *h = - get_chan_extra_half_by_chan(gossmap, chan_extra_map, chan, dir); - if (!h) - return false; - struct amount_msat value_liquidity = h->known_max; - if (!amount_msat_sub(&value_liquidity, value_liquidity, h->htlc_total)) - return false; - *liquidity = value_liquidity; - return true; -} - -/* Checks BOLT 7 HTLC fee condition: - * recv >= base_fee + (send*proportional_fee)/1000000 */ -static bool check_fee_inequality(struct amount_msat recv, - struct amount_msat send, u64 base_fee, - u64 proportional_fee) -{ - // nothing to forward, any incoming amount is good - if (amount_msat_zero(send)) - return true; - // FIXME If this addition fails we return false. The caller will not be - // able to know that there was an addition overflow, he will just assume - // that the fee inequality was not satisfied. - if (!amount_msat_add_fee(&send, base_fee, proportional_fee)) - return false; - return amount_msat_greater_eq(recv, send); -} - -/* Let `recv` be the maximum amount this channel can receive, this function - * computes the maximum amount this channel can forward `send`. - * From BOLT7 specification wee need to satisfy the following inequality: - * - * recv-send >= base_fee + floor(send*proportional_fee/1000000) - * - * That is equivalent to have - * - * send <= Bound(recv,send) - * - * where - * - * Bound(recv, send) = ((recv - base_fee)*1000000 + (send*proportional_fee) - *% 1000000)/(proportional_fee+1000000) - * - * However the quantity we want to determine, `send`, appears on both sides of - * the equation. However the term `send*proportional_fee) % 1000000` only - * contributes by increasing the bound by at most one so that we can neglect - * the extra term and use instead - * - * Bound_simple(recv) = ((recv - - *base_fee)*1000000)/(proportional_fee+1000000) - * - * as the upper bound for `send`. Formally one can check that - * - * Bound_simple(recv) <= Bound(recv, send) < Bound_simple(recv) + 2 - * - * So that if one wishes to find the very highest value of `send` that - * satisfies - * - * send <= Bound(recv, send) - * - * it is enough to compute - * - * send = Bound_simple(recv) - * - * which already satisfies the fee equation and then try to go higher - * with send+1, send+2, etc. But we know that it is enough to try up to - * send+1 because Bound(recv, send) < Bound_simple(recv) + 2. - * */ -static bool channel_maximum_forward(struct amount_msat *max_forward, - const struct gossmap_chan *chan, - const int dir, struct amount_msat recv) -{ - const u64 b = chan->half[dir].base_fee, - p = chan->half[dir].proportional_fee; - - const u64 one_million = 1000000; - u64 x_msat = - recv.millisatoshis; /* Raw: need to invert the fee equation */ - - // special case, when recv - base_fee <= 0, we cannot forward anything - if (x_msat <= b) { - *max_forward = amount_msat(0); - return true; - } - - x_msat -= b; - - if (mul_overflows_u64(one_million, x_msat)) - return false; - - struct amount_msat best_send = - AMOUNT_MSAT_INIT((one_million * x_msat) / (one_million + p)); - - /* Try to increase the value we send (up tp the last millisat) until we - * fail to fulfill the fee inequality. It takes only one iteration - * though. */ - for (size_t i = 0; i < 10; ++i) { - struct amount_msat next_send; - if (!amount_msat_add(&next_send, best_send, amount_msat(1))) - return false; - - if (check_fee_inequality(recv, next_send, b, p)) - best_send = next_send; - else - break; - } - *max_forward = best_send; - return true; -} - -/* Returns the greatest amount we can deliver to the destination using this - * route. It takes into account the current knowledge, pending HTLC, - * htlc_max and fees. */ -static bool flow_maximum_deliverable(struct amount_msat *max_deliverable, - const struct flow *flow, - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map) -{ - assert(tal_count(flow->path) > 0); - assert(tal_count(flow->dirs) > 0); - assert(tal_count(flow->path) == tal_count(flow->dirs)); - struct amount_msat x; - - if (!channel_liquidity(&x, gossmap, chan_extra_map, flow->path[0], - flow->dirs[0])) - return false; - x = amount_msat_min(x, channel_max_htlc(flow->path[0], flow->dirs[0])); - - for (size_t i = 1; i < tal_count(flow->path); ++i) { - // ith node can forward up to 'liquidity_cap' because of the ith - // channel liquidity bound - struct amount_msat liquidity_cap; - - if (!channel_liquidity(&liquidity_cap, gossmap, chan_extra_map, - flow->path[i], flow->dirs[i])) - return false; - - /* ith node can receive up to 'x', therefore he will not forward - * more than 'forward_cap' that we compute below inverting the - * fee equation. */ - struct amount_msat forward_cap; - if (!channel_maximum_forward(&forward_cap, flow->path[i], - flow->dirs[i], x)) - return false; - - struct amount_msat x_new = - amount_msat_min(forward_cap, liquidity_cap); - x_new = amount_msat_min( - x_new, channel_max_htlc(flow->path[i], flow->dirs[i])); - - if (!amount_msat_less_eq(x_new, x)) - return false; - - // safety check: amounts decrease along the route - assert(amount_msat_less_eq(x_new, x)); - - struct amount_msat x_check = x_new; - - if (!amount_msat_zero(x_new) && - !amount_msat_add_fee(&x_check, flow_edge(flow, i)->base_fee, - flow_edge(flow, i)->proportional_fee)) - return false; - - // safety check: the max liquidity in the next hop + fees cannot - // be greater than then max liquidity in the current hop, IF the - // next hop is non-zero. - assert(amount_msat_less_eq(x_check, x)); - - x = x_new; - } - *max_deliverable = x; - return true; -} - -/* How much do we deliver to destination using this set of routes */ -static bool flow_set_delivers(struct amount_msat *delivers, struct flow **flows) -{ - struct amount_msat final = AMOUNT_MSAT(0); - for (size_t i = 0; i < tal_count(flows); i++) { - size_t n = tal_count(flows[i]->amounts); - struct amount_msat this_final = flows[i]->amounts[n - 1]; - - if (!amount_msat_add(&final, this_final, final)) - return false; - } - *delivers = final; - return true; -} - -/* How much this flow (route with amounts) is delivering to the destination - * node. */ -static inline struct amount_msat flow_delivers(const struct flow *flow) -{ - return flow->amounts[tal_count(flow->amounts) - 1]; -} - -/* Checks if the flows satisfy the liquidity bounds imposed by the known maximum - * liquidity and pending HTLCs. - * - * FIXME The function returns false even in the case of failure. The caller has - * no way of knowing the difference between a failure of evaluation and a - * negative answer. */ -static bool check_liquidity_bounds(struct flow **flows, - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map) -{ - bool check = true; - for (size_t i = 0; i < tal_count(flows); ++i) { - struct amount_msat max_deliverable; - if (!flow_maximum_deliverable(&max_deliverable, flows[i], - gossmap, chan_extra_map)) - return false; - struct amount_msat delivers = flow_delivers(flows[i]); - check &= amount_msat_less_eq(delivers, max_deliverable); - } - return check; -} - -/* flows should be a set of optimal routes delivering an amount that is - * slighty less than amount_to_deliver. We will try to reallocate amounts in - * these flows so that it delivers the exact amount_to_deliver to the - * destination. - * Returns how much we are delivering at the end. */ -bool flows_fit_amount(const tal_t *ctx, struct amount_msat *amount_allocated, - struct flow **flows, struct amount_msat amount_to_deliver, - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, char **fail) -{ - tal_t *this_ctx = tal(ctx, tal_t); - char *errmsg; - - struct amount_msat total_deliver; - if (!flow_set_delivers(&total_deliver, flows)) { - if (fail) - *fail = tal_fmt( - ctx, "(%s, line %d) flow_set_delivers failed", - __PRETTY_FUNCTION__, __LINE__); - goto function_fail; - } - if (amount_msat_greater_eq(total_deliver, amount_to_deliver)) { - *amount_allocated = total_deliver; - goto function_success; - } - - struct amount_msat deficit; - if (!amount_msat_sub(&deficit, amount_to_deliver, total_deliver)) { - // this should not happen, because we already checked that - // total_delivercapacity, - ce->half[!dir].known_min)) { - if(fail) - *fail = tal_fmt( - ctx, "cannot substract capacity=%s and known_min=%s", - fmt_amount_msat(ctx, ce->capacity), - fmt_amount_msat(ctx, ce->half[!dir].known_min)); - goto function_fail; - } - if (!amount_msat_sub(&new_known_min, ce->capacity, - ce->half[!dir].known_max)) { - if(fail) - *fail = tal_fmt( - ctx, "cannot substract capacity=%s and known_max=%s", - fmt_amount_msat(ctx, ce->capacity), - fmt_amount_msat(ctx, ce->half[!dir].known_max)); - goto function_fail; - } - - ce->half[dir].known_max = new_known_max; - ce->half[dir].known_min = new_known_min; - return true; - - function_fail: - return false; -} - -/* Update the knowledge that this (channel,direction) can send x msat.*/ -static bool chan_extra_can_send_(const tal_t *ctx, struct chan_extra *ce, - int dir, struct amount_msat x, char **fail) -{ - assert(ce); - assert(dir==0 || dir==1); - const tal_t *this_ctx = tal(ctx,tal_t); - char *errmsg; - if (amount_msat_greater(x, ce->capacity)) { - if(fail) - *fail = tal_fmt( - ctx, - "can send amount (%s) is larger than the " - "channel's capacity (%s)", - fmt_amount_msat(ctx, x), - fmt_amount_msat(ctx, ce->capacity)); - goto function_fail; - } - - struct amount_msat known_min, known_max; - - // in case we fail, let's remember the original state - known_min = ce->half[dir].known_min; - known_max = ce->half[dir].known_max; - - ce->half[dir].known_min = amount_msat_max(ce->half[dir].known_min, x); - ce->half[dir].known_max = amount_msat_max(ce->half[dir].known_max, x); - - if (!chan_extra_adjust_half(this_ctx, ce, !dir, &errmsg)) { - if(fail) - *fail = tal_fmt(ctx, "chan_extra_adjust_half failed: %s", - errmsg); - - goto restore_and_fail; - } - return true; - - restore_and_fail: - // we fail, thus restore the original state - ce->half[dir].known_min = known_min; - ce->half[dir].known_max = known_max; - - function_fail: - return false; -} - -bool chan_extra_can_send(const tal_t *ctx, - struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - char **fail) -{ - assert(scidd); - assert(chan_extra_map); - struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scidd->scid); - if (!ce) { - if(fail) - *fail = chan_extra_not_found_error(ctx, &scidd->scid); - goto function_fail; - } - if (!chan_extra_can_send_(ctx, ce, scidd->dir, - ce->half[scidd->dir].htlc_total, fail)) { - goto function_fail; - } - return true; - - function_fail: - return false; -} - -/* Update the knowledge that this (channel,direction) cannot send.*/ -bool chan_extra_cannot_send(const tal_t *ctx, - struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - char **fail) -{ - assert(scidd); - assert(chan_extra_map); - const tal_t *this_ctx = tal(ctx,tal_t); - char *errmsg; - struct amount_msat x; - struct chan_extra *ce = chan_extra_map_get(chan_extra_map, - scidd->scid); - if(!ce) - { - if(fail) - *fail = chan_extra_not_found_error(ctx, &scidd->scid); - goto function_fail; - } - - /* Note: sent is already included in htlc_total! */ - if (!amount_msat_sub(&x, ce->half[scidd->dir].htlc_total, - AMOUNT_MSAT(1))) { - if(fail) - *fail = tal_fmt( - ctx, "htlc_total=%s is less than 0msats in channel %s", - fmt_amount_msat(this_ctx, ce->half[scidd->dir].htlc_total), - fmt_short_channel_id(this_ctx, scidd->scid)); - goto function_fail; - } - - struct amount_msat known_min, known_max; - // in case we fail, let's remember the original state - known_min = ce->half[scidd->dir].known_min; - known_max = ce->half[scidd->dir].known_max; - - /* If we "knew" the capacity was at least this, we just showed we're wrong! */ - if (amount_msat_less(x, ce->half[scidd->dir].known_min)) { - /* Skip to half of x, since we don't know (rounds down) */ - ce->half[scidd->dir].known_min = amount_msat_div(x, 2); - } - - ce->half[scidd->dir].known_max = amount_msat_min(ce->half[scidd->dir].known_max,x); - - if(!chan_extra_adjust_half(this_ctx, ce,!scidd->dir,&errmsg)) - { - if(fail) - *fail = tal_fmt(ctx, "chan_extra_adjust_half failed: %s", - errmsg); - goto restore_and_fail; - } - tal_free(this_ctx); - return true; - - restore_and_fail: - // we fail, thus restore the original state - ce->half[scidd->dir].known_min = known_min; - ce->half[scidd->dir].known_max = known_max; - - function_fail: - tal_free(this_ctx); - return false; -} -/* Update the knowledge that this (channel,direction) has liquidity x.*/ -static bool chan_extra_set_liquidity_(const tal_t *ctx, struct chan_extra *ce, - int dir, struct amount_msat x, - char **fail) -{ - assert(ce); - assert(dir==0 || dir==1); - const tal_t *this_ctx = tal(ctx,tal_t); - char *errmsg; - if (amount_msat_greater(x, ce->capacity)) { - if(fail) - *fail = tal_fmt( - ctx, - "tried to set liquidity (%s) to a value greater than " - "channel's capacity (%s)", - fmt_amount_msat(this_ctx, x), - fmt_amount_msat(this_ctx, ce->capacity)); - goto function_fail; - } - - // in case we fail, let's remember the original state - struct amount_msat known_min, known_max; - known_min = ce->half[dir].known_min; - known_max = ce->half[dir].known_max; - - ce->half[dir].known_min = x; - ce->half[dir].known_max = x; - - if (!chan_extra_adjust_half(this_ctx, ce, !dir, &errmsg)) { - if(fail) - *fail = tal_fmt(ctx, "chan_extra_adjust_half failed: %s", - errmsg); - goto restore_and_fail; - } - tal_free(this_ctx); - return true; - - restore_and_fail: - // we fail, thus restore the original state - ce->half[dir].known_min = known_min; - ce->half[dir].known_max = known_max; - - function_fail: - tal_free(this_ctx); - return false; -} -bool chan_extra_set_liquidity(const tal_t *ctx, - struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - struct amount_msat x, char **fail) -{ - assert(scidd); - assert(chan_extra_map); - struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scidd->scid); - if (!ce) { - if(fail) - *fail = chan_extra_not_found_error(ctx, &scidd->scid); - goto function_fail; - } - if (!chan_extra_set_liquidity_(ctx, ce, scidd->dir, x, fail)) { - goto function_fail; - } - return true; - - function_fail: - return false; -} -/* Update the knowledge that this (channel,direction) has sent x msat.*/ -bool chan_extra_sent_success(const tal_t *ctx, - struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - struct amount_msat x, char **fail) -{ - assert(scidd); - assert(chan_extra_map); - tal_t *this_ctx = tal(ctx, tal_t); - char *errmsg; - - // if we sent amount x, it first means that all htlcs on this channel fit - // in the liquidity - if (!chan_extra_can_send(this_ctx, chan_extra_map, scidd, &errmsg)) { - if (fail) - *fail = tal_fmt(ctx, "chan_extra_can_send failed: %s", - errmsg); - goto function_fail; - } - - struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scidd->scid); - if (!ce) { - if(fail) - *fail = chan_extra_not_found_error(ctx, &scidd->scid); - goto function_fail; - } - - if (amount_msat_greater(x, ce->capacity)) { - if(fail) - *fail = tal_fmt( - ctx, - "sent success (%s) is larger than the " - "channel's capacity (%s)", - fmt_amount_msat(this_ctx, x), - fmt_amount_msat(this_ctx, ce->capacity)); - goto function_fail; - } - - // in case we fail, let's remember the original state - struct amount_msat known_min, known_max; - known_min = ce->half[scidd->dir].known_min; - known_max = ce->half[scidd->dir].known_max; - - struct amount_msat new_a, new_b; - - if (!amount_msat_sub(&new_a, ce->half[scidd->dir].known_min, x)) - new_a = AMOUNT_MSAT(0); - if (!amount_msat_sub(&new_b, ce->half[scidd->dir].known_max, x)) - new_b = AMOUNT_MSAT(0); - - ce->half[scidd->dir].known_min = new_a; - ce->half[scidd->dir].known_max = new_b; - - if (!chan_extra_adjust_half(this_ctx, ce, !scidd->dir, &errmsg)) { - if(fail) - *fail = - tal_fmt(ctx, "chan_extra_adjust_half failed: %s", errmsg); - goto restore_and_fail; - } - tal_free(this_ctx); - return true; - - // we fail, thus restore the original state - restore_and_fail: - ce->half[scidd->dir].known_min = known_min; - ce->half[scidd->dir].known_max = known_max; - - function_fail: - tal_free(this_ctx); - return false; -} -/* Forget a bit about this (channel,direction) state. */ -static bool chan_extra_relax(const tal_t *ctx, struct chan_extra *ce, int dir, - struct amount_msat down, struct amount_msat up, - char **fail) -{ - assert(ce); - assert(dir==0 || dir==1); - const tal_t *this_ctx = tal(ctx,tal_t); - char *errmsg; - struct amount_msat new_a, new_b; - - if (!amount_msat_sub(&new_a, ce->half[dir].known_min, down)) - new_a = AMOUNT_MSAT(0); - if (!amount_msat_add(&new_b, ce->half[dir].known_max, up)) - new_b = ce->capacity; - new_b = amount_msat_min(new_b, ce->capacity); - - // in case we fail, let's remember the original state - struct amount_msat known_min, known_max; - known_min = ce->half[dir].known_min; - known_max = ce->half[dir].known_max; - - ce->half[dir].known_min = new_a; - ce->half[dir].known_max = new_b; - - if (!chan_extra_adjust_half(this_ctx,ce, !dir, &errmsg)) { - if(fail) - *fail = tal_fmt(ctx, "chan_extra_adjust_half failed: %s", - errmsg); - goto restore_and_fail; - } - tal_free(this_ctx); - return true; - - // we fail, thus restore the original state - restore_and_fail: - ce->half[dir].known_min = known_min; - ce->half[dir].known_max = known_max; - - tal_free(this_ctx); - return false; -} - -/* Forget the channel information by a fraction of the capacity. */ -bool chan_extra_relax_fraction(const tal_t *ctx, struct chan_extra *ce, - double fraction, char **fail) +struct amount_msat *tal_flow_amounts(const tal_t *ctx, const struct flow *flow) { - assert(ce); - assert(fraction>=0); - /* Allow to have values greater than 1 to indicate full relax. */ - // assert(fraction<=1); - const tal_t *this_ctx = tal(ctx,tal_t); - char *errmsg; - fraction = fabs(fraction); // this number is always non-negative - fraction = MIN(1.0, fraction); // this number cannot be greater than 1. - struct amount_msat delta = - amount_msat(ce->capacity.millisatoshis * fraction); /* Raw: get a fraction of the capacity */ + const size_t pathlen = tal_count(flow->path); + struct amount_msat *amounts = tal_arr(ctx, struct amount_msat, pathlen); + amounts[pathlen - 1] = flow->amount; - /* The direction here is not important because the 'down' and the 'up' - * limits are changed by the same amount. - * Notice that if chan[0] with capacity C changes from (a,b) to - * (a-d,b+d) then its counterpart chan[1] changes from (C-b,C-a) to - * (C-b-d,C-a+d), hence both dirs are applied the same transformation. - */ - if (!chan_extra_relax(this_ctx, ce, /*dir=*/0, delta, delta, &errmsg)) { - if(fail) - *fail = tal_fmt(ctx, "chan_extra_relax failed: %s", errmsg); - goto function_fail; + for (int i = (int)pathlen - 2; i >= 0; i--) { + const struct half_chan *h = flow_edge(flow, i + 1); + amounts[i] = amounts[i + 1]; + if (!amount_msat_add_fee(&amounts[i], h->base_fee, + h->proportional_fee)) + goto function_fail; } - tal_free(this_ctx); - return true; - - function_fail: - tal_free(this_ctx); - return false; -} - -/* Returns either NULL, or an entry from the hash */ -struct chan_extra_half * -get_chan_extra_half_by_scid(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd) -{ - assert(scidd); - assert(chan_extra_map); - struct chan_extra *ce; - - ce = chan_extra_map_get(chan_extra_map, scidd->scid); - if (!ce) - return NULL; - return &ce->half[scidd->dir]; -} -/* Helper if we have a gossmap_chan */ -struct chan_extra_half * -get_chan_extra_half_by_chan(const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - const struct gossmap_chan *chan, - int dir) -{ - assert(chan); - assert(dir==0 || dir==1); - assert(gossmap); - assert(chan_extra_map); - struct short_channel_id_dir scidd; - - scidd.scid = gossmap_chan_scid(gossmap, chan); - scidd.dir = dir; - return get_chan_extra_half_by_scid(chan_extra_map, &scidd); -} + return amounts; -// static void destroy_chan_extra(struct chan_extra *ce, -// struct chan_extra_map *chan_extra_map) -// { -// chan_extra_map_del(chan_extra_map, ce); -// } -/* Helper to get the chan_extra_half. If it doesn't exist create a new one. */ -struct chan_extra_half * -get_chan_extra_half_by_chan_verify(const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - const struct gossmap_chan *chan, int dir) -{ - assert(chan); - assert(dir==0 || dir==1); - assert(gossmap); - assert(chan_extra_map); - struct short_channel_id_dir scidd; - - scidd.scid = gossmap_chan_scid(gossmap, chan); - scidd.dir = dir; - struct chan_extra_half *h = - get_chan_extra_half_by_scid(chan_extra_map, &scidd); - if (!h) { - struct amount_sat cap; - struct amount_msat cap_msat; - - if (!gossmap_chan_get_capacity(gossmap, chan, &cap) || - !amount_sat_to_msat(&cap_msat, cap)) { - return NULL; - } - h = &new_chan_extra(chan_extra_map, scidd.scid, cap_msat) - ->half[scidd.dir]; - } - return h; +function_fail: + return tal_free(amounts); } -/* Assuming a uniform distribution, what is the chance this f gets through? - * Here we compute the conditional probability of success for a flow f, given - * the knowledge that the liquidity is in the range [a,b) and some amount - * x is already committed on another part of the payment. - * - * The probability equation for x=0 is: - * - * prob(f) = - * - * for f=f>=a: (b-f)/(b-a) - * for b0 the prob. of success for passing x and f is: - * - * prob(f and x) = prob(x) * prob(f|x) - * - * and it can be shown to be equal to - * - * prob(f and x) = prob(f+x) - * - * The purpose of this function is to obtain prob(f|x), i.e. the probability of - * getting f through provided that we already succeeded in getting x. - * This conditional probability comes with 4 cases: - * - * prob(f|x) = - * - * for x=a-x: (b-x-f)/(b-a) - * for x>=a: (b-x-f)/(b-x) - * for f>b-x: 0. - * - * This is the same as the probability of success of f when the bounds are - * shifted by x amount, the new bounds be [MAX(0,a-x),b-x). - */ -static double edge_probability(const tal_t *ctx, struct amount_msat min, - struct amount_msat max, - struct amount_msat in_flight, - struct amount_msat f, char **fail) +/* Returns the greatest amount we can deliver to the destination using this + * route. It takes into account the current knowledge, pending HTLC, + * htlc_max and fees. + * + * It fails if the maximum that we can + * deliver at node i is smaller than the minimum required to forward the least + * amount greater than zero to the next node. */ +enum renepay_errorcode +flow_maximum_deliverable(struct amount_msat *max_deliverable, + const struct flow *flow, + const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + const struct gossmap_chan **bad_channel) { - assert(amount_msat_less_eq(min,max)); - assert(amount_msat_less_eq(in_flight,max)); - - const tal_t *this_ctx = tal(ctx, tal_t); - - const struct amount_msat one = AMOUNT_MSAT(1); - struct amount_msat B=max; // = max +1 - in_flight + assert(tal_count(flow->path) > 0); + assert(tal_count(flow->dirs) > 0); + assert(tal_count(flow->path) == tal_count(flow->dirs)); + struct amount_msat x; + enum renepay_errorcode err; - // one past the last known value, makes computations simpler - if(!amount_msat_add(&B,B,one)) - { - if(fail) - *fail = tal_fmt(ctx,"addition overflow"); - goto function_fail; + err = channel_liquidity(&x, gossmap, chan_extra_map, flow->path[0], + flow->dirs[0]); + if(err){ + if(bad_channel)*bad_channel = flow->path[0]; + return err; } - // in_flight cannot be greater than max - if(!amount_msat_sub(&B,B,in_flight)) - { - if(fail) - *fail = tal_fmt(ctx, - "in_flight=%s cannot be greater than known_max+1=%s", - fmt_amount_msat(this_ctx, in_flight), - fmt_amount_msat(this_ctx, B) - ); - goto function_fail; - } - struct amount_msat A=min; // = MAX(0,min-in_flight); - - if(!amount_msat_sub(&A,A,in_flight)) - A = AMOUNT_MSAT(0); + x = amount_msat_min(x, channel_htlc_max(flow->path[0], flow->dirs[0])); - struct amount_msat denominator; // = B-A - - // B cannot be smaller than or equal A - if(!amount_msat_sub(&denominator,B,A) || amount_msat_less_eq(B,A)) + if(amount_msat_zero(x)) { - if(fail) - *fail = tal_fmt(ctx,"known_max+1=%s must be greater than known_min=%s", - fmt_amount_msat(this_ctx, B), - fmt_amount_msat(this_ctx, A)); - goto function_fail; + if(bad_channel)*bad_channel = flow->path[0]; + return RENEPAY_BAD_CHANNEL; } - struct amount_msat numerator; // MAX(0,B-f) - - if(!amount_msat_sub(&numerator,B,f)) - numerator = AMOUNT_MSAT(0); - - tal_free(this_ctx); - return amount_msat_less_eq(f,A) ? 1.0 : amount_msat_ratio(numerator,denominator); - - function_fail: - tal_free(this_ctx); - return -1; -} + for (size_t i = 1; i < tal_count(flow->path); ++i) { + // ith node can forward up to 'liquidity_cap' because of the ith + // channel liquidity bound + struct amount_msat liquidity_cap; -// TODO(eduardo): remove this function, is a duplicate -/* If this function fails it means there is a bad data inconsistency and the - * program should stop. */ -bool remove_completed_flow(const tal_t *ctx, const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - struct flow *flow, char **fail) -{ - assert(flow); - assert(gossmap); - assert(chan_extra_map); - tal_t *this_ctx = tal(ctx, tal_t); - for (size_t i = 0; i < tal_count(flow->path); i++) { - struct chan_extra_half *h = get_chan_extra_half_by_chan(gossmap, - chan_extra_map, - flow->path[i], - flow->dirs[i]); - if (!amount_msat_sub(&h->htlc_total, h->htlc_total, flow->amounts[i])) - { - if(fail) - *fail = - tal_fmt(ctx, - "could not substract HTLC amounts, " - "total htlc amount = %s, " - "flow->amounts[%zu] = %s.", - fmt_amount_msat(this_ctx, h->htlc_total), - i, - fmt_amount_msat(this_ctx, flow->amounts[i])); - goto function_fail; + err = channel_liquidity(&liquidity_cap, gossmap, chan_extra_map, + flow->path[i], flow->dirs[i]); + if(err) { + if(bad_channel)*bad_channel = flow->path[i]; + return err; } - if (h->num_htlcs == 0) + + /* ith node can receive up to 'x', therefore he will not forward + * more than 'forward_cap' that we compute below inverting the + * fee equation. */ + struct amount_msat forward_cap; + err = channel_maximum_forward(&forward_cap, flow->path[i], + flow->dirs[i], x); + if(err) { - if(fail) - *fail = - tal_fmt(ctx, "could not decrease HTLC count."); - goto function_fail; + if(bad_channel)*bad_channel = flow->path[i]; + return err; } - h->num_htlcs--; - } - tal_free(this_ctx); - return true; + struct amount_msat x_new = + amount_msat_min(forward_cap, liquidity_cap); + x_new = amount_msat_min( + x_new, channel_htlc_max(flow->path[i], flow->dirs[i])); - function_fail: - tal_free(this_ctx); - return false; -} -// TODO(eduardo): remove this function, is a duplicate -/* If this function fails it means there is a bad data inconsistency and the - * program should stop. */ -bool remove_completed_flowset(const tal_t *ctx, const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - struct flow **flows, char **fail) -{ - assert(flows); - assert(gossmap); - assert(chan_extra_map); - for (size_t i = 0; i < tal_count(flows); ++i) { - if (!remove_completed_flow(ctx, gossmap, chan_extra_map, flows[i], - fail)) { - return false; - } - } - return true; -} + /* safety check: amounts decrease along the route */ + assert(amount_msat_less_eq(x_new, x)); -// TODO(eduardo): remove this function, is a duplicate -bool commit_flow(const tal_t *ctx, const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, struct flow *flow, - char **fail) -{ - assert(flow); - assert(gossmap); - assert(chan_extra_map); - tal_t *this_ctx = tal(ctx, tal_t); - for (size_t i = 0; i < tal_count(flow->path); i++) { - struct chan_extra_half *h = get_chan_extra_half_by_chan(gossmap, - chan_extra_map, - flow->path[i], - flow->dirs[i]); - if (!amount_msat_add(&h->htlc_total, h->htlc_total, flow->amounts[i])) + if(amount_msat_zero(x_new)) { - if (fail) - *fail = - tal_fmt(ctx, - "could not add HTLC amounts, " - "flow->amounts[%zu] = %s.", - i, - fmt_amount_msat(this_ctx, flow->amounts[i])); - goto function_fail; + if(bad_channel)*bad_channel = flow->path[i]; + return RENEPAY_BAD_CHANNEL; } - h->num_htlcs++; - } - tal_free(this_ctx); - return true; - function_fail: - tal_free(this_ctx); - return false; -} -// TODO(eduardo): remove this function, is a duplicate -/* Returns the number of flows successfully commited. */ -size_t commit_flowset(const tal_t *ctx, const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, struct flow **flows, - char **fail) -{ - assert(flows); - assert(gossmap); - assert(chan_extra_map); - const size_t N = tal_count(flows); - for(size_t i=0; ibase_fee, + flow_edge(flow, i)->proportional_fee)); + assert(amount_msat_less_eq(x_check, x)); + + x = x_new; } - return N; + assert(!amount_msat_zero(x)); + *max_deliverable = x; + return RENEPAY_NOERROR; } -/* Helper function to fill in amounts and success_prob for flow - * - * @ctx: tal context for allocated objects that outlive this function call, eg. - * fail - * @flow: the flow we want to complete with precise amounts - * @gossmap: state of the network - * @chan_extra_map: state of the network - * @delivered: how much we are supposed to deliver at destination - * @fail: here we write verbose message errors in case of failure - * - * IMPORTANT: here we do not commit flows to chan_extra, flows are commited - * after we send those htlc. - * - * IMPORTANT: flow->success_prob is misleading, because that's the prob. of - * success provided that there are no other flows in the current MPP flow set. +/* Returns the smallest amount we can send so that the destination can get one + * HTLC of any size. It takes into account htlc_min and fees. * */ -bool flow_complete(const tal_t *ctx, struct flow *flow, - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - struct amount_msat delivered, char **fail) -{ - assert(flow); - assert(gossmap); - assert(chan_extra_map); - tal_t *this_ctx = tal(ctx, tal_t); - char *errmsg; - - flow->success_prob = 1.0; - flow->amounts = - tal_arr(flow, struct amount_msat, tal_count(flow->path)); - - struct amount_msat max_deliverable; - if (!flow_maximum_deliverable(&max_deliverable, flow, gossmap, - chan_extra_map)) { - if (fail) - *fail = tal_fmt(ctx, "flow_maximum_deliverable failed"); - goto function_fail; - } - // we cannot deliver more than it is allowed by the liquidity - // constraints: HTLC max, fees, known_max - delivered = amount_msat_min(delivered, max_deliverable); - - for (int i = tal_count(flow->path) - 1; i >= 0; i--) { - const struct chan_extra_half *h = get_chan_extra_half_by_chan( - gossmap, chan_extra_map, flow->path[i], flow->dirs[i]); - - if (!h) { - if (fail) - *fail = tal_fmt( - ctx, "channel not found in chan_extra_map"); - goto function_fail; - } - - flow->amounts[i] = delivered; - double prob = - edge_probability(this_ctx, h->known_min, h->known_max, - h->htlc_total, delivered, &errmsg); - if (prob < 0) { - if (fail) - *fail = tal_fmt( - ctx, "edge_probability failed: %s", errmsg); - goto function_fail; - } - flow->success_prob *= prob; +// static enum renepay_errorcode +// flow_minimum_sendable(struct amount_msat *min_sendable UNUSED, +// const struct flow *flow UNUSED, +// const struct gossmap *gossmap UNUSED, +// struct chan_extra_map *chan_extra_map UNUSED) +// { +// // TODO +// return RENEPAY_NOERROR; +// } - if (!amount_msat_add_fee( - &delivered, flow_edge(flow, i)->base_fee, - flow_edge(flow, i)->proportional_fee)) { - if (fail) - *fail = tal_fmt(ctx, "fee overflow"); - goto function_fail; - } +/* How much do we deliver to destination using this set of routes */ +bool flowset_delivers(struct amount_msat *delivers, struct flow **flows) +{ + struct amount_msat final = AMOUNT_MSAT(0); + for (size_t i = 0; i < tal_count(flows); i++) { + if (!amount_msat_add(&final, flows[i]->amount, final)) + return false; } - tal_free(this_ctx); + *delivers = final; return true; - -function_fail: - tal_free(this_ctx); - return false; } +/* Checks if the flows satisfy the liquidity bounds imposed by the known maximum + * liquidity and pending HTLCs. + * + * FIXME The function returns false even in the case of failure. The caller has + * no way of knowing the difference between a failure of evaluation and a + * negative answer. */ +// static bool check_liquidity_bounds(struct flow **flows, +// const struct gossmap *gossmap, +// struct chan_extra_map *chan_extra_map) +// { +// bool check = true; +// for (size_t i = 0; i < tal_count(flows); ++i) { +// struct amount_msat max_deliverable; +// if (!flow_maximum_deliverable(&max_deliverable, flows[i], +// gossmap, chan_extra_map)) +// return false; +// struct amount_msat delivers = flow_delivers(flows[i]); +// check &= amount_msat_less_eq(delivers, max_deliverable); +// } +// return check; +// } + /* Compute the prob. of success of a set of concurrent set of flows. * * IMPORTANT: this is not simply the multiplication of the prob. of success of @@ -1281,7 +201,6 @@ double flowset_probability(const tal_t *ctx, struct flow **flows, assert(gossmap); assert(chan_extra_map); tal_t *this_ctx = tal(ctx, tal_t); - char *errmsg; double prob = 1.0; // TODO(eduardo): should it be better to use a map instead of an array @@ -1296,7 +215,18 @@ double flowset_probability(const tal_t *ctx, struct flow **flows, for (size_t i = 0; i < tal_count(flows); ++i) { const struct flow *f = flows[i]; - for (size_t j = 0; j < tal_count(f->path); ++j) { + const size_t pathlen = tal_count(f->path); + struct amount_msat *amounts = tal_flow_amounts(this_ctx, f); + if (!amounts) + { + if (fail) + *fail = tal_fmt( + ctx, + "failed to compute amounts along the path"); + goto function_fail; + } + + for (size_t j = 0; j < pathlen; ++j) { const struct chan_extra_half *h = get_chan_extra_half_by_chan(gossmap, chan_extra_map, f->path[j], f->dirs[j]); @@ -1310,7 +240,7 @@ double flowset_probability(const tal_t *ctx, struct flow **flows, const u32 c_idx = gossmap_chan_idx(gossmap, f->path[j]); const int c_dir = f->dirs[j]; - const struct amount_msat deliver = f->amounts[j]; + const struct amount_msat deliver = amounts[j]; struct amount_msat prev_flow; if (!amount_msat_add(&prev_flow, h->htlc_total, @@ -1322,13 +252,12 @@ double flowset_probability(const tal_t *ctx, struct flow **flows, } double edge_prob = - edge_probability(this_ctx, h->known_min, h->known_max, - prev_flow, deliver, &errmsg); + edge_probability(h->known_min, h->known_max, + prev_flow, deliver); if (edge_prob < 0) { if (fail) *fail = tal_fmt(ctx, - "edge_probability failed: %s", - errmsg); + "edge_probability failed"); goto function_fail; } prob *= edge_prob; @@ -1351,23 +280,56 @@ double flowset_probability(const tal_t *ctx, struct flow **flows, return -1; } +bool flow_spend(struct amount_msat *ret, struct flow *flow) +{ + assert(ret); + assert(flow); + const size_t pathlen = tal_count(flow->path); + struct amount_msat spend = flow->amount; + + for (int i = (int)pathlen - 2; i >= 0; i--) { + const struct half_chan *h = flow_edge(flow, i + 1); + if (!amount_msat_add_fee(&spend, h->base_fee, + h->proportional_fee)) + goto function_fail; + } + + *ret = spend; + return true; + +function_fail: + return false; +} + +bool flow_fee(struct amount_msat *ret, struct flow *flow) +{ + assert(ret); + assert(flow); + struct amount_msat fee; + struct amount_msat spend; + if (!flow_spend(&spend, flow)) + goto function_fail; + if (!amount_msat_sub(&fee, spend, flow->amount)) + goto function_fail; + + *ret = fee; + return true; + +function_fail: + return false; +} + bool flowset_fee(struct amount_msat *ret, struct flow **flows) { assert(ret); assert(flows); struct amount_msat fee = AMOUNT_MSAT(0); - for (size_t i = 0; i < tal_count(flows); i++) { struct amount_msat this_fee; - size_t n = tal_count(flows[i]->amounts); - - if (!amount_msat_sub(&this_fee, flows[i]->amounts[0], - flows[i]->amounts[n - 1])) { + if (!flow_fee(&this_fee, flows[i])) return false; - } - if (!amount_msat_add(&fee, this_fee, fee)) { + if (!amount_msat_add(&fee, this_fee, fee)) return false; - } } *ret = fee; return true; @@ -1381,6 +343,76 @@ const struct half_chan *flow_edge(const struct flow *flow, size_t idx) return &flow->path[idx]->half[flow->dirs[idx]]; } +/* Assign the delivered amount to the flow if it fits + the path maximum capacity. */ +bool flow_assign_delivery(struct flow *flow, const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct amount_msat requested_amount) +{ + struct amount_msat max_deliverable = AMOUNT_MSAT(0); + if (flow_maximum_deliverable(&max_deliverable, flow, gossmap, + chan_extra_map, NULL)) + return false; + assert(!amount_msat_zero(max_deliverable)); + flow->amount = amount_msat_min(requested_amount, max_deliverable); + return true; +} + +/* Helper function to find the success_prob for a single flow + * + * IMPORTANT: flow->success_prob is misleading, because that's the prob. of + * success provided that there are no other flows in the current MPP flow set. + * */ +double flow_probability(struct flow *flow, const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map) +{ + assert(flow); + assert(gossmap); + assert(chan_extra_map); + const size_t pathlen = tal_count(flow->path); + struct amount_msat spend = flow->amount; + double prob = 1.0; + + for (int i = (int)pathlen - 1; i >= 0; i--) { + const struct half_chan *h = flow_edge(flow, i); + const struct chan_extra_half *eh = get_chan_extra_half_by_chan( + gossmap, chan_extra_map, flow->path[i], flow->dirs[i]); + + prob *= edge_probability(eh->known_min, eh->known_max, + eh->htlc_total, spend); + + if (prob < 0) + goto function_fail; + if (!amount_msat_add_fee(&spend, h->base_fee, + h->proportional_fee)) + goto function_fail; + } + + return prob; + +function_fail: + return -1.; +} + +u64 flow_delay(const struct flow *flow) +{ + u64 delay = 0; + for (size_t i = 0; i < tal_count(flow->path); i++) + delay += flow_edge(flow, i)->delay; + return delay; +} + +u64 flows_worst_delay(struct flow **flows) +{ + u64 maxdelay = 0; + for (size_t i = 0; i < tal_count(flows); i++) { + u64 delay = flow_delay(flows[i]); + if (delay > maxdelay) + maxdelay = delay; + } + return maxdelay; +} + #ifndef SUPERVERBOSE_ENABLED #undef SUPERVERBOSE #endif diff --git a/plugins/renepay/flow.h b/plugins/renepay/flow.h index 407ba821f4cc..f097e631ba91 100644 --- a/plugins/renepay/flow.h +++ b/plugins/renepay/flow.h @@ -5,192 +5,8 @@ #include #include #include - - -// TODO(eduardo): a hard coded constant to indicate a limit on any channel -// capacity. Channels for which the capacity is unknown (because they are not -// announced) use this value. It makes sense, because if we don't even know the -// channel capacity the liquidity could be anything but it will never be greater -// than the global number of msats. -// It remains to be checked if this value does not lead to overflow somewhere in -// the code. -#define MAX_CAP (AMOUNT_MSAT(21000000*MSAT_PER_BTC)) - -/* Any implementation needs to keep some data on channels which are - * in-use (or about which we have extra information). We use a hash - * table here, since most channels are not in use. */ -// TODO(eduardo): if we know the liquidity of channel (X,dir) is [A,B] -// then we also know that the liquidity of channel (X,!dir) is [Cap-B,Cap-A]. -// This means that it is redundant to store known_min and known_max for both -// halves of the channel and it also means that once we update the knowledge of -// (X,dir) the knowledge of (X,!dir) is updated as well. -struct chan_extra { - struct short_channel_id scid; - struct amount_msat capacity; - - struct chan_extra_half { - /* How many htlcs we've directed through it */ - size_t num_htlcs; - - /* The total size of those HTLCs */ - struct amount_msat htlc_total; - - /* The known minimum / maximum capacity (if nothing known, 0/capacity */ - struct amount_msat known_min, known_max; - } half[2]; -}; - -bool chan_extra_is_busy(const struct chan_extra *const ce); - -static inline const struct short_channel_id -chan_extra_scid(const struct chan_extra *cd) -{ - return cd->scid; -} - -static inline size_t hash_scid(const struct short_channel_id scid) -{ - /* scids cost money to generate, so simple hash works here */ - return (scid.u64 >> 32) - ^ (scid.u64 >> 16) - ^ scid.u64; -} - -static inline bool chan_extra_eq_scid(const struct chan_extra *cd, - struct short_channel_id scid) -{ - return short_channel_id_eq(scid, cd->scid); -} - -HTABLE_DEFINE_TYPE(struct chan_extra, - chan_extra_scid, hash_scid, chan_extra_eq_scid, - chan_extra_map); - -/* Helpers for chan_extra_map */ -/* Channel knowledge invariants: - * - * 0<=a<=b<=capacity - * - * a_inv = capacity-b - * b_inv = capacity-a - * - * where a,b are the known minimum and maximum liquidities, and a_inv and b_inv - * are the known minimum and maximum liquidities for the channel in the opposite - * direction. - * - * Knowledge update operations can be: - * - * 1. set liquidity (x) - * (a,b) -> (x,x) - * - * The entropy is minimum here (=0). - * - * 2. can send (x): - * xb = min(x,capacity) - * (a,b) -> (max(a,xb),max(b,xb)) - * - * If x<=a then there is no new knowledge and the entropy remains - * the same. - * If x>a the entropy decreases. - * - * - * 3. can't send (x): - * xb = max(0,x-1) - * (a,b) -> (min(a,xb),min(b,xb)) - * - * If x>b there is no new knowledge and the entropy remains. - * If x<=b then the entropy decreases. - * - * 4. sent success (x): - * (a,b) -> (max(0,a-x),max(0,b-x)) - * - * If x<=a there is no new knowledge and the entropy remains. - * If a (max(0,a-x),min(capacity,b+y)) - * - * Entropy increases unless it is already maximum. - * */ - -const char *fmt_chan_extra_map( - const tal_t *ctx, - struct chan_extra_map* chan_extra_map); - -/* Returns "" if nothing useful known about channel, otherwise - * "(details)" */ -const char *fmt_chan_extra_details(const tal_t *ctx, - const struct chan_extra_map* chan_extra_map, - const struct short_channel_id_dir *scidd); - -/* Creates a new chan_extra and adds it to the chan_extra_map. */ -struct chan_extra *new_chan_extra( - struct chan_extra_map *chan_extra_map, - const struct short_channel_id scid, - struct amount_msat capacity); - - -/* Helper to find the min of two amounts */ -static inline struct amount_msat amount_msat_min( - struct amount_msat a, - struct amount_msat b) -{ - return amount_msat_less(a,b) ? a : b; -} -/* Helper to find the max of two amounts */ -static inline struct amount_msat amount_msat_max( - struct amount_msat a, - struct amount_msat b) -{ - return amount_msat_greater(a,b) ? a : b; -} - -/* Update the knowledge that this (channel,direction) can send x msat.*/ -bool chan_extra_can_send(const tal_t *ctx, - struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - char **fail); - -/* Update the knowledge that this (channel,direction) cannot send x msat.*/ -bool chan_extra_cannot_send(const tal_t *ctx, - struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - char **fail); - -/* Update the knowledge that this (channel,direction) has liquidity x.*/ -bool chan_extra_set_liquidity(const tal_t *ctx, - struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - struct amount_msat x, char **fail); - -/* Update the knowledge that this (channel,direction) has sent x msat.*/ -bool chan_extra_sent_success(const tal_t *ctx, - struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd, - struct amount_msat x, char **fail); - -/* Forget the channel information by a fraction of the capacity. */ -bool chan_extra_relax_fraction(const tal_t *ctx, struct chan_extra *ce, - double fraction, char **fail); - -/* Returns either NULL, or an entry from the hash */ -struct chan_extra_half *get_chan_extra_half_by_scid(struct chan_extra_map *chan_extra_map, - const struct short_channel_id_dir *scidd); -/* If the channel is not registered, then a new entry is created. scid must be - * present in the gossmap. */ -struct chan_extra_half * -get_chan_extra_half_by_chan_verify( - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - const struct gossmap_chan *chan, - int dir); - -/* Helper if we have a gossmap_chan */ -struct chan_extra_half *get_chan_extra_half_by_chan(const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - const struct gossmap_chan *chan, - int dir); +#include +#include /* An actual partial flow. */ struct flow { @@ -198,9 +14,8 @@ struct flow { /* The directions to traverse. */ int *dirs; /* Amounts for this flow (fees mean this shrinks across path). */ - struct amount_msat *amounts; - /* Probability of success (0-1) */ double success_prob; + struct amount_msat amount; }; /* Helper to access the half chan at flow index idx */ @@ -221,67 +36,50 @@ double flow_edge_cost(const struct gossmap *gossmap, double basefee_penalty, double delay_riskfactor); -/* Function to fill in amounts and success_prob for flow. */ -bool flow_complete(const tal_t *ctx, struct flow *flow, - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - struct amount_msat delivered, char **fail); - /* Compute the prob. of success of a set of concurrent set of flows. */ double flowset_probability(const tal_t *ctx, struct flow **flows, const struct gossmap *const gossmap, struct chan_extra_map *chan_extra_map, char **fail); -// TODO(eduardo): we probably don't need this. Instead we should have payflow -// input. -/* Once flow is completed, this can remove it from the extra_map */ -bool remove_completed_flow(const tal_t *ctx, const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - struct flow *flow, char **fail); +/* How much do we need to send to make this flow arrive. */ +bool flow_spend(struct amount_msat *ret, struct flow *flow); -// TODO(eduardo): we probably don't need this. Instead we should have payflow -// input. -bool remove_completed_flowset(const tal_t *ctx, const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - struct flow **flows, char **fail); +/* How much do we pay in fees to make this flow arrive. */ +bool flow_fee(struct amount_msat *ret, struct flow *flow); bool flowset_fee(struct amount_msat *fee, struct flow **flows); -// TODO(eduardo): we probably don't need this. Instead we should have payflow -// input. -/* Take the flows and commit them to the chan_extra's . */ -bool commit_flow(const tal_t *ctx, const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, struct flow *flow, - char **fail); - -// TODO(eduardo): we probably don't need this. Instead we should have payflow -// input. -/* Take the flows and commit them to the chan_extra's . - * Returns the number of flows successfully commited. */ -size_t commit_flowset(const tal_t *ctx, const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, struct flow **flows, - char **fail); - -/* flows should be a set of optimal routes delivering an amount that is - * slighty less than amount_to_deliver. We will try to reallocate amounts in - * these flows so that it delivers the exact amount_to_deliver to the - * destination. - * Returns how much we are delivering at the end. */ -bool flows_fit_amount(const tal_t *ctx, struct amount_msat *amount_allocated, - struct flow **flows, struct amount_msat amount_to_deliver, - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, char **fail); +bool flowset_delivers(struct amount_msat *delivers, struct flow **flows); -/* Helpers to get the htlc_max and htlc_min of a channel. */ -static inline struct amount_msat -channel_htlc_max(const struct gossmap_chan *chan, const int dir) +static inline struct amount_msat flow_delivers(const struct flow *flow) { - return amount_msat(fp16_to_u64(chan->half[dir].htlc_max)); -} -static inline struct amount_msat -channel_htlc_min(const struct gossmap_chan *chan, const int dir) -{ - return amount_msat(fp16_to_u64(chan->half[dir].htlc_min)); + return flow->amount; } +struct amount_msat *tal_flow_amounts(const tal_t *ctx, const struct flow *flow); + +enum renepay_errorcode +flow_maximum_deliverable(struct amount_msat *max_deliverable, + const struct flow *flow, + const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + const struct gossmap_chan **bad_channel); + +/* Assign the delivered amount to the flow if it fits + the path maximum capacity. */ +bool flow_assign_delivery(struct flow *flow, const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct amount_msat requested_amount); + +double flow_probability(struct flow *flow, const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map); + +u64 flow_delay(const struct flow *flow); +u64 flows_worst_delay(struct flow **flows); + +struct flow ** +flows_ensure_liquidity_constraints(const tal_t *ctx, struct flow **flows TAKES, + const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map); + #endif /* LIGHTNING_PLUGINS_RENEPAY_FLOW_H */ diff --git a/plugins/renepay/mcf.c b/plugins/renepay/mcf.c index deea4d66c5d0..c83bc5bbf932 100644 --- a/plugins/renepay/mcf.c +++ b/plugins/renepay/mcf.c @@ -1261,7 +1261,6 @@ get_flow_paths(const tal_t *ctx, const struct gossmap *gossmap, char **fail) { tal_t *this_ctx = tal(ctx,tal_t); - char *errmsg; struct flow **flows = tal_arr(ctx,struct flow*,0); assert(amount_msat_less(excess, AMOUNT_MSAT(1000))); @@ -1428,15 +1427,21 @@ get_flow_paths(const tal_t *ctx, const struct gossmap *gossmap, } excess = amount_msat(0); - // complete the flow path by adding real fees and - // probabilities. - if (!flow_complete(this_ctx, fp, gossmap, - chan_extra_map, delivered, - &errmsg)) { + if (!flow_assign_delivery(fp, gossmap, chan_extra_map, + delivered)) { if (fail) - *fail = tal_fmt( - ctx, "flow_complete failed: %s", - errmsg); + *fail = + tal_fmt(ctx, "failed to add final " + "amount to flow"); + goto function_fail; + } + fp->success_prob = + flow_probability(fp, gossmap, chan_extra_map); + if (fp->success_prob < 0) { + if (fail) + *fail = + tal_fmt(ctx, "failed to compute " + "flow probability"); goto function_fail; } From 7e14b3da01fc1c378c39a29596bbd41d48fde63e Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 14:56:46 +0100 Subject: [PATCH 02/31] renepay: concurrent pay calls Refactor the payment structure to handle multiple pay requests for the same or different invoices. --- plugins/renepay/payment.c | 635 ++++++++++++++++---------------------- plugins/renepay/payment.h | 276 ++++++++++------- 2 files changed, 432 insertions(+), 479 deletions(-) diff --git a/plugins/renepay/payment.c b/plugins/renepay/payment.c index 1ec331f4d8f0..7c7f33044511 100644 --- a/plugins/renepay/payment.c +++ b/plugins/renepay/payment.c @@ -1,462 +1,365 @@ #include "config.h" #include #include -#include +#include +#include #include #include -#include +#include #include - -struct payment *payment_new(const tal_t *ctx, - struct command *cmd, - const char *invstr TAKES, - const char *label TAKES, - const char *description TAKES, - const struct sha256 *local_offer_id TAKES, - const struct secret *payment_secret TAKES, - const u8 *payment_metadata TAKES, - const struct route_info **routes TAKES, - const struct node_id *destination, - const struct sha256 *payment_hash, - struct amount_msat amount, - struct amount_msat maxfee, - unsigned int maxdelay, - u64 retryfor, - u16 final_cltv, - /* Tweakable in --developer mode */ - u64 base_fee_penalty, - u64 prob_cost_factor, - u64 riskfactor_millionths, - u64 min_prob_success_millionths, - bool use_shadow) +#include +#include + +static struct command_result *payment_finish(struct payment *p); + +struct payment *payment_new( + const tal_t *ctx, + const struct sha256 *payment_hash, + const char *invstr TAKES, + const char *label TAKES, + const char *description TAKES, + const struct secret *payment_secret TAKES, + const u8 *payment_metadata TAKES, + const struct route_info **routehints TAKES, + const struct node_id *destination, + struct amount_msat amount, + struct amount_msat maxfee, + unsigned int maxdelay, + u64 retryfor, + u16 final_cltv, + /* Tweakable in --developer mode */ + u64 base_fee_penalty_millionths, + u64 prob_cost_factor_millionths, + u64 riskfactor_millionths, + u64 min_prob_success_millionths, + bool use_shadow) { - struct payment *p = tal(ctx,struct payment); - p->cmd = cmd; - p->paynotes = tal_arr(p, const char *, 0); + struct payment *p = tal(ctx, struct payment); - p->total_sent = AMOUNT_MSAT(0); - p->total_delivering = AMOUNT_MSAT(0); + /* === Unique properties === */ + assert(payment_hash); + p->payment_hash = *payment_hash; + assert(invstr); p->invstr = tal_strdup(p, invstr); - p->amount = amount; - p->destination = *destination; - p->payment_hash = *payment_hash; - if (!amount_msat_add(&p->maxspend, amount, maxfee)) - p->maxspend = AMOUNT_MSAT(UINT64_MAX); + p->label = tal_strdup_or_null(p, label); + p->description = tal_strdup_or_null(p, description); + p->payment_secret = tal_dup_or_null(p, struct secret, payment_secret); + p->payment_metadata = tal_dup_talarr(p, u8, payment_metadata); - if (taken(routes)) - p->routes = tal_steal(p, routes); + if (taken(routehints)) + p->routehints = tal_steal(p, routehints); else { /* Deep copy */ - p->routes = tal_dup_talarr(p, const struct route_info *, routes); - for (size_t i = 0; i < tal_count(p->routes); i++) - p->routes[i] = tal_steal(p->routes, p->routes[i]); + p->routehints = + tal_dup_talarr(p, const struct route_info *, routehints); + for (size_t i = 0; i < tal_count(p->routehints); i++) + p->routehints[i] = + tal_steal(p->routehints, p->routehints[i]); } + + assert(destination); + p->destination = *destination; + p->amount = amount; + + + /* === Payment attempt parameters === */ + if (!amount_msat_add(&p->maxspend, amount, maxfee)) + p->maxspend = AMOUNT_MSAT(UINT64_MAX); p->maxdelay = maxdelay; + p->start_time = time_now(); p->stop_time = timeabs_add(p->start_time, time_from_sec(retryfor)); - p->preimage = NULL; - p->payment_secret = tal_dup_or_null(p, struct secret, payment_secret); - p->payment_metadata = tal_dup_talarr(p, u8, payment_metadata); - p->status=PAYMENT_PENDING; - list_head_init(&p->flows); - p->final_cltv=final_cltv; - // p->list= - p->description = tal_strdup_or_null(p, description); - p->label = tal_strdup_or_null(p, label); + p->final_cltv = final_cltv; + + /* === Developer options === */ + p->base_fee_penalty = base_fee_penalty_millionths / 1e6; + p->prob_cost_factor = prob_cost_factor_millionths / 1e6; p->delay_feefactor = riskfactor_millionths / 1e6; - p->base_fee_penalty = base_fee_penalty; - p->prob_cost_factor = prob_cost_factor; p->min_prob_success = min_prob_success_millionths / 1e6; - - p->local_offer_id = tal_dup_or_null(p, struct sha256, local_offer_id); p->use_shadow = use_shadow; - p->groupid=1; - p->local_gossmods = NULL; - p->disabled_scids = tal_arr(p,struct short_channel_id,0); - p->next_partid=1; - p->progress_deadline = NULL; + /* === Public State === */ + p->status = PAYMENT_PENDING; + p->preimage = NULL; + p->error_code = LIGHTNINGD; + p->error_msg = NULL; + p->total_sent = AMOUNT_MSAT(0); + p->total_delivering = AMOUNT_MSAT(0); + p->paynotes = tal_arr(p, const char *, 0); + p->groupid = 1; + + + /* === Hidden State === */ + p->exec_state = INVALID_STATE; + p->next_partid = 1; + p->cmd_array = tal_arr(p, struct command *, 0); + p->local_gossmods = NULL; + p->disabled_scids = tal_arr(p, struct short_channel_id, 0); + + p->have_results = false; + p->retry = false; + p->waitresult_timer = NULL; + + p->routes_computed = NULL; + p->routetracker = new_routetracker(p); return p; } -/* Disable this scid for this payment, and tell me why! */ -void payflow_disable_chan(struct pay_flow *pf, - struct short_channel_id scid, - enum log_level lvl, - const char *fmt, ...) +/* A payment that finishes execution must clean its hidden state. */ +static void payment_cleanup(struct payment *p) { - va_list ap; - const char *str; - - va_start(ap, fmt); - str = tal_vfmt(tmpctx, fmt, ap); - va_end(ap); - payflow_note(pf, lvl, "disabling %s: %s", - fmt_short_channel_id(tmpctx, scid), - str); - tal_arr_expand(&pf->payment->disabled_scids, scid); + p->exec_state = INVALID_STATE; + tal_resize(&p->cmd_array, 0); + p->local_gossmods = tal_free(p->local_gossmods); + tal_resize(&p->disabled_scids, 0); + p->waitresult_timer = tal_free(p->waitresult_timer); + + p->routes_computed = tal_free(p->routes_computed); + routetracker_cleanup(p->routetracker); } -void payment_disable_chan(struct payment *p, - struct short_channel_id scid, - enum log_level lvl, - const char *fmt, ...) +bool payment_update( + struct payment *p, + struct amount_msat maxfee, + unsigned int maxdelay, + u64 retryfor, + u16 final_cltv, + /* Tweakable in --developer mode */ + u64 base_fee_penalty_millionths, + u64 prob_cost_factor_millionths, + u64 riskfactor_millionths, + u64 min_prob_success_millionths, + bool use_shadow) { - va_list ap; - const char *str; + assert(p); - va_start(ap, fmt); - str = tal_vfmt(tmpctx, fmt, ap); - va_end(ap); - payment_note(p, lvl, "disabling %s: %s", - fmt_short_channel_id(tmpctx, scid), - str); - tal_arr_expand(&p->disabled_scids, scid); + /* === Unique properties === */ + // unchanged + + /* === Payment attempt parameters === */ + if (!amount_msat_add(&p->maxspend, p->amount, maxfee)) + p->maxspend = AMOUNT_MSAT(UINT64_MAX); + p->maxdelay = maxdelay; + + p->start_time = time_now(); + p->stop_time = timeabs_add(p->start_time, time_from_sec(retryfor)); + + p->final_cltv = final_cltv; + + /* === Developer options === */ + p->base_fee_penalty = base_fee_penalty_millionths / 1e6; + p->prob_cost_factor = prob_cost_factor_millionths / 1e6; + p->delay_feefactor = riskfactor_millionths / 1e6; + p->min_prob_success = min_prob_success_millionths / 1e6; + p->use_shadow = use_shadow; + + + /* === Public State === */ + p->status = PAYMENT_PENDING; + + /* I shouldn't be calling a payment_update on a payment that already + * succeed */ + assert(p->preimage == NULL); + + p->error_code = LIGHTNINGD; + p->error_msg = tal_free(p->error_msg);; + p->total_sent = AMOUNT_MSAT(0); + p->total_delivering = AMOUNT_MSAT(0); + // p->paynotes are unchanged, they accumulate messages + p->groupid++; + + + /* === Hidden State === */ + p->exec_state = INVALID_STATE; + p->next_partid = 1; + + /* I shouldn't be calling a payment_update on a payment that has pending + * cmds. */ + assert(p->cmd_array); + assert(tal_count(p->cmd_array) == 0); + + p->local_gossmods = tal_free(p->local_gossmods); + + assert(p->disabled_scids); + tal_resize(&p->disabled_scids, 0); + + p->have_results = false; + p->retry = false; + p->waitresult_timer = tal_free(p->waitresult_timer); + + /* It is weird to have routes here stuck. */ + if (p->routes_computed) + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, + "We have %zu unsent routes in this payment.", + tal_count(p->routes_computed)); + p->routes_computed = tal_free(p->routes_computed); + return true; } struct amount_msat payment_sent(const struct payment *p) { + assert(p); return p->total_sent; } struct amount_msat payment_delivered(const struct payment *p) { + assert(p); return p->total_delivering; } struct amount_msat payment_amount(const struct payment *p) { + assert(p); return p->amount; } struct amount_msat payment_fees(const struct payment *p) { + assert(p); struct amount_msat fees; struct amount_msat sent = payment_sent(p), delivered = payment_delivered(p); - if(!amount_msat_sub(&fees,sent,delivered)) - plugin_err(pay_plugin->plugin, "Strange, sent amount (%s) is less than delivered (%s), aborting.", - fmt_amount_msat(tmpctx, sent), - fmt_amount_msat(tmpctx, delivered)); + if (!amount_msat_sub(&fees, sent, delivered)) + plugin_err( + pay_plugin->plugin, + "Strange, sent amount (%s) is less than delivered (%s), " + "aborting.", + fmt_amount_msat(tmpctx, sent), + fmt_amount_msat(tmpctx, delivered)); return fees; } -void payment_note(struct payment *p, - enum log_level lvl, - const char *fmt, ...) +u64 payment_parts(const struct payment *payment) { - va_list ap; - const char *str; - - va_start(ap, fmt); - str = tal_vfmt(p->paynotes, fmt, ap); - va_end(ap); - tal_arr_expand(&p->paynotes, str); - /* Log at debug, unless it's weird... */ - plugin_log(pay_plugin->plugin, - lvl < LOG_UNUSUAL ? LOG_DBG : lvl, "%s", str); - - if (p->cmd) - plugin_notify_message(p->cmd, lvl, "%s", str); + assert(payment); + return payment->next_partid - 1; } -void payflow_note(struct pay_flow *pf, - enum log_level lvl, - const char *fmt, ...) +/* attach a command to this payment */ +bool payment_register_command(struct payment *p, struct command *cmd) { - va_list ap; - const char *str; - - va_start(ap, fmt); - str = tal_vfmt(tmpctx, fmt, ap); - va_end(ap); - - payment_note(pf->payment, lvl, " Flow %"PRIu64": %s", - pf->key.partid, str); + assert(p); + assert(cmd); + assert(p->cmd_array); + tal_arr_expand(&p->cmd_array, cmd); + return true; } -void payment_assert_delivering_incomplete(const struct payment *p) -{ - if(!amount_msat_less(p->total_delivering, p->amount)) - { - plugin_err(pay_plugin->plugin, - "Strange, delivering (%s) is not smaller than amount (%s)", - fmt_amount_msat(tmpctx, p->total_delivering), - fmt_amount_msat(tmpctx, p->amount)); - } -} -void payment_assert_delivering_all(const struct payment *p) +/* are there pending commands on this payment? */ +bool payment_commands_empty(const struct payment *p) { - if(amount_msat_less(p->total_delivering, p->amount)) - { - plugin_err(pay_plugin->plugin, - "Strange, delivering (%s) is less than amount (%s)", - fmt_amount_msat(tmpctx, p->total_delivering), - fmt_amount_msat(tmpctx, p->amount)); - } + assert(p); + assert(p->cmd_array); + return tal_count(p->cmd_array) == 0; } -struct command_result *payment_success(struct payment *p) +struct command *payment_command(struct payment *p) { - /* We only finish command once: its destructor clears this. */ - if (!p->cmd) + assert(p); + assert(p->cmd_array); + if (tal_count(p->cmd_array) == 0) return NULL; - - struct json_stream *response - = jsonrpc_stream_success(p->cmd); - - /* Any one succeeding is success. */ - json_add_preimage(response, "payment_preimage", p->preimage); - json_add_sha256(response, "payment_hash", &p->payment_hash); - json_add_timeabs(response, "created_at", p->start_time); - json_add_u32(response, "parts", payment_parts(p)); - json_add_amount_msat(response, "amount_msat", - p->amount); - json_add_amount_msat(response, "amount_sent_msat", - p->total_sent); - json_add_string(response, "status", "complete"); - json_add_node_id(response, "destination", &p->destination); - - return command_finished(p->cmd, response); + return p->cmd_array[0]; } -struct command_result *payment_fail( - struct payment *payment, - enum jsonrpc_errcode code, - const char *fmt, ...) +struct command_result *payment_success(struct payment *payment, + const struct preimage *preimage TAKES) { - struct command *cmd; + assert(payment); + assert(preimage); + payment->status = PAYMENT_SUCCESS; + payment->preimage = tal_free(payment->preimage); + payment->preimage = tal_dup(payment, struct preimage, preimage); + return payment_finish(payment); +} - /* We usually get called because a flow failed, but we - * can also get called because we couldn't route any more - * or some strange error. */ +struct command_result *payment_fail(struct payment *payment, + enum jsonrpc_errcode code, const char *fmt, + ...) +{ payment->status = PAYMENT_FAIL; - - /* We only finish command once: its destructor clears this. */ - if (!payment->cmd) - return NULL; + payment->error_code = code; + payment->error_msg = tal_free(payment->error_msg); va_list args; va_start(args, fmt); - char *message = tal_vfmt(tmpctx,fmt,args); + payment->error_msg = tal_vfmt(payment, fmt, args); va_end(args); - /* Don't bother notifying command, it's about to get failure */ - cmd = payment->cmd; - payment->cmd = NULL; - payment_note(payment, LOG_DBG, "%s", message); - /* Restore to keep destructor happy! */ - payment->cmd = cmd; - - return command_fail(cmd,code,"%s",message); -} + payment_note(payment, LOG_DBG, "Payment failed: %s", + payment->error_msg); -u64 payment_parts(const struct payment *payment) -{ - return payment->next_partid-1; + return payment_finish(payment); } -void payment_reconsider(struct payment *payment) +void payment_note(struct payment *p, enum log_level lvl, const char *fmt, ...) { - struct pay_flow *i, *next; - bool have_state[NUM_PAY_FLOW] = {false}; - enum jsonrpc_errcode final_error COMPILER_WANTS_INIT("gcc 12.3.0 -O3"), ecode; - const char *final_msg COMPILER_WANTS_INIT("gcc 12.3.0 -O3"); - const char *errmsg; - - plugin_log(pay_plugin->plugin, LOG_DBG, "payment_reconsider"); - - /* Harvest results and free up finished flows */ - list_for_each_safe(&payment->flows, i, next, list) { - plugin_log(pay_plugin->plugin, LOG_DBG, "Flow in state %u", i->state); - have_state[i->state] = true; - - switch (i->state) { - case PAY_FLOW_NOT_STARTED: - /* Can't happen: we start just after we add. */ - plugin_err(pay_plugin->plugin, "flow not started?"); - case PAY_FLOW_IN_PROGRESS: - /* Don't free, it's still going! */ - continue; - case PAY_FLOW_FAILED: - break; - case PAY_FLOW_FAILED_FINAL: - final_error = i->final_error; - final_msg = tal_steal(tmpctx, i->final_msg); - break; - case PAY_FLOW_FAILED_GOSSIP_PENDING: - /* Don't free, it's still going! */ - continue; - case PAY_FLOW_SUCCESS: - if (payment->preimage) { - /* This should be impossible without breaking SHA256 */ - if (!preimage_eq(payment->preimage, - i->payment_preimage)) { - plugin_err(pay_plugin->plugin, - "Impossible preimage clash for %s: %s and %s?", - fmt_sha256(tmpctx, - &payment->payment_hash), - fmt_preimage(tmpctx, - payment->preimage), - fmt_preimage(tmpctx, - i->payment_preimage)); - } - } else { - payment->preimage = tal_dup(payment, struct preimage, - i->payment_preimage); - } - break; - } - tal_free(i); - } + va_list ap; + const char *str; - /* First, did one of these succeed? */ - if (have_state[PAY_FLOW_SUCCESS]) { - plugin_log(pay_plugin->plugin, LOG_DBG, "one succeeded!"); - - switch (payment->status) { - case PAYMENT_PENDING: - /* The normal case: one part succeeded, we can succeed immediately */ - payment_success(payment); - payment->status = PAYMENT_SUCCESS; - /* fall thru */ - case PAYMENT_SUCCESS: - /* Since we already succeeded, cmd must be NULL */ - assert(payment->cmd == NULL); - break; - case PAYMENT_FAIL: - /* OK, they told us it failed, but also - * succeeded? It's theoretically possible, - * but someone screwed up. */ - plugin_log(pay_plugin->plugin, LOG_BROKEN, - "Destination %s succeeded payment %s" - " (preimage %s) after previous final failure?", - fmt_node_id(tmpctx, &payment->destination), - fmt_sha256(tmpctx, &payment->payment_hash), - fmt_preimage(tmpctx, payment->preimage)); - break; - } - - /* We don't need to do anything else. */ - return; - } + va_start(ap, fmt); + str = tal_vfmt(p->paynotes, fmt, ap); + va_end(ap); - /* One of these returned an error from the destination? */ - if (have_state[PAY_FLOW_FAILED_FINAL]) { - plugin_log(pay_plugin->plugin, LOG_DBG, "one failed final!"); - switch (payment->status) { - case PAYMENT_PENDING: - /* The normal case: we can fail immediately */ - payment_fail(payment, final_error, "%s", final_msg); - /* fall thru */ - case PAYMENT_FAIL: - /* Since we already failed, cmd must be NULL */ - assert(payment->cmd == NULL); - break; - case PAYMENT_SUCCESS: - /* OK, they told us it failed, but also - * succeeded? It's theoretically possible, - * but someone screwed up. */ - plugin_log(pay_plugin->plugin, LOG_BROKEN, - "Destination %s failed payment %s with %u/%s" - " after previous success?", - fmt_node_id(tmpctx, - &payment->destination), - fmt_sha256(tmpctx, - &payment->payment_hash), - final_error, final_msg); - break; - } - - /* We don't need to do anything else. */ - return; - } + tal_arr_expand(&p->paynotes, str); + /* Log at debug, unless it's weird... */ + plugin_log(pay_plugin->plugin, lvl < LOG_UNUSUAL ? LOG_DBG : lvl, "%s", + str); - /* Now, do we still care about retrying the payment? It could - * have terminated a while ago, and we're just collecting - * outstanding results. */ - switch (payment->status) { - case PAYMENT_PENDING: - break; - case PAYMENT_FAIL: - case PAYMENT_SUCCESS: - assert(!payment->cmd); - plugin_log(pay_plugin->plugin, LOG_DBG, "payment already status %u!", - payment->status); - return; + for (size_t i = 0; i < tal_count(p->cmd_array); i++) { + struct command *cmd = p->cmd_array[i]; + plugin_notify_message(cmd, lvl, "%s", str); } +} - /* Are we waiting on addgossip? We'll come back later when - * they call pay_flow_finished_adding_gossip. */ - if (have_state[PAY_FLOW_FAILED_GOSSIP_PENDING]) { - plugin_log(pay_plugin->plugin, LOG_DBG, - "%s waiting on addgossip return", - fmt_sha256(tmpctx, - &payment->payment_hash)); - return; +static struct command_result *my_command_finish(struct payment *p, + struct command *cmd) +{ + struct json_stream *result; + if (p->status == PAYMENT_SUCCESS) { + result = jsonrpc_stream_success(cmd); + json_add_payment(result, p); + return command_finished(cmd, result); } + assert(p->status == PAYMENT_FAIL); + assert(p->error_msg); + return command_fail(cmd, p->error_code, "%s", p->error_msg); +} - /* Do we still have pending payment parts? First time, we set - * up a deadline so we don't respond immediately to every - * return: it's better to gather a few failed flows before - * retrying. */ - if (have_state[PAY_FLOW_IN_PROGRESS]) { - struct timemono now = time_mono(); - - /* If we don't have a deadline yet, set it now. */ - if (!payment->progress_deadline) { - payment->progress_deadline = tal(payment, struct timemono); - *payment->progress_deadline = timemono_add(now, - time_from_msec(TIMER_COLLECT_FAILURES_MSEC)); - plugin_log(pay_plugin->plugin, LOG_DBG, "Set deadline"); - } - - /* FIXME: add timemono_before to ccan/time */ - if (time_less_(now.ts, payment->progress_deadline->ts)) { - /* Come back later. */ - /* We don't care that this temporily looks like a leak; we don't even - * care if we end up with multiple outstanding. They just check - * the progress_deadline. */ - plugin_log(pay_plugin->plugin, LOG_DBG, "Setting timer to kick us"); - notleak(plugin_timer(pay_plugin->plugin, - timemono_between(*payment->progress_deadline, now), - payment_reconsider, payment)); - return; - } - } +static struct command_result *payment_finish(struct payment *p) +{ + assert(p->status == PAYMENT_FAIL || p->status == PAYMENT_SUCCESS); + assert(!payment_commands_empty(p)); + struct command *cmd = p->cmd_array[0]; - /* At this point, we may have some funds to deliver (or we - * could still be waiting). */ - if (amount_msat_greater_eq(payment->total_delivering, payment->amount)) { - plugin_log(pay_plugin->plugin, LOG_DBG, "No more to deliver right now"); - assert(have_state[PAY_FLOW_IN_PROGRESS]); - return; + // notify all commands that the payment completed + for (size_t i = 1; i < tal_count(p->cmd_array); ++i) { + my_command_finish(p, p->cmd_array[i]); } - /* If we had a deadline, reset it */ - payment->progress_deadline = tal_free(payment->progress_deadline); - - /* Before we do that, make sure we're not going over time. */ - if (time_after(time_now(), payment->stop_time)) { - payment_fail(payment, PAY_STOPPED_RETRYING, "Timed out"); - return; - } + // set the payment into a valid final state + payment_cleanup(p); - plugin_log(pay_plugin->plugin, LOG_DBG, "Retrying payment"); - errmsg = try_paying(tmpctx, payment, &ecode); - if (errmsg) - payment_fail(payment, ecode, "%s", errmsg); + return my_command_finish(p, cmd); } -/* Remove all flows with the given state. */ -void payment_remove_flows(struct payment *p, enum pay_flow_state state) +void payment_disable_chan(struct payment *p, struct short_channel_id scid, + enum log_level lvl, const char *fmt, ...) { - struct pay_flow *pf, *next; - list_for_each_safe(&p->flows, pf, next, list) { - if(pf->state == state) - list_del(&pf->list); - } + assert(p); + assert(p->disabled_scids); + va_list ap; + const char *str; + + va_start(ap, fmt); + str = tal_vfmt(tmpctx, fmt, ap); + va_end(ap); + payment_note(p, lvl, "disabling %s: %s", + fmt_short_channel_id(tmpctx, scid), + str); + tal_arr_expand(&p->disabled_scids, scid); } diff --git a/plugins/renepay/payment.h b/plugins/renepay/payment.h index f73c32597ddb..01a53a3a5901 100644 --- a/plugins/renepay/payment.h +++ b/plugins/renepay/payment.h @@ -3,61 +3,43 @@ #include "config.h" #include #include -#include -/* FIXME: this shouldn't be here, the dependency tree is a little messed-up. */ -enum pay_flow_state; +enum payment_status { PAYMENT_PENDING, PAYMENT_SUCCESS, PAYMENT_FAIL }; -struct pay_flow; - -enum payment_status { - PAYMENT_PENDING, PAYMENT_SUCCESS, PAYMENT_FAIL -}; +#define INVALID_STATE UINT64_MAX struct payment { /* Inside pay_plugin->payments list */ + // TODO: probably not necessary after we store all payments in a + // hashtable instead of a list struct list_node list; - /* Overall, how are we going? */ - enum payment_status status; - - /* The flows we are managing. */ - struct list_head flows; - - /* Deadline for flow status collection. */ - struct timemono *progress_deadline; - /* The command if still running */ - struct command *cmd; - - /* Localmods to apply to gossip_map for our own use. */ - struct gossmap_localmods *local_gossmods; - - /* Channels we decided to disable for various reasons. */ - struct short_channel_id *disabled_scids; + /* === Unique properties === */ - /* Used in get_payflows to set ids to each pay_flow. */ - u64 next_partid; + /* payment_hash is unique */ + struct sha256 payment_hash; - /* Chatty description of attempts. */ - const char **paynotes; + /* invstring (bolt11 or bolt12) */ + const char *invstr; - /* Total sent, including fees. */ - struct amount_msat total_sent; + /* Description and labels, if any. */ + const char *description, *label; - /* Total that is delivering (i.e. without fees) */ - struct amount_msat total_delivering; + /* payment_secret, if specified by invoice. */ + struct secret *payment_secret; - /* invstring (bolt11 or bolt12) */ - const char *invstr; + /* Payment metadata, if specified by invoice. */ + const u8 *payment_metadata; /* Extracted routehints */ - const struct route_info **routes; + const struct route_info **routehints; /* How much, what, where */ - struct amount_msat amount; struct node_id destination; - struct sha256 payment_hash; + struct amount_msat amount; + + /* === Payment attempt parameters === */ /* Limits on what routes we'll accept. */ struct amount_msat maxspend; @@ -65,33 +47,31 @@ struct payment { /* Max accepted HTLC delay.*/ unsigned int maxdelay; + /* TODO new feature: Maximum number of hops */ + // see common/gossip_constants.h:8:#define ROUTING_MAX_HOPS 20 + // int max_num_hops; + /* We promised this in pay() output */ struct timeabs start_time; /* We stop trying after this time is reached. */ struct timeabs stop_time; - /* Payment preimage, in case of success. */ - const struct preimage *preimage; - - /* payment_secret, if specified by invoice. */ - struct secret *payment_secret; + u32 final_cltv; - /* Payment metadata, if specified by invoice. */ - const u8 *payment_metadata; - u32 final_cltv; + /* === Developer options === */ - /* Description and labels, if any. */ - const char *description, *label; + /* Penalty for base fee */ + double base_fee_penalty; + /* Conversion from prob. cost to millionths */ + double prob_cost_factor; + /* prob. cost = - prob_cost_factor * log prob. */ /* Penalty for CLTV delays */ double delay_feefactor; - /* Penalty for base fee */ - double base_fee_penalty; - /* With these the effective linear fee cost is computed as * * linear fee cost = @@ -103,88 +83,158 @@ struct payment { /* The minimum acceptable prob. of success */ double min_prob_success; - /* Conversion from prob. cost to millionths */ - double prob_cost_factor; - /* prob. cost = - * - prob_cost_factor * log prob. */ + /* --developer allows disabling shadow route */ + bool use_shadow; - /* If this is paying a local offer, this is the one (sendpay ensures we - * don't pay twice for single-use offers) */ - // TODO(eduardo): this is not being used! - struct sha256 *local_offer_id; + /* === Public State === */ + /* TODO: these properties should be private and only changed through + * payment_ methods. */ - /* --developer allows disabling shadow route */ - bool use_shadow; + /* Overall, how are we going? */ + enum payment_status status; + + /* Payment preimage, in case of success. */ + struct preimage *preimage; + + /* Final error code and message, in case of failure. */ + enum jsonrpc_errcode error_code; + const char *error_msg; + + /* Total sent, including fees. */ + struct amount_msat total_sent; + + /* Total that is delivering (i.e. without fees) */ + struct amount_msat total_delivering; + + /* Chatty description of attempts. */ + const char **paynotes; /* Groupid, so listpays() can group them back together */ u64 groupid; -}; -struct payment *payment_new(const tal_t *ctx, - struct command *cmd, - const char *invstr TAKES, - const char *label TAKES, - const char *description TAKES, - const struct sha256 *local_offer_id TAKES, - const struct secret *payment_secret TAKES, - const u8 *payment_metadata TAKES, - const struct route_info **routes TAKES, - const struct node_id *destination, - const struct sha256 *payment_hash, - struct amount_msat amount, - struct amount_msat maxfee, - unsigned int maxdelay, - u64 retryfor, - u16 final_cltv, - /* Tweakable in --developer mode */ - u64 base_fee_penalty, - u64 prob_cost_factor, - u64 riskfactor_millionths, - u64 min_prob_success_millionths, - bool use_shadow); + /* === Hidden State === */ + /* Position in the payment virtual machine */ + u64 exec_state; + + /* Used in get_payflows to set ids to each pay_flow. */ + u64 next_partid; + + /* Running commands that want this payment */ + struct command **cmd_array; + + /* Localmods to apply to gossip_map for our own use. */ + struct gossmap_localmods *local_gossmods; + + /* Channels we decided to disable for various reasons. */ + struct short_channel_id *disabled_scids; + + + /* Flag to indicate wether we have collected enough results to make a + * decision on the payment progress. */ + bool have_results; + + /* Flag to indicate wether we would like to retry the payment. */ + bool retry; + + /* Timer we use to wait for results. */ + struct plugin_timer *waitresult_timer; + + struct route **routes_computed; + struct routetracker *routetracker; +}; + +static inline const struct sha256 payment_hash(const struct payment *p) +{ + return p->payment_hash; +} + +static inline size_t payment_hash64(const struct sha256 h) +{ + return ((u64)h.u.u32[1] << 32) ^ h.u.u32[0]; +} + +static inline bool payment_hash_eq(const struct payment *p, + const struct sha256 h) +{ + return p->payment_hash.u.u32[0] == h.u.u32[0] && + p->payment_hash.u.u32[1] == h.u.u32[1] && + p->payment_hash.u.u32[2] == h.u.u32[2] && + p->payment_hash.u.u32[3] == h.u.u32[3] && + p->payment_hash.u.u32[4] == h.u.u32[4] && + p->payment_hash.u.u32[5] == h.u.u32[5] && + p->payment_hash.u.u32[6] == h.u.u32[6] && + p->payment_hash.u.u32[7] == h.u.u32[7]; +} + +HTABLE_DEFINE_TYPE(struct payment, payment_hash, payment_hash64, + payment_hash_eq, payment_map); + +struct payment *payment_new( + const tal_t *ctx, + const struct sha256 *payment_hash, + const char *invstr TAKES, + const char *label TAKES, + const char *description TAKES, + const struct secret *payment_secret TAKES, + const u8 *payment_metadata TAKES, + const struct route_info **routehints TAKES, + const struct node_id *destination, + struct amount_msat amount, + struct amount_msat maxfee, + unsigned int maxdelay, + u64 retryfor, + u16 final_cltv, + /* Tweakable in --developer mode */ + u64 base_fee_penalty_millionths, + u64 prob_cost_factor_millionths, + u64 riskfactor_millionths, + u64 min_prob_success_millionths, + bool use_shadow); + +bool payment_update( + struct payment *p, + struct amount_msat maxfee, + unsigned int maxdelay, + u64 retryfor, + u16 final_cltv, + /* Tweakable in --developer mode */ + u64 base_fee_penalty_millionths, + u64 prob_cost_factor_millionths, + u64 riskfactor_millionths, + u64 min_prob_success_millionths, + bool use_shadow); struct amount_msat payment_sent(const struct payment *p); struct amount_msat payment_delivered(const struct payment *p); struct amount_msat payment_amount(const struct payment *p); struct amount_msat payment_fees(const struct payment *p); -/* These log at LOG_DBG, append to notes, and send command notification */ -void payment_note(struct payment *p, - enum log_level lvl, - const char *fmt, ...); -void payflow_note(struct pay_flow *pf, - enum log_level lvl, - const char *fmt, ...); -void payment_assert_delivering_incomplete(const struct payment *p); -void payment_assert_delivering_all(const struct payment *p); - -/* A flow has changed state, or we've hit a timeout: do something! */ -void payment_reconsider(struct payment *p); - u64 payment_parts(const struct payment *payment); -/* Disable this scid for this payment, and tell me why! */ -void payflow_disable_chan(struct pay_flow *pf, - struct short_channel_id scid, - enum log_level lvl, - const char *fmt, ...); +/* attach a command to this payment */ +bool payment_register_command(struct payment *p, struct command *cmd); +/* are there pending commands on this payment? */ +bool payment_commands_empty(const struct payment *p); +struct command *payment_command(struct payment *p); -/* Sometimes, disabling chan is independent of a flow. */ -void payment_disable_chan(struct payment *p, - struct short_channel_id scid, - enum log_level lvl, - const char *fmt, ...); +/* get me the result of this payment, not necessarily a completed payment */ +struct json_stream *payment_result(struct payment *p, struct command *cmd); -/* Remove all flows with the given state. */ -void payment_remove_flows(struct payment *p, enum pay_flow_state state); +/* flag the payment as success and write the preimage as proof */ +struct command_result *payment_success(struct payment *payment, + const struct preimage *preimage TAKES); -struct command_result *payment_fail( - struct payment *payment, - enum jsonrpc_errcode code, - const char *fmt, ...); +/* flag the payment as failed and write the reason */ +struct command_result *payment_fail(struct payment *payment, + enum jsonrpc_errcode code, const char *fmt, + ...); + +/* These log at LOG_DBG, append to notes, and send command notification */ +void payment_note(struct payment *p, enum log_level lvl, const char *fmt, ...); -struct command_result *payment_success(struct payment *p); +void payment_disable_chan(struct payment *p, struct short_channel_id scid, + enum log_level lvl, const char *fmt, ...); #endif /* LIGHTNING_PLUGINS_RENEPAY_PAYMENT_H */ From 7afc371e17891801b91a744c3d662496e326c15d Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 14:59:24 +0100 Subject: [PATCH 03/31] renepay: abandon pay_flow for route The role of the structure pay_flow is not taken by a new structure called route. --- plugins/renepay/pay_flow.c | 730 ------------------------------------- plugins/renepay/pay_flow.h | 141 ------- plugins/renepay/route.c | 112 ++++++ plugins/renepay/route.h | 177 +++++++++ 4 files changed, 289 insertions(+), 871 deletions(-) delete mode 100644 plugins/renepay/pay_flow.c delete mode 100644 plugins/renepay/pay_flow.h create mode 100644 plugins/renepay/route.c create mode 100644 plugins/renepay/route.h diff --git a/plugins/renepay/pay_flow.c b/plugins/renepay/pay_flow.c deleted file mode 100644 index f8b67503122f..000000000000 --- a/plugins/renepay/pay_flow.c +++ /dev/null @@ -1,730 +0,0 @@ -/* Routines to get suitable pay_flow array from pay constraints */ -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// FIXME These macros are used in more than one place of the code, they could be -// defined in a single header. -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) -#define MIN(x, y) (((x) < (y)) ? (x) : (y)) - -/* BOLT #7: - * - * If a route is computed by simply routing to the intended recipient and summing - * the `cltv_expiry_delta`s, then it's possible for intermediate nodes to guess - * their position in the route. Knowing the CLTV of the HTLC, the surrounding - * network topology, and the `cltv_expiry_delta`s gives an attacker a way to guess - * the intended recipient. Therefore, it's highly desirable to add a random offset - * to the CLTV that the intended recipient will receive, which bumps all CLTVs - * along the route. - * - * In order to create a plausible offset, the origin node MAY start a limited - * random walk on the graph, starting from the intended recipient and summing the - * `cltv_expiry_delta`s, and use the resulting sum as the offset. - * This effectively creates a _shadow route extension_ to the actual route and - * provides better protection against this attack vector than simply picking a - * random offset would. - */ - -/* There's little benefit in doing this per-flow, since you can - * correlate flows so trivially, but it's good practice for when we - * have PTLCs and that's not true. */ - -#define MAX_SHADOW_LEN 3 - -static void remove_htlc_payflow( - struct chan_extra_map *chan_extra_map, - struct pay_flow *pf) -{ - for (size_t i = 0; i < tal_count(pf->path_scidds); i++) { - struct chan_extra_half *h = get_chan_extra_half_by_scid( - chan_extra_map, - &pf->path_scidds[i]); - if(!h) - { - plugin_err(pay_plugin->plugin, - "%s could not resolve chan_extra_half", - __PRETTY_FUNCTION__); - } - if (!amount_msat_sub(&h->htlc_total, h->htlc_total, pf->amounts[i])) - { - plugin_err(pay_plugin->plugin, - "%s could not substract HTLC amounts, " - "half total htlc amount = %s, " - "pf->amounts[%lld] = %s.", - __PRETTY_FUNCTION__, - fmt_amount_msat(tmpctx, h->htlc_total), - i, - fmt_amount_msat(tmpctx, pf->amounts[i])); - } - if (h->num_htlcs == 0) - { - plugin_err(pay_plugin->plugin, - "%s could not decrease HTLC count.", - __PRETTY_FUNCTION__); - } - h->num_htlcs--; - } -} - -static void commit_htlc_payflow( - struct chan_extra_map *chan_extra_map, - const struct pay_flow *pf) -{ - for (size_t i = 0; i < tal_count(pf->path_scidds); i++) { - struct chan_extra_half *h = get_chan_extra_half_by_scid( - chan_extra_map, - &pf->path_scidds[i]); - if(!h) - { - plugin_err(pay_plugin->plugin, - "%s could not resolve chan_extra_half", - __PRETTY_FUNCTION__); - } - if (!amount_msat_add(&h->htlc_total, h->htlc_total, pf->amounts[i])) - { - plugin_err(pay_plugin->plugin, - "%s could not add HTLC amounts, " - "pf->amounts[%lld] = %s.", - __PRETTY_FUNCTION__, - i, - fmt_amount_msat(tmpctx, pf->amounts[i])); - } - h->num_htlcs++; - } -} - -/* Returns CLTV, and fills in *shadow_fee, based on extending the path */ -static u32 shadow_one_flow(const struct gossmap *gossmap, - const struct flow *f, - struct amount_msat *shadow_fee) -{ - size_t numpath = tal_count(f->amounts); - struct amount_msat amount = f->amounts[numpath-1]; - struct gossmap_node *n; - size_t hop; - struct gossmap_chan *chans[MAX_SHADOW_LEN]; - int dirs[MAX_SHADOW_LEN]; - u32 shadow_delay = 0; - - /* Start at end of path */ - n = gossmap_nth_node(gossmap, f->path[numpath-1], !f->dirs[numpath-1]); - - /* We only create shadow for extra CLTV delays, *not* for - * amounts. This is because with MPP our amounts are random - * looking already. */ - for (hop = 0; hop < MAX_SHADOW_LEN && pseudorand(2); hop++) { - /* Try for a believable channel up to 10 times, then stop */ - for (size_t i = 0; i < 10; i++) { - struct amount_sat cap; - chans[hop] = gossmap_nth_chan(gossmap, n, pseudorand(n->num_chans), - &dirs[hop]); - if (!gossmap_chan_set(chans[hop], dirs[hop]) - || !gossmap_chan_get_capacity(gossmap, chans[hop], &cap) - /* This test is approximate, since amount would differ */ - || amount_msat_greater_sat(amount, cap)) { - chans[hop] = NULL; - continue; - } - } - if (!chans[hop]) - break; - - shadow_delay += chans[hop]->half[dirs[hop]].delay; - n = gossmap_nth_node(gossmap, chans[hop], !dirs[hop]); - } - - /* If we were actually trying to get amount to end of shadow, - * what would we be paying to the "intermediary" node (real dest) */ - for (int i = (int)hop - 1; i >= 0; i--) - if (!amount_msat_add_fee(&amount, - chans[i]->half[dirs[i]].base_fee, - chans[i]->half[dirs[i]].proportional_fee)) - /* Ignore: treats impossible event as zero fee. */ - ; - - /* Shouldn't happen either */ - if (!amount_msat_sub(shadow_fee, amount, f->amounts[numpath-1])) - plugin_err(pay_plugin->plugin, - "Failed to calc shadow fee: %s - %s", - fmt_amount_msat(tmpctx, amount), - fmt_amount_msat(tmpctx, f->amounts[numpath-1])); - - return shadow_delay; -} - -static bool add_to_amounts(const struct gossmap *gossmap, - struct flow *f, - struct amount_msat maxspend, - struct amount_msat additional) -{ - struct amount_msat *amounts; - size_t num = tal_count(f->amounts); - - /* Recalculate amounts backwards */ - amounts = tal_arr(tmpctx, struct amount_msat, num); - if (!amount_msat_add(&amounts[num-1], f->amounts[num-1], additional)) - return false; - - for (int i = num-2; i >= 0; i--) { - amounts[i] = amounts[i+1]; - if (!amount_msat_add_fee(&amounts[i], - flow_edge(f, i+1)->base_fee, - flow_edge(f, i+1)->proportional_fee)) - return false; - } - - /* Do we now exceed budget? */ - if (amount_msat_greater(amounts[0], maxspend)) - return false; - - /* OK, replace amounts */ - tal_free(f->amounts); - f->amounts = tal_steal(f, amounts); - return true; -} - -static u64 flow_delay(const struct flow *flow) -{ - u64 delay = 0; - for (size_t i = 0; i < tal_count(flow->path); i++) - delay += flow->path[i]->half[flow->dirs[i]].delay; - return delay; -} - -/* This enhances f->amounts, and returns per-flow cltvs */ -static u32 *shadow_additions(const tal_t *ctx, - const struct gossmap *gossmap, - struct payment *p, - struct flow **flows, - bool is_entire_payment) -{ - u32 *final_cltvs; - - /* Set these up now in case we decide to do nothing */ - final_cltvs = tal_arr(ctx, u32, tal_count(flows)); - for (size_t i = 0; i < tal_count(flows); i++) - final_cltvs[i] = p->final_cltv; - - /* --developer can disable this */ - if (!p->use_shadow) - return final_cltvs; - - for (size_t i = 0; i < tal_count(flows); i++) { - u32 shadow_delay; - struct amount_msat shadow_fee; - - shadow_delay = shadow_one_flow(gossmap, flows[i], - &shadow_fee); - if (flow_delay(flows[i]) + shadow_delay > p->maxdelay) { - payment_note(p, LOG_UNUSUAL, - "No shadow for flow %zu/%zu:" - " delay would add %u to %"PRIu64", exceeding max delay.", - i, tal_count(flows), - shadow_delay, - flow_delay(flows[i])); - continue; - } - - /* We don't need to add fee amounts to obfuscate most payments - * when we're using MPP, since we randomly split amounts. But - * if this really is the entire thing, we want to, since - * people use round numbers of msats in invoices. */ - if (is_entire_payment && tal_count(flows) == 1) { - if (!add_to_amounts(gossmap, flows[i], p->maxspend, - shadow_fee)) { - payment_note(p, LOG_UNUSUAL, - "No shadow fee for flow %zu/%zu:" - " fee would add %s to %s, exceeding budget %s.", - i, tal_count(flows), - fmt_amount_msat(tmpctx, shadow_fee), - fmt_amount_msat(tmpctx, - flows[i]->amounts[0]), - fmt_amount_msat(tmpctx, p->maxspend)); - } else { - payment_note(p, LOG_DBG, - "No MPP, so added %s shadow fee", - fmt_amount_msat(tmpctx, shadow_fee)); - } - } - - final_cltvs[i] += shadow_delay; - payment_note(p, LOG_DBG, "Shadow route on flow %zu/%zu added %u block delay. now %u", - i, tal_count(flows), shadow_delay, final_cltvs[i]); - } - - return final_cltvs; -} - -static void destroy_payment_flow(struct pay_flow *pf) -{ - list_del_from(&pf->payment->flows, &pf->list); -} - -/* Print out flow, and any information we already know */ -static const char *flow_path_annotated(const tal_t *ctx, - const struct pay_flow *flow) -{ - char *s = tal_strdup(ctx, ""); - for (size_t i = 0; i < tal_count(flow->path_scidds); i++) { - tal_append_fmt(&s, "-%s%s->", - fmt_short_channel_id_dir(tmpctx, - &flow->path_scidds[i]), - fmt_chan_extra_details(tmpctx, - pay_plugin->chan_extra_map, - &flow->path_scidds[i])); - } - return s; -} - -/* Calculates delays and converts to scids, and links to the payment. - * Frees flows. */ -static void convert_and_attach_flows(struct payment *payment, - struct gossmap *gossmap, - struct flow **flows STEALS, - const u32 *final_cltvs, - u64 *next_partid) -{ - for (size_t i = 0; i < tal_count(flows); i++) { - struct flow *f = flows[i]; - struct pay_flow *pf = tal(payment, struct pay_flow); - size_t plen; - - plen = tal_count(f->path); - - pf->payment = payment; - pf->state = PAY_FLOW_NOT_STARTED; - pf->key.partid = (*next_partid)++; - pf->key.groupid = payment->groupid; - pf->key.payment_hash = payment->payment_hash; - - /* Convert gossmap_chan into scids and nodes */ - pf->path_scidds = tal_arr(pf, struct short_channel_id_dir, plen); - pf->path_nodes = tal_arr(pf, struct node_id, plen); - for (size_t j = 0; j < plen; j++) { - struct gossmap_node *n; - n = gossmap_nth_node(gossmap, f->path[j], !f->dirs[j]); - gossmap_node_get_id(gossmap, n, &pf->path_nodes[j]); - pf->path_scidds[j].scid - = gossmap_chan_scid(gossmap, f->path[j]); - pf->path_scidds[j].dir = f->dirs[j]; - } - - /* Calculate cumulative delays (backwards) */ - pf->cltv_delays = tal_arr(pf, u32, plen); - pf->cltv_delays[plen-1] = final_cltvs[i]; - for (int j = (int)plen-2; j >= 0; j--) { - pf->cltv_delays[j] = pf->cltv_delays[j+1] - + f->path[j+1]->half[f->dirs[j+1]].delay; - } - pf->amounts = tal_steal(pf, f->amounts); - pf->success_prob = f->success_prob; - - /* Payment keeps a list of its flows. */ - list_add(&payment->flows, &pf->list); - - /* First time they see this: annotate important points */ - payflow_note(pf, LOG_INFORM, - "amount=%s prob=%.3lf fees=%s delay=%u path=%s", - fmt_amount_msat(tmpctx, payflow_delivered(pf)), - pf->success_prob, - fmt_amount_msat(tmpctx, payflow_fee(pf)), - pf->cltv_delays[0] - pf->cltv_delays[plen-1], - flow_path_annotated(tmpctx, pf)); - - /* Increase totals for payment */ - if(!amount_msat_add(&payment->total_sent, - payment->total_sent, - pf->amounts[0])) - { - // TODO: fail this call and notifiy the plugin - assert(0); - } - if(!amount_msat_add(&payment->total_delivering, - payment->total_delivering, - payflow_delivered(pf))) - { - // TODO: fail this call and notifiy the plugin - assert(0); - } - - /* We keep a global map to identify notifications - * about this flow. */ - payflow_map_add(pay_plugin->payflow_map, pf); - - /* record these HTLC along the flow path */ - commit_htlc_payflow(pay_plugin->chan_extra_map, pf); - - tal_add_destructor(pf, destroy_payment_flow); - } - tal_free(flows); -} - -static bitmap *make_disabled_bitmap(const tal_t *ctx, - const struct gossmap *gossmap, - const struct short_channel_id *scids) -{ - bitmap *disabled - = tal_arrz(ctx, bitmap, - BITMAP_NWORDS(gossmap_max_chan_idx(gossmap))); - - for (size_t i = 0; i < tal_count(scids); i++) { - struct gossmap_chan *c = gossmap_find_chan(gossmap, &scids[i]); - if (c) - bitmap_set_bit(disabled, gossmap_chan_idx(gossmap, c)); - } - return disabled; -} - - -static u64 flows_worst_delay(struct flow **flows) -{ - u64 maxdelay = 0; - for (size_t i = 0; i < tal_count(flows); i++) { - u64 delay = flow_delay(flows[i]); - if (delay > maxdelay) - maxdelay = delay; - } - return maxdelay; -} - -/* FIXME: If only path has channels marked disabled, we should try... */ -static bool disable_htlc_violations_oneflow(struct payment *p, - const struct flow *flow, - const struct gossmap *gossmap, - bitmap *disabled) -{ - bool disabled_some = false; - - for (size_t i = 0; i < tal_count(flow->path); i++) { - const struct half_chan *h = &flow->path[i]->half[flow->dirs[i]]; - struct short_channel_id scid; - const char *reason; - - if (!h->enabled) - reason = "channel_update said it was disabled"; - else if (amount_msat_greater_fp16(flow->amounts[i], h->htlc_max)) - reason = "htlc above maximum"; - else if (amount_msat_less_fp16(flow->amounts[i], h->htlc_min)) - reason = "htlc below minimum"; - else - continue; - - scid = gossmap_chan_scid(gossmap, flow->path[i]); - payment_disable_chan(p, scid, LOG_INFORM, "%s", reason); - /* Add to existing bitmap */ - bitmap_set_bit(disabled, - gossmap_chan_idx(gossmap, flow->path[i])); - disabled_some = true; - } - return disabled_some; -} - -/* If we can't use one of these flows because we hit limits, we disable that - * channel for future searches and return false */ -static bool disable_htlc_violations(struct payment *payment, - struct flow **flows, - const struct gossmap *gossmap, - bitmap *disabled) -{ - bool disabled_some = false; - - /* We continue through all of them, to disable many at once. */ - for (size_t i = 0; i < tal_count(flows); i++) { - disabled_some |= disable_htlc_violations_oneflow(payment, flows[i], - gossmap, - disabled); - } - return disabled_some; -} - -const char *add_payflows(const tal_t *ctx, struct payment *p, - struct amount_msat amount_to_deliver, - struct amount_msat feebudget, bool is_entire_payment, - enum jsonrpc_errcode *ecode) -{ - bitmap *disabled; - const struct gossmap_node *src, *dst; - char *errmsg, *fail = NULL; - - disabled = make_disabled_bitmap(tmpctx, pay_plugin->gossmap, - p->disabled_scids); - src = gossmap_find_node(pay_plugin->gossmap, &pay_plugin->my_id); - if (!src) { - *ecode = PAY_ROUTE_NOT_FOUND; - return tal_fmt(ctx, "We don't have any channels."); - } - dst = gossmap_find_node(pay_plugin->gossmap, &p->destination); - if (!dst) { - *ecode = PAY_ROUTE_NOT_FOUND; - return tal_fmt(ctx, - "Destination is unknown in the network gossip."); - } - - /* probability "bugdet". We will prefer solutions whose probability of - * success is above this value. */ - double min_prob_success = p->min_prob_success; - - while (!amount_msat_zero(amount_to_deliver)) { - struct flow **flows = minflow( - tmpctx, pay_plugin->gossmap, src, dst, - pay_plugin->chan_extra_map, disabled, amount_to_deliver, - feebudget, min_prob_success, p->delay_feefactor, - p->base_fee_penalty, p->prob_cost_factor, &errmsg); - if (!flows) { - *ecode = PAY_ROUTE_NOT_FOUND; - - /* We fail to allocate a portion of the payment, cleanup - * previous payflows. */ - // FIXME wouldn't it be better to put these payflows - // into a tal ctx with a destructor? - fail = tal_fmt( - ctx, - "minflow couldn't find a feasible flow for %s, %s", - fmt_amount_msat(tmpctx, amount_to_deliver), - errmsg); - goto function_fail; - } - - /* `delivering` could be smaller than `amount_to_deliver` - * because minflow does not count fees when constraining flows. - * Try to redistribute the missing amount among the optimal - * routes. */ - struct amount_msat delivering; - - if (!flows_fit_amount(tmpctx, &delivering, flows, - amount_to_deliver, pay_plugin->gossmap, - pay_plugin->chan_extra_map, &errmsg)) { - fail = tal_fmt(ctx, - "(%s, line %d) flows_fit_amount failed " - "with error: %s", - __PRETTY_FUNCTION__, __LINE__, errmsg); - goto function_fail; - } - - /* Are we unhappy? */ - double prob = - flowset_probability(tmpctx, flows, pay_plugin->gossmap, - pay_plugin->chan_extra_map, &errmsg); - if (prob < 0) { - plugin_err(pay_plugin->plugin, - "flow_set_probability failed: %s", errmsg); - } - struct amount_msat fee; - if (!flowset_fee(&fee, flows)) { - plugin_err(pay_plugin->plugin, "flowset_fee failed"); - } - u64 delay = flows_worst_delay(flows) + p->final_cltv; - - payment_note(p, LOG_INFORM, - "we have computed a set of %ld flows with " - "probability %.3lf, fees %s and delay %ld", - tal_count(flows), prob, - fmt_amount_msat(tmpctx, fee), - delay); - - if (amount_msat_greater(fee, feebudget)) { - *ecode = PAY_ROUTE_TOO_EXPENSIVE; - fail = tal_fmt( - ctx, - "Fee exceeds our fee budget, " - "fee = %s (maxfee = %s)", - fmt_amount_msat(tmpctx, fee), - fmt_amount_msat(tmpctx, feebudget)); - goto function_fail; - } - if (delay > p->maxdelay) { - /* FIXME: What is a sane limit? */ - if (p->delay_feefactor > 1000) { - *ecode = PAY_ROUTE_TOO_EXPENSIVE; - fail = tal_fmt( - ctx, - "CLTV delay exceeds our CLTV budget, " - "delay = %" PRIu64 " (maxdelay = %u)", - delay, p->maxdelay); - goto function_fail; - } - - p->delay_feefactor *= 2; - payment_note(p, LOG_INFORM, - "delay %" PRIu64 - " exceeds our max %u, so doubling " - "delay_feefactor to %f", - delay, p->maxdelay, p->delay_feefactor); - - continue; // retry - } - - /* Now we check for min/max htlc violations, and - * excessive htlc counts. It would be more efficient - * to do this inside minflow(), but the diagnostics here - * are far better, since we can report min/max which - * *actually* made us reconsider. */ - if (disable_htlc_violations(p, flows, pay_plugin->gossmap, - disabled)) { - continue; // retry - } - - /* This can adjust amounts and final cltv for each flow, - * to make it look like it's going elsewhere */ - // FIXME adding shadow fees after flows_fit_amount could mean - // that we end up again with over-commitments - const u32 *final_cltvs = shadow_additions( - tmpctx, pay_plugin->gossmap, p, flows, is_entire_payment); - - /* OK, we are happy with these flows: convert to - * pay_flows in the current payment, to outlive the - * current gossmap. */ - convert_and_attach_flows(p, pay_plugin->gossmap, flows, - final_cltvs, &p->next_partid); - if (prob < 1e-10) { - // this last flow probability is too small for division - min_prob_success = 1.0; - } else { - /* prob here is a conditional probability, the next - * round of flows will have a conditional probability - * prob2 and we would like that - * prob*prob2 >= min_prob_success - * hence min_prob_success/prob becomes the next - * iteration's target. */ - min_prob_success = MIN(1.0, min_prob_success / prob); - } - if (!amount_msat_sub(&feebudget, feebudget, fee)) { - plugin_err( - pay_plugin->plugin, - "%s: cannot substract feebudget (%s) - fee(%s)", - __PRETTY_FUNCTION__, - fmt_amount_msat(tmpctx, feebudget), - fmt_amount_msat(tmpctx, fee)); - } - if (!amount_msat_sub(&amount_to_deliver, amount_to_deliver, - delivering)) { - // If we allow overpayment we might let some bugs - // get through. - plugin_err(pay_plugin->plugin, - "%s: minflow has produced an overpayment, " - "amount_to_deliver=%s delivering=%s", - __PRETTY_FUNCTION__, - fmt_amount_msat(tmpctx, amount_to_deliver), - fmt_amount_msat(tmpctx, delivering)); - } - } - return NULL; - -function_fail: - payment_remove_flows(p, PAY_FLOW_NOT_STARTED); - return fail; -} - -const char *flow_path_to_str(const tal_t *ctx, const struct pay_flow *flow) -{ - char *s = tal_strdup(ctx, ""); - for (size_t i = 0; i < tal_count(flow->path_scidds); i++) { - tal_append_fmt(&s, "-%s->", - fmt_short_channel_id(tmpctx, - flow->path_scidds[i].scid)); - } - return s; -} - -/* How much does this flow deliver to destination? */ -struct amount_msat payflow_delivered(const struct pay_flow *flow) -{ - return flow->amounts[tal_count(flow->amounts)-1]; -} - -/* How much does this flow pay in fees? */ -struct amount_msat payflow_fee(const struct pay_flow *pf) -{ - struct amount_msat fee; - - if (!amount_msat_sub(&fee, pf->amounts[0], payflow_delivered(pf))) - abort(); - return fee; -} - -static struct pf_result *pf_resolve(struct pay_flow *pf, - enum pay_flow_state oldstate, - enum pay_flow_state newstate, - bool reconsider) -{ - assert(pf->state == oldstate); - pf->state = newstate; - - /* If it didn't deliver, remove from totals */ - if (pf->state != PAY_FLOW_SUCCESS) { - if(!amount_msat_sub(&pf->payment->total_delivering, - pf->payment->total_delivering, - payflow_delivered(pf))) - { - // TODO: fail this call and notifiy the plugin - assert(0); - } - if(!amount_msat_sub(&pf->payment->total_sent, - pf->payment->total_sent, - pf->amounts[0])) - { - // TODO: fail this call and notifiy the plugin - assert(0); - } - } - - /* Subtract HTLC counters from the path */ - remove_htlc_payflow(pay_plugin->chan_extra_map, pf); - /* And remove from the global map: no more notifications about this! */ - payflow_map_del(pay_plugin->payflow_map, pf); - - if (reconsider) - payment_reconsider(pf->payment); - return NULL; -} - -/* We've been notified that a pay_flow has failed */ -struct pf_result *pay_flow_failed(struct pay_flow *pf) -{ - return pf_resolve(pf, PAY_FLOW_IN_PROGRESS, PAY_FLOW_FAILED, true); -} - -/* We've been notified that a pay_flow has failed, payment is done. */ -struct pf_result *pay_flow_failed_final(struct pay_flow *pf, - enum jsonrpc_errcode final_error, - const char *final_msg TAKES) -{ - pf->final_error = final_error; - pf->final_msg = tal_strdup(pf, final_msg); - - return pf_resolve(pf, PAY_FLOW_IN_PROGRESS, PAY_FLOW_FAILED_FINAL, true); -} - -/* We've been notified that a pay_flow has failed, adding gossip. */ -struct pf_result *pay_flow_failed_adding_gossip(struct pay_flow *pf) -{ - /* Don't bother reconsidering until addgossip done */ - return pf_resolve(pf, PAY_FLOW_IN_PROGRESS, PAY_FLOW_FAILED_GOSSIP_PENDING, - false); -} - -/* We've finished adding gossip. */ -struct pf_result *pay_flow_finished_adding_gossip(struct pay_flow *pf) -{ - assert(pf->state == PAY_FLOW_FAILED_GOSSIP_PENDING); - pf->state = PAY_FLOW_FAILED; - - payment_reconsider(pf->payment); - return NULL; -} - -/* We've been notified that a pay_flow has succeeded. */ -struct pf_result *pay_flow_succeeded(struct pay_flow *pf, - const struct preimage *preimage) -{ - pf->payment_preimage = tal_dup(pf, struct preimage, preimage); - return pf_resolve(pf, PAY_FLOW_IN_PROGRESS, PAY_FLOW_SUCCESS, true); -} diff --git a/plugins/renepay/pay_flow.h b/plugins/renepay/pay_flow.h deleted file mode 100644 index 71ea459c560a..000000000000 --- a/plugins/renepay/pay_flow.h +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef LIGHTNING_PLUGINS_RENEPAY_PAY_FLOW_H -#define LIGHTNING_PLUGINS_RENEPAY_PAY_FLOW_H -#include "config.h" -#include -#include -#include -#include -#include - -/* There are several states a payment can be in */ -enum pay_flow_state { - /* Created, but not sent to sendpay */ - PAY_FLOW_NOT_STARTED, - /* Normally, here */ - PAY_FLOW_IN_PROGRESS, - /* Failed: we've fed the data back to the uncertainly network. */ - PAY_FLOW_FAILED, - /* Failed from the final node, so give up: see ->final_error. */ - PAY_FLOW_FAILED_FINAL, - /* Failed, but still updating gossip. */ - PAY_FLOW_FAILED_GOSSIP_PENDING, - /* Succeeded: see ->payment_preimage. */ - PAY_FLOW_SUCCESS, -}; -#define NUM_PAY_FLOW (PAY_FLOW_SUCCESS + 1) - -/* This is like a struct flow, but independent of gossmap, and contains - * all we need to actually send the part payment. */ -struct pay_flow { - /* Linked from payment->flows */ - struct list_node list; - - enum pay_flow_state state; - /* Iff state == PAY_FLOW_SUCCESS */ - const struct preimage *payment_preimage; - /* Iff state == PAY_FAILED_FINAL */ - enum jsonrpc_errcode final_error; - const char *final_msg; - - /* So we can be an independent object for callbacks. */ - struct payment * payment; - - /* Information to link this flow to a unique sendpay. */ - struct payflow_key - { - struct sha256 payment_hash; - u64 groupid; - u64 partid; - } key; - - /* The series of channels and nodes to traverse. */ - struct short_channel_id_dir *path_scidds; - struct node_id *path_nodes; - /* CLTV delays for each hop */ - u32 *cltv_delays; - /* The amounts at each step */ - struct amount_msat *amounts; - /* Probability estimate (0-1) */ - double success_prob; -}; - -static inline struct payflow_key -payflow_key(const struct sha256 *hash, u64 groupid, u64 partid) -{ - struct payflow_key k= {*hash,groupid,partid}; - return k; -} - -static inline const char* fmt_payflow_key( - const tal_t *ctx, - const struct payflow_key * k) -{ - char *str = tal_fmt( - ctx, - "key: groupid=%"PRIu64", partid=%"PRIu64", payment_hash=%s", - k->groupid,k->partid, - fmt_sha256(ctx, &k->payment_hash)); - return str; -} - - -static inline const struct payflow_key * -payflow_get_key(const struct pay_flow * pf) -{ - return &pf->key; -} - -static inline size_t payflow_key_hash(const struct payflow_key *k) -{ - return k->payment_hash.u.u32[0] ^ (k->groupid << 32) ^ k->partid; -} - -static inline bool payflow_key_equal(const struct pay_flow *pf, - const struct payflow_key *k) -{ - return pf->key.partid==k->partid && pf->key.groupid==k->groupid - && sha256_eq(&pf->key.payment_hash, &k->payment_hash); -} - -HTABLE_DEFINE_TYPE(struct pay_flow, - payflow_get_key, payflow_key_hash, payflow_key_equal, - payflow_map); - -/* Add one or more IN_PROGRESS pay_flow to payment. Return NULL if we did, - * otherwise an error message (and sets *ecode). */ -const char *add_payflows(const tal_t *ctx, - struct payment *payment, - struct amount_msat amount, - struct amount_msat feebudget, - bool is_entire_payment, - enum jsonrpc_errcode *ecode); - -/* Each payflow is eventually terminated by one of these. - * - * To make sure you deal with flows, they return a special type. - */ - -/* We've been notified that a pay_flow has failed */ -struct pf_result *pay_flow_failed(struct pay_flow *pf STEALS); -/* We've been notified that a pay_flow has failed, payment is done. */ -struct pf_result *pay_flow_failed_final(struct pay_flow *pf STEALS, - enum jsonrpc_errcode final_error, - const char *final_msg TAKES); -/* We've been notified that a pay_flow has failed, adding gossip. */ -struct pf_result *pay_flow_failed_adding_gossip(struct pay_flow *pf STEALS); -/* We've finished adding gossip. */ -struct pf_result *pay_flow_finished_adding_gossip(struct pay_flow *pf STEALS); -/* We've been notified that a pay_flow has succeeded. */ -struct pf_result *pay_flow_succeeded(struct pay_flow *pf STEALS, - const struct preimage *preimage); - -/* Formatting helpers */ -const char *flow_path_to_str(const tal_t *ctx, const struct pay_flow *flow); - -/* How much does this flow deliver to destination? */ -struct amount_msat payflow_delivered(const struct pay_flow *flow); - -/* At what cost? */ -struct amount_msat payflow_fee(const struct pay_flow *flow); - -#endif /* LIGHTNING_PLUGINS_RENEPAY_PAY_FLOW_H */ diff --git a/plugins/renepay/route.c b/plugins/renepay/route.c new file mode 100644 index 000000000000..6e8498139d3d --- /dev/null +++ b/plugins/renepay/route.c @@ -0,0 +1,112 @@ +#include "config.h" +#include +#include + +struct route *new_route(const tal_t *ctx, struct payment *payment, u32 groupid, + u32 partid, struct sha256 payment_hash, + struct amount_msat amount, + struct amount_msat amount_sent) +{ + struct route *route = tal(ctx, struct route); + route->payment = payment; + route->key.partid = partid; + route->key.groupid = groupid; + route->key.payment_hash = payment_hash; + + route->final_error = LIGHTNINGD; + route->final_msg = NULL; + route->hops = NULL; + route->success_prob = 0.0; + route->result = NULL; + + route->amount = amount; + route->amount_sent = amount_sent; + return route; +} + +/* Construct a route from a flow. + * + * @ctx: allocator + * @payment: NULL or the payment this route will point to + * @groupid, @partid, @payment_hash: unique identification keys for this route + * @final_cltv: final delay required by the payment + * @gossmap: global gossmap + * @flow: the flow to convert to route */ +struct route *flow_to_route(const tal_t *ctx, struct payment *payment, + u32 groupid, u32 partid, struct sha256 payment_hash, + u32 final_cltv, struct gossmap *gossmap, + struct flow *flow) +{ + struct route *route = + new_route(ctx, payment, groupid, partid, payment_hash, + AMOUNT_MSAT(0), AMOUNT_MSAT(0)); + + size_t pathlen = tal_count(flow->path); + route->hops = tal_arr(route, struct route_hop, pathlen); + + for (size_t i = 0; i < pathlen; i++) { + struct route_hop *hop = &route->hops[i]; + struct gossmap_node *n; + n = gossmap_nth_node(gossmap, flow->path[i], !flow->dirs[i]); + gossmap_node_get_id(gossmap, n, &hop->node_id); + + hop->scid = gossmap_chan_scid(gossmap, flow->path[i]); + hop->direction = flow->dirs[i]; + } + + /* Calculate cumulative delays (backwards) */ + route->hops[pathlen - 1].delay = final_cltv; + route->hops[pathlen - 1].amount = flow->amount; + + for (int i = (int)pathlen - 2; i >= 0; i--) { + const struct half_chan *h = flow_edge(flow, i + 1); + + route->hops[i].delay = route->hops[i + 1].delay + h->delay; + route->hops[i].amount = route->hops[i + 1].amount; + if (!amount_msat_add_fee(&route->hops[i].amount, h->base_fee, + h->proportional_fee)) + goto function_fail; + } + route->success_prob = flow->success_prob; + route->amount = route->hops[pathlen - 1].amount; + route->amount_sent = route->hops[0].amount; + return route; + +function_fail: + return tal_free(route); +} + +struct route **flows_to_routes(const tal_t *ctx, struct payment *payment, + u32 groupid, u32 partid, + struct sha256 payment_hash, u32 final_cltv, + struct gossmap *gossmap, struct flow **flows) +{ + assert(gossmap); + assert(flows); + const size_t N = tal_count(flows); + struct route **routes = tal_arr(ctx, struct route *, N); + for (size_t i = 0; i < N; i++) { + routes[i] = + flow_to_route(routes, payment, groupid, partid++, + payment_hash, final_cltv, gossmap, flows[i]); + if (!routes[i]) + goto function_fail; + } + return routes; + +function_fail: + return tal_free(routes); +} + +const char *fmt_route_path(const tal_t *ctx, const struct route *route) +{ + char *s = tal_strdup(ctx, ""); + const size_t pathlen = tal_count(route->hops); + for (size_t i = 0; i < pathlen; i++) { + const struct short_channel_id_dir scidd = + hop_to_scidd(&route->hops[i]); + tal_append_fmt(&s, "-%s->", + fmt_short_channel_id(tmpctx, scidd.scid)); + } + return s; +} diff --git a/plugins/renepay/route.h b/plugins/renepay/route.h new file mode 100644 index 000000000000..36ae5b2de4dd --- /dev/null +++ b/plugins/renepay/route.h @@ -0,0 +1,177 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_ROUTE_H +#define LIGHTNING_PLUGINS_RENEPAY_ROUTE_H + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct payment; + +/* States returned by listsendpays, waitsendpay, etc. */ +enum sendpay_result_status { + SENDPAY_PENDING, + SENDPAY_COMPLETE, + SENDPAY_FAILED +}; + +/* A parsed version of the possible outcomes that a sendpay / payment may + * result in. It excludes the redundant fields such as payment_hash and partid + * which are already present in the `struct payment` itself. */ +struct payment_result { + /* DB internal id */ + // TODO check all this variables + u64 id; + struct preimage *payment_preimage; + enum sendpay_result_status status; + struct amount_msat amount_sent; + enum jsonrpc_errcode code; + const char *failcodename; + enum onion_wire failcode; + const u8 *raw_message; + const char *message; + u32 *erring_index; + struct node_id *erring_node; + struct short_channel_id *erring_channel; + int *erring_direction; +}; + +/* Describes a payment route. It points to a unique sendpay and payment. */ +struct route { + enum jsonrpc_errcode final_error; + const char *final_msg; + + /* So we can be an independent object for callbacks. */ + struct payment *payment; + + /* Information to link this flow to a unique sendpay. */ + struct routekey { + struct sha256 payment_hash; + u64 groupid; + u64 partid; + } key; + + /* The series of channels and nodes to traverse. */ + struct route_hop *hops; + + /* amounts are redundant here if we know the hops, however sometimes we + * don't know the hops, eg. by calling listsendpays */ + struct amount_msat amount, amount_sent; + + /* Probability estimate (0-1) */ + double success_prob; + + /* result of waitsenday */ + struct payment_result *result; +}; + +static inline struct routekey routekey(const struct sha256 *hash, u64 groupid, + u64 partid) +{ + struct routekey k = {*hash, groupid, partid}; + return k; +} + +static inline const char *fmt_routekey(const tal_t *ctx, + const struct routekey *k) +{ + char *str = tal_fmt( + ctx, + "key: groupid=%" PRIu64 ", partid=%" PRIu64 ", payment_hash=%s", + k->groupid, k->partid, + fmt_sha256(ctx, &k->payment_hash)); + return str; +} + +static inline const struct routekey *route_get_key(const struct route *route) +{ + return &route->key; +} + +static inline size_t routekey_hash(const struct routekey *k) +{ + return k->payment_hash.u.u32[0] ^ (k->groupid << 32) ^ k->partid; +} + +static inline bool routekey_equal(const struct route *route, + const struct routekey *k) +{ + return route->key.partid == k->partid && + route->key.groupid == k->groupid && + sha256_eq(&route->key.payment_hash, &k->payment_hash); +} + +HTABLE_DEFINE_TYPE(struct route, route_get_key, routekey_hash, routekey_equal, + route_map); + +struct route *new_route(const tal_t *ctx, struct payment *payment, u32 groupid, + u32 partid, struct sha256 payment_hash, + struct amount_msat amount, + struct amount_msat amount_sent); + +struct route *flow_to_route(const tal_t *ctx, struct payment *payment, + u32 groupid, u32 partid, struct sha256 payment_hash, + u32 final_cltv, struct gossmap *gossmap, + struct flow *flow); + +struct route **flows_to_routes(const tal_t *ctx, struct payment *payment, + u32 groupid, u32 partid, + struct sha256 payment_hash, u32 final_cltv, + struct gossmap *gossmap, struct flow **flows); + +static inline struct short_channel_id_dir +hop_to_scidd(const struct route_hop *hop) +{ + struct short_channel_id_dir scidd; + scidd.scid = hop->scid; + scidd.dir = hop->direction; + return scidd; +} + +const char *fmt_route_path(const tal_t *ctx, const struct route *route); + +static inline struct amount_msat route_delivers(const struct route *route) +{ + assert(route); + if (route->hops && tal_count(route->hops) > 0) + assert(amount_msat_eq( + route->amount, + route->hops[tal_count(route->hops) - 1].amount)); + return route->amount; +} +static inline struct amount_msat route_sends(const struct route *route) +{ + assert(route); + if (route->hops && tal_count(route->hops) > 0) + assert( + amount_msat_eq(route->amount_sent, route->hops[0].amount)); + return route->amount_sent; +} +static inline struct amount_msat route_fees(const struct route *route) +{ + struct amount_msat fees; + if (!amount_msat_sub(&fees, route_sends(route), + route_delivers(route))) { + assert(0 && "route sends is greater than delivers"); + } + return fees; +} +static inline u32 route_delay(const struct route *route) +{ + assert(route); + assert(route->hops); + assert(tal_count(route->hops) > 0); + const size_t pathlen = tal_count(route->hops); + assert(route->hops[0].delay >= route->hops[pathlen - 1].delay); + return route->hops[0].delay - route->hops[pathlen - 1].delay; +} + +#endif /* LIGHTNING_PLUGINS_RENEPAY_ROUTE_H */ From 862360d2d3d90fcac212ba2874645d8f0deaa7b7 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 15:03:12 +0100 Subject: [PATCH 04/31] renepay: rename uncertainty_network for uncertanty --- plugins/renepay/{uncertainty_network.c => uncertainty.c} | 0 plugins/renepay/{uncertainty_network.h => uncertainty.h} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename plugins/renepay/{uncertainty_network.c => uncertainty.c} (100%) rename plugins/renepay/{uncertainty_network.h => uncertainty.h} (100%) diff --git a/plugins/renepay/uncertainty_network.c b/plugins/renepay/uncertainty.c similarity index 100% rename from plugins/renepay/uncertainty_network.c rename to plugins/renepay/uncertainty.c diff --git a/plugins/renepay/uncertainty_network.h b/plugins/renepay/uncertainty.h similarity index 100% rename from plugins/renepay/uncertainty_network.h rename to plugins/renepay/uncertainty.h From b313200f1f3688d5671585fce00555d831e710a6 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 15:05:06 +0100 Subject: [PATCH 05/31] renepay: refactor uncertainty API The uncertainty structure is updated based on the result of a route, previously chan_extra and pay_flow were used instead. --- plugins/renepay/uncertainty.c | 397 +++++++++++----------------------- plugins/renepay/uncertainty.h | 108 ++++----- 2 files changed, 177 insertions(+), 328 deletions(-) diff --git a/plugins/renepay/uncertainty.c b/plugins/renepay/uncertainty.c index 785782896f99..9d4bdd84e1f6 100644 --- a/plugins/renepay/uncertainty.c +++ b/plugins/renepay/uncertainty.c @@ -1,317 +1,164 @@ #include "config.h" -#include -#include -#include -#include +#include -static bool chan_extra_check_invariants(struct chan_extra *ce) +void uncertainty_route_success(struct uncertainty *uncertainty, + const struct route *route) { - bool all_ok = true; - for(int i=0;i<2;++i) - { - all_ok &= amount_msat_less_eq(ce->half[i].known_min, - ce->half[i].known_max); - all_ok &= amount_msat_less_eq(ce->half[i].known_max, - ce->capacity); - } - struct amount_msat diff_cb,diff_ca; + if (!route->hops) + return; - all_ok &= amount_msat_sub(&diff_cb,ce->capacity,ce->half[1].known_max); - all_ok &= amount_msat_sub(&diff_ca,ce->capacity,ce->half[1].known_min); + for (size_t i = 0; i < tal_count(route->hops); i++) { + const struct route_hop *hop = &route->hops[i]; + struct short_channel_id_dir scidd = {hop->scid, hop->direction}; - all_ok &= amount_msat_eq(ce->half[0].known_min,diff_cb); - all_ok &= amount_msat_eq(ce->half[0].known_max,diff_ca); - return all_ok; + // FIXME: check errors here, report back + chan_extra_sent_success(uncertainty->chan_extra_map, &scidd, + route->hops[i].amount); + } } - - -/* Checks the entire uncertainty network for invariant violations. */ -bool uncertainty_network_check_invariants(struct chan_extra_map *chan_extra_map) +void uncertainty_remove_htlcs(struct uncertainty *uncertainty, + const struct route *route) { - bool all_ok = true; + // FIXME: how could we get the route details of a sendpay that we did + // not send? + if (!route->hops) + return; - struct chan_extra_map_iter it; - for(struct chan_extra *ce = chan_extra_map_first(chan_extra_map,&it); - ce && all_ok; - ce=chan_extra_map_next(chan_extra_map,&it)) - { - all_ok &= chan_extra_check_invariants(ce); - } + const size_t pathlen = tal_count(route->hops); + for (size_t i = 0; i < pathlen; i++) { + const struct route_hop *hop = &route->hops[i]; + struct short_channel_id_dir scidd = {hop->scid, hop->direction}; - return all_ok; + // FIXME: check error + chan_extra_remove_htlc(uncertainty->chan_extra_map, &scidd, + hop->amount); + } } -static void add_hintchan( - struct chan_extra_map *chan_extra_map, - struct gossmap_localmods *local_gossmods, - const struct node_id *src, - const struct node_id *dst, - u16 cltv_expiry_delta, - const struct short_channel_id scid, - u32 fee_base_msat, - u32 fee_proportional_millionths) +void uncertainty_commit_htlcs(struct uncertainty *uncertainty, + const struct route *route) { - int dir = node_id_cmp(src, dst) < 0 ? 0 : 1; - - struct chan_extra *ce = chan_extra_map_get(chan_extra_map, - scid); - if(!ce) - { - /* this channel is not public, we don't know his capacity */ - // TODO(eduardo): one possible solution is set the capacity to - // MAX_CAP and the state to [0,MAX_CAP]. Alternatively we set - // the capacity to amoung and state to [amount,amount]. - ce = new_chan_extra(chan_extra_map, - scid, - MAX_CAP); - /* FIXME: features? */ - gossmap_local_addchan(local_gossmods, - src, dst, scid, NULL); - gossmap_local_updatechan(local_gossmods, - scid, - /* We assume any HTLC is allowed */ - AMOUNT_MSAT(0), MAX_CAP, - fee_base_msat, fee_proportional_millionths, - cltv_expiry_delta, - true, - dir); - } + // FIXME: how could we get the route details of a sendpay that we did + // not send? + if (!route->hops) + return; - /* It is wrong to assume that this channel has sufficient capacity! - * Doing so leads to knowledge updates in which the known min liquidity - * is greater than the channel's capacity. */ - // chan_extra_can_send(chan_extra_map,scid,dir,amount); -} + const size_t pathlen = tal_count(route->hops); + for (size_t i = 0; i < pathlen; i++) { + const struct route_hop *hop = &route->hops[i]; + struct short_channel_id_dir scidd = {hop->scid, hop->direction}; -/* Add routehints provided by bolt11 */ -void uncertainty_network_add_routehints( - struct chan_extra_map *chan_extra_map, - const struct route_info **routes, - struct payment *p) -{ - for (size_t i = 0; i < tal_count(routes); i++) { - /* Each one, presumably, leads to the destination */ - const struct route_info *r = routes[i]; - const struct node_id *end = & p->destination; - for (int j = tal_count(r)-1; j >= 0; j--) { - add_hintchan( - chan_extra_map, - p->local_gossmods, - &r[j].pubkey, end, - r[j].cltv_expiry_delta, - r[j].short_channel_id, - r[j].fee_base_msat, - r[j].fee_proportional_millionths); - end = &r[j].pubkey; - } + // FIXME: check error + chan_extra_commit_htlc(uncertainty->chan_extra_map, &scidd, + hop->amount); } } -/* Mirror the gossmap in the public uncertainty network. - * result: Every channel in gossmap must have associated data in chan_extra_map, - * while every channel in chan_extra_map is also registered in gossmap. - * */ -void uncertainty_network_update( - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map) +void uncertainty_channel_can_send(struct uncertainty *uncertainty, + struct route *route, u32 erridx) { - const tal_t* this_ctx = tal(tmpctx,tal_t); - - // For each chan in chan_extra_map remove if not in the gossmap - struct short_channel_id *del_list - = tal_arr(this_ctx,struct short_channel_id,0); - - struct chan_extra_map_iter it; - for(struct chan_extra *ce = chan_extra_map_first(chan_extra_map,&it); - ce; - ce=chan_extra_map_next(chan_extra_map,&it)) - { - struct gossmap_chan * chan = gossmap_find_chan(gossmap,&ce->scid); - /* Only if the channel is not in the gossmap and there are not - * HTLCs pending we can remove it. */ - if(!chan && !chan_extra_is_busy(ce)) - { - // TODO(eduardo): is this efficiently implemented? - // otherwise i'll use a ccan list - tal_arr_expand(&del_list, ce->scid); - } - } + if (!route->hops) + return; - for(size_t i=0;iplugin,"%s (line %d) unexpected chan_extra ce is NULL", - __PRETTY_FUNCTION__, - __LINE__); - } - chan_extra_map_del(chan_extra_map, ce); - tal_free(ce); - // TODO(eduardo): if you had added a destructor to ce, you could have removed - // the ce from the map automatically. + const size_t pathlen = tal_count(route->hops); + for (size_t i = 0; i < erridx && i < pathlen; i++) { + const struct route_hop *hop = &route->hops[i]; + struct short_channel_id_dir scidd = {hop->scid, hop->direction}; + // FIXME: check error + chan_extra_can_send(uncertainty->chan_extra_map, &scidd); } +} +void uncertainty_channel_cannot_send(struct uncertainty *uncertainty, + struct short_channel_id scid, + int direction) +{ + struct short_channel_id_dir scidd = {scid, direction}; + // FIXME: check error + chan_extra_cannot_send(uncertainty->chan_extra_map, &scidd); +} - // For each channel in the gossmap, create a extra data in - // chan_extra_map - for(struct gossmap_chan *chan = gossmap_first_chan(gossmap); - chan; - chan=gossmap_next_chan(gossmap,chan)) - { - struct short_channel_id scid = - gossmap_chan_scid(gossmap,chan); - struct chan_extra *ce = chan_extra_map_get(chan_extra_map, - gossmap_chan_scid(gossmap,chan)); - if(!ce) - { +void uncertainty_update(struct uncertainty *uncertainty, + struct gossmap *gossmap) +{ + // FIXME: after running for some time we might find some channels in + // chan_extra_map that are not needed and do not exist in the gossmap + // for being private or closed. + + /* For each channel in the gossmap, create a extra data in + * chan_extra_map */ + for (struct gossmap_chan *chan = gossmap_first_chan(gossmap); chan; + chan = gossmap_next_chan(gossmap, chan)) { + struct short_channel_id scid = gossmap_chan_scid(gossmap, chan); + struct chan_extra *ce = + chan_extra_map_get(uncertainty->chan_extra_map, + gossmap_chan_scid(gossmap, chan)); + if (!ce) { struct amount_sat cap; struct amount_msat cap_msat; - if(!gossmap_chan_get_capacity(gossmap,chan,&cap)) - { - plugin_err(pay_plugin->plugin,"%s (line %d) unable to fetch channel capacity", - __PRETTY_FUNCTION__, - __LINE__); - } - if(!amount_sat_to_msat(&cap_msat,cap)) - { - plugin_err(pay_plugin->plugin,"%s (line %d) unable convert sat to msat", - __PRETTY_FUNCTION__, - __LINE__); - } - new_chan_extra(chan_extra_map,scid,cap_msat); + // FIXME: check errors + if (!gossmap_chan_get_capacity(gossmap, chan, &cap) || + !amount_sat_to_msat(&cap_msat, cap) || + !new_chan_extra(uncertainty->chan_extra_map, scid, + cap_msat)) + return; } } - tal_free(this_ctx); } -void uncertainty_network_flow_success( - struct chan_extra_map *chan_extra_map, - struct pay_flow *pf) +struct uncertainty *uncertainty_new(const tal_t *ctx) { - char *errmsg; - for (size_t i = 0; i < tal_count(pf->path_scidds); i++) - { - const char *old_state - = fmt_chan_extra_details(tmpctx, pay_plugin->chan_extra_map, - &pf->path_scidds[i]); - if (!chan_extra_sent_success(tmpctx, chan_extra_map, - &pf->path_scidds[i], - pf->amounts[i], &errmsg)) { - plugin_err(pay_plugin->plugin, - "chan_extra_sent_success failed: %s", - errmsg); - } - payflow_note(pf, LOG_INFORM, - "Success forwarding amount %s in channel %s, " - "state change %s -> %s", - fmt_amount_msat(tmpctx, pf->amounts[i]), - fmt_short_channel_id_dir(tmpctx, - &pf->path_scidds[i]), - old_state, - fmt_chan_extra_details(tmpctx, - pay_plugin->chan_extra_map, - &pf->path_scidds[i])); - } -} -/* All parts up to erridx succeeded, so we know something about min - * capacity! */ -void uncertainty_network_channel_can_send( - struct chan_extra_map * chan_extra_map, - struct pay_flow *pf, - u32 erridx) -{ - char *fail; - for (size_t i = 0; i < erridx; i++) - { - if (!chan_extra_can_send( - tmpctx, chan_extra_map, &pf->path_scidds[i], - &fail)) { - plugin_err(pay_plugin->plugin, - "chan_extra_can_send failed: %s", fail); - } - } -} + struct uncertainty *uncertainty = tal(ctx, struct uncertainty); + if (uncertainty == NULL) + goto function_fail; -void uncertainty_network_update_from_listpeerchannels(struct payment *p, - const struct short_channel_id_dir *scidd, - struct amount_msat max, - bool enabled, - const char *buf, - const jsmntok_t *chantok, - struct chan_extra_map *chan_extra_map) -{ - struct chan_extra *ce; - char *errmsg; + uncertainty->chan_extra_map = tal(uncertainty, struct chan_extra_map); + if (uncertainty->chan_extra_map == NULL) + goto function_fail; - if (!enabled) { - payment_disable_chan(p, scidd->scid, LOG_DBG, - "listpeerchannelks says not enabled"); - return; - } + chan_extra_map_init(uncertainty->chan_extra_map); - ce = chan_extra_map_get(chan_extra_map, scidd->scid); - if (!ce) { - const jsmntok_t *totaltok; - struct amount_msat capacity; + return uncertainty; - /* this channel is not public, but it belongs to us */ - totaltok = json_get_member(buf, chantok, "total_msat"); - if (!totaltok) { - errmsg = tal_fmt( - tmpctx, - "Failed to update channel from listpeerchannels " - "scid=%s, missing total_msat", - fmt_short_channel_id(tmpctx, scidd->scid)); - goto error; - } - if (!json_to_msat(buf, totaltok, &capacity)) { - errmsg = tal_fmt( - tmpctx, - "Failed to update channel from listpeerchannels " - "scid=%s, cannot parse total_msat", - fmt_short_channel_id(tmpctx, scidd->scid)); - goto error; - } +function_fail: + return tal_free(uncertainty); +} - ce = new_chan_extra(chan_extra_map, scidd->scid, capacity); - } +struct chan_extra_map * +uncertainty_get_chan_extra_map(struct uncertainty *uncertainty) +{ + // TODO: do we really need this function? + return uncertainty->chan_extra_map; +} - /* FIXME: There is a bug with us trying to send more down a local - * channel (after fees) than it has capacity. For now, we reduce - * our capacity by 1% of total, to give fee headroom. */ - if (!amount_msat_sub(&max, max, amount_msat_div(p->amount, 100))) - max = AMOUNT_MSAT(0); +/* Add channel to the Uncertainty Network if it doesn't already exist. */ +const struct chan_extra * +uncertainty_add_channel(struct uncertainty *uncertainty, + const struct short_channel_id scid, + struct amount_msat capacity) +{ + const struct chan_extra *ce = + chan_extra_map_get(uncertainty->chan_extra_map, scid); + if (ce) + return ce; - // TODO(eduardo): this does not include pending HTLC of previous - // payments! - /* We know min and max liquidity exactly now! */ - if (!chan_extra_set_liquidity(tmpctx, chan_extra_map, scidd, max, - &errmsg)) { - plugin_err(pay_plugin->plugin, - "chan_extra_set_liquidity failed: %s", errmsg); - } - return; + return new_chan_extra(uncertainty->chan_extra_map, scid, capacity); +} + +bool uncertainty_set_liquidity(struct uncertainty *uncertainty, + const struct short_channel_id_dir *scidd, + struct amount_msat amount) +{ + // FIXME check error + enum renepay_errorcode err = chan_extra_set_liquidity( + uncertainty->chan_extra_map, scidd, amount); - error: - plugin_log(pay_plugin->plugin, LOG_UNUSUAL, "%s", errmsg); + return err == RENEPAY_NOERROR; } -/* Forget ALL channels information by a fraction of the capacity. */ -void uncertainty_network_relax_fraction(struct chan_extra_map *chan_extra_map, - double fraction) +struct chan_extra *uncertainty_find_channel(struct uncertainty *uncertainty, + const struct short_channel_id scid) { - struct chan_extra_map_iter it; - char *fail; - for (struct chan_extra *ce = chan_extra_map_first(chan_extra_map, &it); - ce; ce = chan_extra_map_next(chan_extra_map, &it)) { - if (!chan_extra_relax_fraction(tmpctx, ce, fraction, &fail)) { - plugin_err(pay_plugin->plugin, - "chan_extra_relax_fraction failed for " - "channel %s: %s", - fmt_short_channel_id(tmpctx, ce->scid), - fail); - } - } + return chan_extra_map_get(uncertainty->chan_extra_map, scid); } diff --git a/plugins/renepay/uncertainty.h b/plugins/renepay/uncertainty.h index 8376676f59c0..c0807caf6dfd 100644 --- a/plugins/renepay/uncertainty.h +++ b/plugins/renepay/uncertainty.h @@ -1,55 +1,57 @@ -#ifndef LIGHTNING_PLUGINS_RENEPAY_UNCERTAINTY_NETWORK_H -#define LIGHTNING_PLUGINS_RENEPAY_UNCERTAINTY_NETWORK_H +#ifndef LIGHTNING_PLUGINS_RENEPAY_UNETWORK_H +#define LIGHTNING_PLUGINS_RENEPAY_UNETWORK_H #include "config.h" +#include #include -#include -#include -#include - -struct pay_flow; -struct route_info; - -/* Checks the entire uncertainty network for invariant violations. */ -bool uncertainty_network_check_invariants(struct chan_extra_map *chan_extra_map); - -/* Add routehints provided by bolt11 */ -void uncertainty_network_add_routehints( - struct chan_extra_map *chan_extra_map, - const struct route_info **routes, - struct payment *p); - -/* Mirror the gossmap in the public uncertainty network. - * result: Every channel in gossmap must have associated data in chan_extra_map, - * while every channel in chan_extra_map is also registered in gossmap. - * */ -void uncertainty_network_update( - const struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map); - -void uncertainty_network_flow_success( - struct chan_extra_map *chan_extra_map, - struct pay_flow *flow); - -/* All parts up to erridx succeeded, so we know something about min - * capacity! */ -void uncertainty_network_channel_can_send( - struct chan_extra_map * chan_extra_map, - struct pay_flow *flow, - u32 erridx); - -/* listpeerchannels gives us the certainty on local channels' capacity. Of course, - * this is racy and transient, but better than nothing! */ -void uncertainty_network_update_from_listpeerchannels(struct payment *p, - const struct short_channel_id_dir *scidd, - struct amount_msat max, - bool enabled, - const char *buf, - const jsmntok_t *chantok, - struct chan_extra_map *chan_extra_map); - -/* Forget ALL channels information by a fraction of the capacity. */ -void uncertainty_network_relax_fraction( - struct chan_extra_map* chan_extra_map, - double fraction); - -#endif /* LIGHTNING_PLUGINS_RENEPAY_UNCERTAINTY_NETWORK_H */ +#include +#include + +/* FIXME a hard coded constant to indicate a limit on any channel + capacity. Channels for which the capacity is unknown (because they are not + announced) use this value. It makes sense, because if we don't even know the + channel capacity the liquidity could be anything but it will never be greater + than the global number of msats. + It remains to be checked if this value does not lead to overflow somewhere in + the code. */ +#define MAX_CAPACITY (AMOUNT_MSAT(21000000 * MSAT_PER_BTC)) + +struct uncertainty { + struct chan_extra_map *chan_extra_map; +}; + +void uncertainty_route_success(struct uncertainty *uncertainty, + const struct route *route); +void uncertainty_remove_htlcs(struct uncertainty *uncertainty, + const struct route *route); + +void uncertainty_commit_htlcs(struct uncertainty *uncertainty, + const struct route *route); + +void uncertainty_channel_can_send(struct uncertainty *uncertainty, + struct route *route, u32 erridx); + +void uncertainty_channel_cannot_send(struct uncertainty *uncertainty, + struct short_channel_id scid, + int direction); + +void uncertainty_update(struct uncertainty *uncertainty, + struct gossmap *gossmap); + +struct uncertainty *uncertainty_new(const tal_t *ctx); + +struct chan_extra_map * +uncertainty_get_chan_extra_map(struct uncertainty *uncertainty); + +const struct chan_extra * +uncertainty_add_channel(struct uncertainty *uncertainty, + const struct short_channel_id scid, + struct amount_msat capacity); + +bool uncertainty_set_liquidity(struct uncertainty *uncertainty, + const struct short_channel_id_dir *scidd, + struct amount_msat amount); + +struct chan_extra *uncertainty_find_channel(struct uncertainty *uncertainty, + const struct short_channel_id scid); + +#endif /* LIGHTNING_PLUGINS_RENEPAY_UNETWORK_H */ From 66660883418522829a0f6eba16af4fb623b4393c Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 15:07:25 +0100 Subject: [PATCH 06/31] renepay: THE plugin global structure The plugin global structure is now defined in payplugin.h --- plugins/renepay/{pay.h => payplugin.h} | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) rename plugins/renepay/{pay.h => payplugin.h} (92%) diff --git a/plugins/renepay/pay.h b/plugins/renepay/payplugin.h similarity index 92% rename from plugins/renepay/pay.h rename to plugins/renepay/payplugin.h index 014c08880542..2b9fb2a8127a 100644 --- a/plugins/renepay/pay.h +++ b/plugins/renepay/payplugin.h @@ -1,11 +1,12 @@ -#ifndef LIGHTNING_PLUGINS_RENEPAY_PAY_H -#define LIGHTNING_PLUGINS_RENEPAY_PAY_H +#ifndef LIGHTNING_PLUGINS_RENEPAY_PAYPLUGIN_H +#define LIGHTNING_PLUGINS_RENEPAY_PAYPLUGIN_H #include "config.h" #include #include #include #include #include +#include // TODO(eduardo): renepaystatus should be similar to paystatus @@ -65,12 +66,13 @@ struct pay_plugin { /* All the struct payment */ struct list_head payments; + struct payment_map *payment_map; /* Per-channel metadata: some persists between payments */ - struct chan_extra_map *chan_extra_map; + struct uncertainty *uncertainty; /* Pending sendpays (to match notifications to). */ - struct payflow_map * payflow_map; + struct route_map *route_map; bool debug_mcf; bool debug_payflow; @@ -99,4 +101,5 @@ extern struct pay_plugin *pay_plugin; const char *try_paying(const tal_t *ctx, struct payment *payment, enum jsonrpc_errcode *ecode); -#endif /* LIGHTNING_PLUGINS_RENEPAY_PAY_H */ + +#endif /* LIGHTNING_PLUGINS_RENEPAY_PAYPLUGIN_H */ From a35cee03da7f6a5b1abacecf8be642763daf24aa Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 15:09:34 +0100 Subject: [PATCH 07/31] renepay: the plugin executable is main.c - move the plugin executable from pay.c to main.c, - adapt the plugin main program to handle concurrent pay calls, --- plugins/renepay/main.c | 422 ++++++++++++ plugins/renepay/pay.c | 1466 ---------------------------------------- 2 files changed, 422 insertions(+), 1466 deletions(-) create mode 100644 plugins/renepay/main.c delete mode 100644 plugins/renepay/pay.c diff --git a/plugins/renepay/main.c b/plugins/renepay/main.c new file mode 100644 index 000000000000..ee009a749e41 --- /dev/null +++ b/plugins/renepay/main.c @@ -0,0 +1,422 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO(eduardo): notice that pending attempts performed with another +// pay plugin are not considered by the uncertainty network in renepay, +// it would be nice if listsendpay would give us the route of pending +// sendpays. + +struct pay_plugin *pay_plugin; + +static void memleak_mark(struct plugin *p, struct htable *memtable) +{ + memleak_scan_obj(memtable, pay_plugin); + + // TODO is this necessary? + // memleak_scan_htable(memtable, &pay_plugin->chan_extra_map->raw); + // memleak_scan_htable(memtable, &pay_plugin->payment_map->raw); +} + +static const char *init(struct plugin *p, + const char *buf UNUSED, const jsmntok_t *config UNUSED) +{ + size_t num_channel_updates_rejected; + + tal_steal(p, pay_plugin); + pay_plugin->plugin = p; + pay_plugin->last_time = 0; + + rpc_scan(p, "getinfo", take(json_out_obj(NULL, NULL, NULL)), + "{id:%}", JSON_SCAN(json_to_node_id, &pay_plugin->my_id)); + + rpc_scan(p, "listconfigs", + take(json_out_obj(NULL, NULL, NULL)), + "{configs:" + "{max-locktime-blocks:{value_int:%}," + "experimental-offers:{set:%}}}", + JSON_SCAN(json_to_number, &pay_plugin->maxdelay_default), + JSON_SCAN(json_to_bool, &pay_plugin->exp_offers) + ); + + list_head_init(&pay_plugin->payments); + + pay_plugin->payment_map = tal(pay_plugin, struct payment_map); + payment_map_init(pay_plugin->payment_map); + + pay_plugin->route_map = tal(pay_plugin,struct route_map); + route_map_init(pay_plugin->route_map); + + pay_plugin->gossmap = gossmap_load(pay_plugin, + GOSSIP_STORE_FILENAME, + &num_channel_updates_rejected); + + if (!pay_plugin->gossmap) + plugin_err(p, "Could not load gossmap %s: %s", + GOSSIP_STORE_FILENAME, strerror(errno)); + if (num_channel_updates_rejected) + plugin_log(p, LOG_DBG, + "gossmap ignored %zu channel updates", + num_channel_updates_rejected); + pay_plugin->uncertainty = uncertainty_new(pay_plugin); + uncertainty_update(pay_plugin->uncertainty, pay_plugin->gossmap); + + plugin_set_memleak_handler(p, memleak_mark); + return NULL; +} + +static struct command_result *json_paystatus(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + const char *invstring; + struct json_stream *ret; + struct payment *p; + + if (!param(cmd, buf, params, + p_opt("invstring", param_invstring, &invstring), + NULL)) + return command_param_failed(); + + ret = jsonrpc_stream_success(cmd); + json_array_start(ret, "paystatus"); + if(invstring) + { + /* select the payment that matches this invoice */ + + if (bolt12_has_prefix(invstring)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "BOLT12 invoices are not yet supported."); + + char *fail; + struct bolt11 *b11 = + bolt11_decode(tmpctx, invstring, plugin_feature_set(cmd->plugin), + NULL, chainparams, &fail); + if (b11 == NULL) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Invalid bolt11: %s", fail); + + struct payment *payment = + payment_map_get(pay_plugin->payment_map, b11->payment_hash); + + if(payment) + { + json_object_start(ret, NULL); + json_add_payment(ret, payment); + json_object_end(ret); + } + }else + { + /* show all payments */ + // TODO: loop over the payment_map, remove pay_plugin->payments + // list + // seeconst char *fmt_chan_extra_map(const tal_t *ctx, struct chan_extra_map *chan_extra_map) + list_for_each(&pay_plugin->payments, p, list) { + json_object_start(ret, NULL); + json_add_payment(ret, p); + json_object_end(ret); + } + } + json_array_end(ret); + + return command_finished(cmd, ret); +} + +static struct command_result * payment_start(struct payment *p) +{ + assert(p); + p->status = PAYMENT_PENDING; + plugin_log(pay_plugin->plugin, LOG_DBG, "Starting renepay"); + p->exec_state = 0; + return payment_continue(p); +} + +static struct command_result *json_pay(struct command *cmd, const char *buf, + const jsmntok_t *params) +{ + /* === Parse command line arguments === */ + // TODO check if we leak some of these temporary variables + + const char *invstr; + struct amount_msat *msat; + struct amount_msat *maxfee; + u32 *maxdelay; + u32 *retryfor; + const char *description; + const char *label; + + // dev options + bool *use_shadow; + + // MCF options + u64 *base_fee_penalty_millionths; // base fee to proportional fee + u64 *prob_cost_factor_millionths; // prob. cost to proportional fee + u64 *riskfactor_millionths; // delay to proportional proportional fee + u64 *min_prob_success_millionths; // target probability + + if (!param(cmd, buf, params, + p_req("invstring", param_invstring, &invstr), + p_opt("amount_msat", param_msat, &msat), + p_opt("maxfee", param_msat, &maxfee), + + p_opt_def("maxdelay", param_number, &maxdelay, + /* maxdelay has a configuration default value named + * "max-locktime-blocks", this is retrieved at + * init. */ + pay_plugin->maxdelay_default), + + p_opt_def("retry_for", param_number, &retryfor, + 60), // 60 seconds + p_opt("description", param_string, &description), + p_opt("label", param_string, &label), + + // FIXME add support for offers + // p_opt("localofferid", param_sha256, &local_offer_id), + + p_opt_dev("dev_use_shadow", param_bool, &use_shadow, true), + + // MCF options + p_opt_dev("dev_base_fee_penalty", param_millionths, + &base_fee_penalty_millionths, + 10000000), // default is 10.0 + p_opt_dev("dev_prob_cost_factor", param_millionths, + &prob_cost_factor_millionths, + 10000000), // default is 10.0 + p_opt_dev("dev_riskfactor", param_millionths, + &riskfactor_millionths, 1), // default is 1e-6 + p_opt_dev("dev_min_prob_success", param_millionths, + &min_prob_success_millionths, + 900000), // default is 0.9 + NULL)) + return command_param_failed(); + + /* === Parse invoice === */ + + // FIXME: add support for bolt12 invoices + if (bolt12_has_prefix(invstr)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "BOLT12 invoices are not yet supported."); + + char *fail; + struct bolt11 *b11 = + bolt11_decode(tmpctx, invstr, plugin_feature_set(cmd->plugin), + description, chainparams, &fail); + if (b11 == NULL) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Invalid bolt11: %s", fail); + + /* Sanity check */ + if (feature_offered(b11->features, OPT_VAR_ONION) && + !b11->payment_secret) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Invalid bolt11:" + " sets feature var_onion with no secret"); + /* BOLT #11: + * A reader: + *... + * - MUST check that the SHA2 256-bit hash in the `h` field + * exactly matches the hashed description. + */ + if (!b11->description) { + if (!b11->description_hash) + return command_fail( + cmd, JSONRPC2_INVALID_PARAMS, + "Invalid bolt11: missing description"); + + if (!description) + return command_fail( + cmd, JSONRPC2_INVALID_PARAMS, + "bolt11 uses description_hash, but you did " + "not provide description parameter"); + } + + if (b11->msat) { + // amount is written in the invoice + if (msat) + return command_fail( + cmd, JSONRPC2_INVALID_PARAMS, + "amount_msat parameter unnecessary"); + msat = b11->msat; + } else { + // amount is not written in the invoice + if (!msat) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "amount_msat parameter required"); + } + + // Default max fee is 5 sats, or 0.5%, whichever is *higher* + if (!maxfee) { + struct amount_msat fee = amount_msat_div(*msat, 200); + if (amount_msat_less(fee, AMOUNT_MSAT(5000))) + fee = AMOUNT_MSAT(5000); + maxfee = tal_dup(tmpctx, struct amount_msat, &fee); + } + + const u64 now_sec = time_now().ts.tv_sec; + if (now_sec > (b11->timestamp + b11->expiry)) + return command_fail(cmd, PAY_INVOICE_EXPIRED, + "Invoice expired"); + + /* === Get payment === */ + + // one payment_hash one payment is not assumed, it is enforced + struct payment *payment = + payment_map_get(pay_plugin->payment_map, b11->payment_hash); + + if(!payment) + { + payment = payment_new( + tmpctx, + &b11->payment_hash, + take(invstr), + take(label), + take(description), + b11->payment_secret, + b11->metadata, + cast_const2(const struct route_info**, b11->routes), + &b11->receiver_id, + *msat, + *maxfee, + *maxdelay, + *retryfor, + b11->min_final_cltv_expiry, + *base_fee_penalty_millionths, + *prob_cost_factor_millionths, + *riskfactor_millionths, + *min_prob_success_millionths, + use_shadow); + + if (!payment) + return command_fail(cmd, PLUGIN_ERROR, + "failed to create a new payment"); + if (!payment_register_command(payment, cmd)) + return command_fail(cmd, PLUGIN_ERROR, + "failed to register command"); + + // good to go + payment = tal_steal(pay_plugin, payment); + + // FIXME do we really need a list here? + list_add_tail(&pay_plugin->payments, &payment->list); + payment_map_add(pay_plugin->payment_map, payment); + + return payment_start(payment); + } + + /* === Start or continue payment === */ + if (payment->status == PAYMENT_SUCCESS) { + assert(payment_commands_empty(payment)); + // this payment is already a success, we show the result + struct json_stream *result = jsonrpc_stream_success(cmd); + json_add_payment(result, payment); + return command_finished(cmd, result); + } + + if (payment->status == PAYMENT_FAIL) { + // FIXME: should we refuse to pay if the invoices are different? + // or should we consider this a new payment? + if (!payment_update(payment, + *maxfee, + *maxdelay, + *retryfor, + b11->min_final_cltv_expiry, + *base_fee_penalty_millionths, + *prob_cost_factor_millionths, + *riskfactor_millionths, + *min_prob_success_millionths, + use_shadow)) + return command_fail( + cmd, PLUGIN_ERROR, + "failed to update the payment parameters"); + + // this payment already failed, we try again + assert(payment_commands_empty(payment)); + if (!payment_register_command(payment, cmd)) + return command_fail(cmd, PLUGIN_ERROR, + "failed to register command"); + + return payment_start(payment); + } + + // else: this payment is pending we continue its execution, we merge all + // calling cmds into a single payment request + assert(payment->status == PAYMENT_PENDING); + if (!payment_register_command(payment, cmd)) + return command_fail(cmd, PLUGIN_ERROR, + "failed to register command"); + return command_still_pending(cmd); +} + +static const struct plugin_command commands[] = { + { + "renepaystatus", + "payment", + "Detail status of attempts to pay {bolt11}, or all", + "Covers both old payments and current ones.", + json_paystatus + }, + { + "renepay", + "payment", + "Send payment specified by {invstring}", + "Attempt to pay an invoice.", + json_pay + }, +}; + +static const struct plugin_notification notifications[] = { + { + "sendpay_success", + notification_sendpay_success, + }, + { + "sendpay_failure", + notification_sendpay_failure, + } +}; + +int main(int argc, char *argv[]) +{ + setup_locale(); + + /* Most gets initialized in init(), but set debug options here. */ + pay_plugin = tal(NULL, struct pay_plugin); + pay_plugin->debug_mcf = pay_plugin->debug_payflow = false; + + plugin_main( + argv, + init, + PLUGIN_RESTARTABLE, + /* init_rpc */ true, + /* features */ NULL, + commands, ARRAY_SIZE(commands), + notifications, ARRAY_SIZE(notifications), + /* hooks */ NULL, 0, + /* notification topics */ NULL, 0, + plugin_option("renepay-debug-mcf", "flag", + "Enable renepay MCF debug info.", + flag_option, &pay_plugin->debug_mcf), + plugin_option("renepay-debug-payflow", "flag", + "Enable renepay payment flows debug info.", + flag_option, &pay_plugin->debug_payflow), + NULL); + + return 0; +} diff --git a/plugins/renepay/pay.c b/plugins/renepay/pay.c deleted file mode 100644 index 30ff14758f7d..000000000000 --- a/plugins/renepay/pay.c +++ /dev/null @@ -1,1466 +0,0 @@ -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// TODO(eduardo): maybe there are too many debug_err and plugin_err and -// plugin_log(...,LOG_BROKEN,...) that could be resolved with a command_fail - -// TODO(eduardo): notice that pending attempts performed with another -// pay plugin are not considered by the uncertainty network in renepay, -// it would be nice if listsendpay would give us the route of pending -// sendpays. - -#define INVALID_ID UINT64_MAX -#define MAX(a,b) ((a)>(b)? (a) : (b)) -#define MIN(a,b) ((a)<(b)? (a) : (b)) - -struct pay_plugin *pay_plugin; - -static void memleak_mark(struct plugin *p, struct htable *memtable) -{ - memleak_scan_obj(memtable, pay_plugin); - memleak_scan_htable(memtable, &pay_plugin->chan_extra_map->raw); -} - -static const char *init(struct plugin *p, - const char *buf UNUSED, const jsmntok_t *config UNUSED) -{ - size_t num_channel_updates_rejected; - - tal_steal(p, pay_plugin); - pay_plugin->plugin = p; - pay_plugin->last_time = 0; - - rpc_scan(p, "getinfo", take(json_out_obj(NULL, NULL, NULL)), - "{id:%}", JSON_SCAN(json_to_node_id, &pay_plugin->my_id)); - - rpc_scan(p, "listconfigs", - take(json_out_obj(NULL, NULL, NULL)), - "{configs:" - "{max-locktime-blocks:{value_int:%}," - "experimental-offers:{set:%}}}", - JSON_SCAN(json_to_number, &pay_plugin->maxdelay_default), - JSON_SCAN(json_to_bool, &pay_plugin->exp_offers) - ); - - list_head_init(&pay_plugin->payments); - - pay_plugin->chan_extra_map = tal(pay_plugin,struct chan_extra_map); - chan_extra_map_init(pay_plugin->chan_extra_map); - - pay_plugin->payflow_map = tal(pay_plugin,struct payflow_map); - payflow_map_init(pay_plugin->payflow_map); - - pay_plugin->gossmap = gossmap_load(pay_plugin, - GOSSIP_STORE_FILENAME, - &num_channel_updates_rejected); - - if (!pay_plugin->gossmap) - plugin_err(p, "Could not load gossmap %s: %s", - GOSSIP_STORE_FILENAME, strerror(errno)); - if (num_channel_updates_rejected) - plugin_log(p, LOG_DBG, - "gossmap ignored %zu channel updates", - num_channel_updates_rejected); - - uncertainty_network_update(pay_plugin->gossmap, - pay_plugin->chan_extra_map); - plugin_set_memleak_handler(p, memleak_mark); - return NULL; -} - -/* Sometimes we don't know exactly who to blame... */ -static struct pf_result *handle_unhandleable_error(struct pay_flow *pf, - const char *what) -{ - plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - size_t n = tal_count(pf); - - /* We got a mangled reply. We don't know who to penalize! */ - payflow_note(pf, LOG_UNUSUAL, "%s on route %s", - what, flow_path_to_str(tmpctx, pf)); - - if (n == 1) - { - /* This is a terminal error. */ - return pay_flow_failed_final(pf, PAY_UNPARSEABLE_ONION, what); - } - /* FIXME: check chan_extra_map, since we might have succeeded though - * this node before? */ - - /* Prefer a node not directly connected to either end. */ - if (n > 3) { - /* us ->0-> ourpeer ->1-> rando ->2-> theirpeer ->3-> dest */ - n = 1 + pseudorand(n - 2); - } else - /* Assume it's not the destination */ - n = pseudorand(n-1); - - payflow_disable_chan(pf, pf->path_scidds[n].scid, - LOG_INFORM, "randomly chosen"); - - return pay_flow_failed(pf); -} - -/* We hold onto the flow (and delete the timer) while we're waiting for - * gossipd to receive the channel_update we got from the error. */ -struct addgossip { - struct short_channel_id scid; - struct pay_flow *pf; -}; - -static struct command_result *addgossip_done(struct command *cmd, - const char *buf, - const jsmntok_t *err, - struct addgossip *adg) -{ - plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - - /* This may free adg (pf is the parent), or otherwise it'll - * happen later. */ - pay_flow_finished_adding_gossip(adg->pf); - - bool gossmap_changed = gossmap_refresh(pay_plugin->gossmap, NULL); - - if (pay_plugin->gossmap == NULL) - plugin_err(pay_plugin->plugin, "Failed to refresh gossmap: %s", - strerror(errno)); - - if (gossmap_changed) - uncertainty_network_update(pay_plugin->gossmap, - pay_plugin->chan_extra_map); - - return command_still_pending(cmd); -} - -static struct command_result *addgossip_failure(struct command *cmd, - const char *buf, - const jsmntok_t *err, - struct addgossip *adg) - -{ - plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - - payflow_disable_chan(adg->pf, adg->scid, - LOG_INFORM, "addgossip failed (%.*s)", - err->end - err->start, buf + err->start); - - return addgossip_done(cmd, buf, err, adg); -} - -static struct pf_result *submit_update(struct pay_flow *pf, - const u8 *update, - struct short_channel_id errscid) -{ - plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - struct out_req *req; - struct addgossip *adg = tal(pf, struct addgossip); - - /* We need to stash scid in case this fails, and we need to hold flow so - * we don't get a rexmit before this is complete. */ - adg->scid = errscid; - adg->pf = pf; - - payflow_note(pf, LOG_DBG, "... extracted channel_update %s, telling gossipd", tal_hex(tmpctx, update)); - - req = jsonrpc_request_start(pay_plugin->plugin, NULL, "addgossip", - addgossip_done, - addgossip_failure, - adg); - json_add_hex_talarr(req->js, "message", update); - send_outreq(pay_plugin->plugin, req); - - /* Don't retry until we call pay_flow_finished_adding_gossip! */ - return pay_flow_failed_adding_gossip(pf); -} - -/* Fix up the channel_update to include the type if it doesn't currently have - * one. See ElementsProject/lightning#1730 and lightningnetwork/lnd#1599 for the - * in-depth discussion on why we break message parsing here... */ -static u8 *patch_channel_update(const tal_t *ctx, u8 *channel_update TAKES) -{ - u8 *fixed; - if (channel_update != NULL && - fromwire_peektype(channel_update) != WIRE_CHANNEL_UPDATE) { - /* This should be a channel_update, prefix with the - * WIRE_CHANNEL_UPDATE type, but isn't. Let's prefix it. */ - fixed = tal_arr(ctx, u8, 0); - towire_u16(&fixed, WIRE_CHANNEL_UPDATE); - towire(&fixed, channel_update, tal_bytelen(channel_update)); - if (taken(channel_update)) - tal_free(channel_update); - return fixed; - } else { - return tal_dup_talarr(ctx, u8, channel_update); - } -} - - -/* Return NULL if the wrapped onion error message has no channel_update field, - * or return the embedded channel_update message otherwise. */ -static u8 *channel_update_from_onion_error(const tal_t *ctx, - const u8 *onion_message) -{ - u8 *channel_update = NULL; - struct amount_msat unused_msat; - u32 unused32; - - /* Identify failcodes that have some channel_update. - * - * TODO > BOLT 1.0: Add new failcodes when updating to a - * new BOLT version. */ - if (!fromwire_temporary_channel_failure(ctx, - onion_message, - &channel_update) && - !fromwire_amount_below_minimum(ctx, - onion_message, &unused_msat, - &channel_update) && - !fromwire_fee_insufficient(ctx, - onion_message, &unused_msat, - &channel_update) && - !fromwire_incorrect_cltv_expiry(ctx, - onion_message, &unused32, - &channel_update) && - !fromwire_expiry_too_soon(ctx, - onion_message, - &channel_update)) - /* No channel update. */ - return NULL; - - return patch_channel_update(ctx, take(channel_update)); -} - -/* Once we've sent it, we immediate wait for reply. */ -static struct command_result *flow_sent(struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct pay_flow *pf) -{ - plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - return command_still_pending(cmd); -} - -/* sendpay really only fails immediately in two ways: - * 1. We screwed up and misused the API. - * 2. The first peer is disconnected. - */ -static struct command_result *flow_sendpay_failed(struct command *cmd, - const char *buf, - const jsmntok_t *err, - struct pay_flow *pf) -{ - struct payment *payment = pf->payment; - enum jsonrpc_errcode errcode; - const char *msg; - - plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - - assert(payment); - - if (json_scan(tmpctx, buf, err, - "{code:%,message:%}", - JSON_SCAN(json_to_jsonrpc_errcode, &errcode), - JSON_SCAN_TAL(tmpctx, json_strdup, &msg))) { - plugin_err(pay_plugin->plugin, "Bad fail from sendpay: %.*s", - json_tok_full_len(err), json_tok_full(buf, err)); - } - if (errcode != PAY_TRY_OTHER_ROUTE) - plugin_err(pay_plugin->plugin, "Strange error from sendpay: %.*s", - json_tok_full_len(err), json_tok_full(buf, err)); - - /* There is no new knowledge from this kind of failure. - * We just disable this scid. */ - payflow_disable_chan(pf, pf->path_scidds[0].scid, - LOG_INFORM, - "sendpay didn't like first hop: %s", msg); - - pay_flow_failed(pf); - return command_still_pending(cmd); -} - -/* Kick off all pay_flows which are in state PAY_FLOW_NOT_STARTED */ -static void sendpay_new_flows(struct payment *p) -{ - struct pay_flow *pf; - - list_for_each(&p->flows, pf, list) { - struct out_req *req; - - if (pf->state != PAY_FLOW_NOT_STARTED) - continue; - - /* FIXME: We don't actually want cmd to own this sendpay, so we use NULL here, - * but we should use a variant which allows us to set json id! */ - req = jsonrpc_request_start(pay_plugin->plugin, NULL, "sendpay", - flow_sent, flow_sendpay_failed, - pf); - - json_array_start(req->js, "route"); - for (size_t j = 0; j < tal_count(pf->path_nodes); j++) { - json_object_start(req->js, NULL); - json_add_node_id(req->js, "id", - &pf->path_nodes[j]); - json_add_short_channel_id(req->js, "channel", - pf->path_scidds[j].scid); - json_add_amount_msat(req->js, "amount_msat", - pf->amounts[j]); - json_add_num(req->js, "direction", - pf->path_scidds[j].dir); - json_add_u32(req->js, "delay", - pf->cltv_delays[j]); - json_add_string(req->js,"style","tlv"); - json_object_end(req->js); - } - json_array_end(req->js); - - json_add_sha256(req->js, "payment_hash", &p->payment_hash); - json_add_secret(req->js, "payment_secret", p->payment_secret); - - /* FIXME: sendpay has a check that we don't total more than - * the exact amount, if we're setting partid (i.e. MPP). However, - * we always set partid, and we add a shadow amount *if we've - * only have one part*, so we have to use that amount here. - * - * The spec was loosened so you are actually allowed - * to overpay, so this check is now overzealous. */ - if (amount_msat_greater(payflow_delivered(pf), p->amount)) { - json_add_amount_msat(req->js, "amount_msat", - payflow_delivered(pf)); - } else { - json_add_amount_msat(req->js, "amount_msat", p->amount); - } - - json_add_u64(req->js, "partid", pf->key.partid); - - json_add_u64(req->js, "groupid", p->groupid); - if (p->payment_metadata) - json_add_hex_talarr(req->js, "payment_metadata", - p->payment_metadata); - - /* FIXME: We don't need these three for all payments! */ - if (p->label) - json_add_string(req->js, "label", p->label); - json_add_string(req->js, "bolt11", p->invstr); - if (p->description) - json_add_string(req->js, "description", p->description); - - send_outreq(pay_plugin->plugin, req); - - /* Now you're started! */ - pf->state = PAY_FLOW_IN_PROGRESS; - } - - /* Safety check. */ - payment_assert_delivering_all(p); -} - -const char *try_paying(const tal_t *ctx, - struct payment *payment, - enum jsonrpc_errcode *ecode) -{ - plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - - struct amount_msat feebudget, fees_spent, remaining; - - assert(payment->status == PAYMENT_PENDING); - - /* Total feebudget */ - if (!amount_msat_sub(&feebudget, payment->maxspend, payment->amount)) - { - plugin_err(pay_plugin->plugin, - "%s (line %d) could not substract maxspend=%s and amount=%s.", - __PRETTY_FUNCTION__, - __LINE__, - fmt_amount_msat(tmpctx, payment->maxspend), - fmt_amount_msat(tmpctx, payment->amount)); - } - - /* Fees spent so far */ - if (!amount_msat_sub(&fees_spent, payment->total_sent, payment->total_delivering)) - { - plugin_err(pay_plugin->plugin, - "%s (line %d) could not substract total_sent=%s and total_delivering=%s.", - __PRETTY_FUNCTION__, - __LINE__, - fmt_amount_msat(tmpctx, payment->total_sent), - fmt_amount_msat(tmpctx, payment->total_delivering)); - } - - /* Remaining fee budget. */ - if (!amount_msat_sub(&feebudget, feebudget, fees_spent)) - { - plugin_err(pay_plugin->plugin, - "%s (line %d) could not substract feebudget=%s and fees_spent=%s.", - __PRETTY_FUNCTION__, - __LINE__, - fmt_amount_msat(tmpctx, feebudget), - fmt_amount_msat(tmpctx, fees_spent)); - } - - /* How much are we still trying to send? */ - if (!amount_msat_sub(&remaining, payment->amount, payment->total_delivering)) - { - plugin_err(pay_plugin->plugin, - "%s (line %d) could not substract amount=%s and total_delivering=%s.", - __PRETTY_FUNCTION__, - __LINE__, - fmt_amount_msat(tmpctx, payment->amount), - fmt_amount_msat(tmpctx, payment->total_delivering)); - } - - // plugin_log(pay_plugin->plugin,LOG_DBG,fmt_chan_extra_map(tmpctx,pay_plugin->chan_extra_map)); - - const char *err_msg; - - /* We let this return an unlikely path, as it's better to try once - * than simply refuse. Plus, models are not truth! */ - gossmap_apply_localmods(pay_plugin->gossmap, payment->local_gossmods); - err_msg = add_payflows(tmpctx, - payment, - remaining, feebudget, - /* is entire payment? */ - amount_msat_eq(remaining, AMOUNT_MSAT(0)), - ecode); - gossmap_remove_localmods(pay_plugin->gossmap, payment->local_gossmods); - - /* MCF cannot find a feasible route, we stop. */ - if (err_msg) - return err_msg; - - /* Now begin making payments */ - sendpay_new_flows(payment); - - return NULL; -} - -static void destroy_cmd_payment_ptr(struct command *cmd, - struct payment *payment) -{ - assert(payment->cmd == cmd); - payment->cmd = NULL; -} - -static void gossmod_cb(struct gossmap_localmods *mods, - const struct node_id *self, - const struct node_id *peer, - const struct short_channel_id_dir *scidd, - struct amount_msat htlcmin, - struct amount_msat htlcmax, - struct amount_msat spendable, - struct amount_msat fee_base, - u32 fee_proportional, - u32 cltv_delta, - bool enabled, - const char *buf, - const jsmntok_t *chantok, - struct payment *payment) -{ - struct amount_msat min, max; - - if (scidd->dir == node_id_idx(self, peer)) { - /* our side of the channel can send up to what's spendable */ - min = AMOUNT_MSAT(0); - max = spendable; - } else { - /* the remote side can send up to no more than spendable */ - min = htlcmin; - max = amount_msat_min(spendable, htlcmax); - } - - /* 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); - - /* Also update uncertainty map */ - uncertainty_network_update_from_listpeerchannels(payment, scidd, max, enabled, - buf, chantok, - pay_plugin->chan_extra_map); -} - -static struct command_result *listpeerchannels_done( - struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct payment *payment) -{ - plugin_log(pay_plugin->plugin,LOG_DBG,"calling %s",__PRETTY_FUNCTION__); - const char *errmsg; - enum jsonrpc_errcode ecode; - - payment->local_gossmods = gossmods_from_listpeerchannels(payment, &pay_plugin->my_id, - buf, result, true, - gossmod_cb, payment); - - // TODO(eduardo): check that there won't be a prob. cost associated with - // any gossmap local chan. The same way there aren't fees to pay for my - // local channels. - - // TODO(eduardo): are there route hints for B12? - // Add any extra hidden channel revealed by the routehints to the uncertainty network. - uncertainty_network_add_routehints(pay_plugin->chan_extra_map, payment->routes, payment); - - /* From now on, we keep a record of the payment, so persist it beyond this cmd. */ - tal_steal(pay_plugin->plugin, payment); - /* When we terminate cmd for any reason, clear it from payment so we don't do it again. */ - assert(cmd == payment->cmd); - tal_add_destructor2(cmd, destroy_cmd_payment_ptr, payment); - - /* This looks for a route, and if OK, fires off the sendpay commands */ - errmsg = try_paying(tmpctx, payment, &ecode); - if (errmsg) - return payment_fail(payment, ecode, "%s", errmsg); - - return command_still_pending(cmd); -} - - -static void destroy_payment(struct payment *p) -{ - list_del_from(&pay_plugin->payments, &p->list); -} - -static struct command_result *json_paystatus(struct command *cmd, - const char *buf, - const jsmntok_t *params) -{ - const char *invstring; - struct json_stream *ret; - struct payment *p; - - if (!param(cmd, buf, params, - p_opt("invstring", param_invstring, &invstring), - NULL)) - return command_param_failed(); - - ret = jsonrpc_stream_success(cmd); - json_array_start(ret, "paystatus"); - - list_for_each(&pay_plugin->payments, p, list) { - if (invstring && !streq(invstring, p->invstr)) - continue; - - json_object_start(ret, NULL); - if (p->label != NULL) - json_add_string(ret, "label", p->label); - - if (p->invstr) - json_add_invstring(ret,p->invstr); - - json_add_amount_msat(ret, "amount_msat", p->amount); - json_add_sha256(ret, "payment_hash", &p->payment_hash); - json_add_node_id(ret, "destination", &p->destination); - - if (p->description) - json_add_string(ret, "description", p->description); - - json_add_timeabs(ret,"created_at",p->start_time); - json_add_u64(ret,"groupid",p->groupid); - - switch(p->status) - { - case PAYMENT_SUCCESS: - json_add_string(ret,"status","complete"); - assert(p->preimage); - json_add_preimage(ret,"payment_preimage",p->preimage); - json_add_amount_msat(ret, "amount_sent_msat", p->total_sent); - - break; - case PAYMENT_FAIL: - json_add_string(ret,"status","failed"); - - break; - default: - json_add_string(ret,"status","pending"); - } - - json_array_start(ret, "notes"); - for (size_t i = 0; i < tal_count(p->paynotes); i++) - json_add_string(ret, NULL, p->paynotes[i]); - json_array_end(ret); - json_object_end(ret); - - // TODO(eduardo): maybe we should add also: - // - payment_secret? - // - payment_metadata? - // - number of parts? - } - json_array_end(ret); - - return command_finished(cmd, ret); -} - -static struct command_result *selfpay_success(struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct payment *p) -{ - struct preimage preimage; - const char *err; - err = json_scan(tmpctx, buf, result, - "{payment_preimage:%}", - JSON_SCAN(json_to_preimage, &preimage)); - p->preimage = tal_dup(p, struct preimage, &preimage); - if (err) - plugin_err(cmd->plugin, - "selfpay didn't have payment_preimage? %.*s", - json_tok_full_len(result), - json_tok_full(buf, result)); - p->status = PAYMENT_SUCCESS; - payment_note(p, LOG_DBG, "Paid with self-pay."); - return payment_success(p); -} - -/* Self-payment used in plugins/pay.c */ -static struct command_result *selfpay(struct command *cmd, struct payment *p) -{ - struct out_req *req; - - /* From now on, we keep a record of the payment, so persist it beyond this cmd. */ - tal_steal(pay_plugin->plugin, p); - assert(cmd == p->cmd); - /* When we terminate cmd for any reason, clear it from payment so we don't do it again. */ - tal_add_destructor2(cmd, destroy_cmd_payment_ptr, p); - - req = jsonrpc_request_start(cmd->plugin, cmd, "sendpay", - selfpay_success, - forward_error, p); - /* Empty route means "to-self" */ - json_array_start(req->js, "route"); - json_array_end(req->js); - json_add_sha256(req->js, "payment_hash", &p->payment_hash); - if (p->label) - json_add_string(req->js, "label", p->label); - json_add_amount_msat(req->js, "amount_msat", p->amount); - json_add_string(req->js, "bolt11", p->invstr); - if (p->payment_secret) - json_add_secret(req->js, "payment_secret", p->payment_secret); - json_add_u64(req->js, "groupid", p->groupid); - if (p->payment_metadata) - json_add_hex_talarr(req->js, "payment_metadata", p->payment_metadata); - if (p->description) - json_add_string(req->js, "description", p->description); - - /* Pretend we have sent partid=1 with the total amount. */ - p->next_partid = 2; - p->total_sent = p->amount; - return send_outreq(cmd->plugin, req); -} - -/* Taken from ./plugins/pay.c - * - * We are interested in any prior attempts to pay this payment_hash / - * invoice so we can set the `groupid` correctly and ensure we don't - * already have a pending payment running. We also collect the summary - * about an eventual previous complete payment so we can return that - * as a no-op. */ -static struct command_result * -payment_listsendpays_previous( - struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct payment * payment) -{ - size_t i; - const jsmntok_t *t, *arr; - - /* Group ID of the pending payment, this will be the one - * who's result gets replayed if we end up suspending. */ - u64 pending_group_id = INVALID_ID; - u64 max_pending_partid=0; - u64 max_group_id = 0; - struct amount_msat pending_sent = AMOUNT_MSAT(0), - pending_msat = AMOUNT_MSAT(0); - - /* Metadata for a complete payment, if one exists. */ - u32 complete_parts = 0; - struct preimage complete_preimage; - struct amount_msat complete_sent = AMOUNT_MSAT(0), - complete_msat = AMOUNT_MSAT(0); - u32 complete_created_at; - - arr = json_get_member(buf, result, "payments"); - if (!arr || arr->type != JSMN_ARRAY) - return command_fail( - cmd, LIGHTNINGD, - "Unexpected non-array result from listsendpays: %.*s", - json_tok_full_len(result), - json_tok_full(buf, result)); - - json_for_each_arr(i, t, arr) - { - u64 partid = 0, groupid; - struct amount_msat this_msat, this_sent; - const char *status; - - // TODO: we assume amount_msat is always present, but according - // to the documentation this field is optional. How do I - // interpret if amount_msat is missing? - const char *err = - json_scan(tmpctx,buf,t, - "{status:%" - ",partid?:%" - ",groupid:%" - ",amount_msat:%" - ",amount_sent_msat:%}", - JSON_SCAN_TAL(tmpctx, json_strdup, &status), - JSON_SCAN(json_to_u64,&partid), - JSON_SCAN(json_to_u64,&groupid), - JSON_SCAN(json_to_msat,&this_msat), - JSON_SCAN(json_to_msat,&this_sent)); - - if(err) - plugin_err(pay_plugin->plugin, - "%s json_scan of listsendpay returns the following error: %s", - __PRETTY_FUNCTION__, - err); - - /* If we decide to create a new group, we base it on max_group_id */ - if (groupid > max_group_id) - max_group_id = groupid; - - /* status could be completed, pending or failed */ - if (streq(status, "complete")) { - /* Now we know the payment completed. */ - if(!amount_msat_add(&complete_msat,complete_msat,this_msat)) - plugin_err(pay_plugin->plugin,"%s (line %d) msat overflow.", - __PRETTY_FUNCTION__,__LINE__); - if(!amount_msat_add(&complete_sent,complete_sent,this_sent)) - plugin_err(pay_plugin->plugin,"%s (line %d) msat overflow.", - __PRETTY_FUNCTION__,__LINE__); - json_scan(tmpctx, buf, t, - "{created_at:%" - ",payment_preimage:%}", - JSON_SCAN(json_to_u32, &complete_created_at), - JSON_SCAN(json_to_preimage, &complete_preimage)); - complete_parts++; - - plugin_log(pay_plugin->plugin,LOG_DBG, - "this part is complete then " - "complete_msat = %s", - fmt_amount_msat(tmpctx, complete_msat)); - } else if (streq(status, "pending")) { - /* If we have more than one pending group, something went wrong! */ - if (pending_group_id != INVALID_ID - && groupid != pending_group_id) - return command_fail(cmd, PAY_STATUS_UNEXPECTED, - "Multiple pending groups for this payment?"); - pending_group_id = groupid; - if (partid > max_pending_partid) - max_pending_partid = partid; - - if (!amount_msat_add(&pending_msat, pending_msat, - this_msat) || - !amount_msat_add(&pending_sent, pending_sent, - this_sent)) - plugin_err(pay_plugin->plugin, - "%s (line %d) msat overflow.", - __PRETTY_FUNCTION__, __LINE__); - - } else - assert(streq(status, "failed")); - } - - if (complete_parts != 0) { - /* There are completed sendpays, we don't need to do anything - * but summarize the result. */ - struct json_stream *ret = jsonrpc_stream_success(cmd); - json_add_preimage(ret, "payment_preimage", &complete_preimage); - json_add_string(ret, "status", "complete"); - json_add_amount_msat(ret, "amount_msat", complete_msat); - json_add_amount_msat(ret, "amount_sent_msat",complete_sent); - json_add_node_id(ret, "destination", &payment->destination); - json_add_sha256(ret, "payment_hash", &payment->payment_hash); - json_add_u32(ret, "created_at", complete_created_at); - json_add_num(ret, "parts", complete_parts); - - /* This payment was already completed, we don't keep record of - * it twice: payment will be freed with cmd */ - return command_finished(cmd, ret); - } else if (pending_group_id != INVALID_ID) { - /* Continue where we left off? */ - payment->groupid = pending_group_id; - payment->next_partid = max_pending_partid+1; - - payment->total_sent = pending_sent; - payment->total_delivering = pending_msat; - - plugin_log(pay_plugin->plugin,LOG_DBG, - "There are pending sendpays to this invoice. " - "groupid = %"PRIu64" " - "delivering = %s, " - "last_partid = %"PRIu64, - pending_group_id, - fmt_amount_msat(tmpctx, payment->total_delivering), - max_pending_partid); - - if(amount_msat_greater_eq(payment->total_delivering,payment->amount)) - { - /* Pending payment already pays the full amount, we - * better stop. */ - return command_fail(cmd, PAY_IN_PROGRESS, - "Payment is pending with full amount already commited"); - } - }else - { - /* There are no pending nor completed sendpays, get me the last - * sendpay group. */ - /* FIXME: use groupid 0 to have sendpay assign an unused groupid, - * as this is theoretically racy against other plugins paying the - * same thing! - * *BUT* that means we have to create one flow first, so we - * can match the others. */ - payment->groupid = max_group_id + 1; - payment->next_partid=1; - } - - /* Bypass everything if we're doing (synchronous) self-pay */ - if (node_id_eq(&pay_plugin->my_id, &payment->destination)) - return selfpay(cmd, payment); - - - struct out_req *req; - /* Get local capacities... */ - req = jsonrpc_request_start(cmd->plugin, cmd, "listpeerchannels", - listpeerchannels_done, - listpeerchannels_done, payment); - return send_outreq(cmd->plugin, req); -} - -static struct command_result *json_pay(struct command *cmd, - const char *buf, - const jsmntok_t *params) -{ - const char *invstr; - const char *label; - const char *description; - struct sha256 * local_offer_id; - u64 invexpiry; - struct amount_msat *msat, *invmsat; - struct amount_msat *maxfee; - struct sha256 payment_hash; - struct secret *payment_secret; - const u8 *payment_metadata; - struct node_id destination; - u32 *maxdelay; - u32 *retryfor; - u64 *base_fee_penalty; - u64 *prob_cost_factor; - u64 *riskfactor_millionths; - u64 *min_prob_success_millionths; - bool *use_shadow; - u16 final_cltv; - const struct route_info **routes = NULL; - - if (!param(cmd, buf, params, - p_req("invstring", param_invstring, &invstr), - p_opt("amount_msat", param_msat, &msat), - p_opt("maxfee", param_msat, &maxfee), - - p_opt_def("maxdelay", param_number, &maxdelay, - /* We're initially called to probe usage, before init! */ - pay_plugin ? pay_plugin->maxdelay_default : 0), - - - p_opt_def("retry_for", param_number, &retryfor, 60), // 60 seconds - p_opt("localofferid", param_sha256, &local_offer_id), - p_opt("description", param_string, &description), - p_opt("label", param_string, &label), - // MCF parameters - // TODO(eduardo): are these parameters read correctly? - p_opt_dev("dev_base_fee_penalty", param_millionths, &base_fee_penalty,10), - p_opt_dev("dev_prob_cost_factor", param_millionths, &prob_cost_factor,10), - p_opt_dev("dev_riskfactor", param_millionths,&riskfactor_millionths,1), - p_opt_dev("dev_min_prob_success", param_millionths, - &min_prob_success_millionths,900000),// default is 90% - p_opt_dev("dev_use_shadow", param_bool, &use_shadow, true), - NULL)) - return command_param_failed(); - - /* We might need to parse invstring to get amount */ - if (!bolt12_has_prefix(invstr)) { - struct bolt11 *b11; - char *fail; - - b11 = - bolt11_decode(tmpctx, invstr, plugin_feature_set(cmd->plugin), - description, chainparams, &fail); - if (b11 == NULL) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Invalid bolt11: %s", fail); - - invmsat = b11->msat; - invexpiry = b11->timestamp + b11->expiry; - - destination = b11->receiver_id; - payment_hash = b11->payment_hash; - payment_secret = - tal_dup_or_null(cmd, struct secret, b11->payment_secret); - if (b11->metadata) - payment_metadata = tal_dup_talarr(cmd, u8, b11->metadata); - else - payment_metadata = NULL; - - - final_cltv = b11->min_final_cltv_expiry; - /* Sanity check */ - if (feature_offered(b11->features, OPT_VAR_ONION) && - !b11->payment_secret) - return command_fail( - cmd, JSONRPC2_INVALID_PARAMS, - "Invalid bolt11:" - " sets feature var_onion with no secret"); - /* BOLT #11: - * A reader: - *... - * - MUST check that the SHA2 256-bit hash in the `h` field - * exactly matches the hashed description. - */ - if (!b11->description) { - if (!b11->description_hash) { - return command_fail(cmd, - JSONRPC2_INVALID_PARAMS, - "Invalid bolt11: missing description"); - } - if (!description) - return command_fail(cmd, - JSONRPC2_INVALID_PARAMS, - "bolt11 uses description_hash, but you did not provide description parameter"); - } - - routes = cast_const2(const struct route_info **, - b11->routes); - } else { - /* FIXME We have not yet added support for BOLT12 invoices, - * refuse to pay. */ - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "BOLT12 invoices are not yet supported."); - - // TODO(eduardo): check this, compare with `pay` - const struct tlv_invoice *b12; - char *fail; - b12 = invoice_decode(tmpctx, invstr, strlen(invstr), - plugin_feature_set(cmd->plugin), - chainparams, &fail); - if (b12 == NULL) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Invalid bolt12: %s", fail); - if (!pay_plugin->exp_offers) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "experimental-offers disabled"); - - if (!b12->offer_node_id) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "invoice missing offer_node_id"); - if (!b12->invoice_payment_hash) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "invoice missing payment_hash"); - if (!b12->invoice_created_at) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "invoice missing created_at"); - if (b12->invoice_amount) { - invmsat = tal(cmd, struct amount_msat); - *invmsat = amount_msat(*b12->invoice_amount); - } else - invmsat = NULL; - - node_id_from_pubkey(&destination, b12->offer_node_id); - payment_hash = *b12->invoice_payment_hash; - if (b12->invreq_recurrence_counter && !label) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "recurring invoice requires a label"); - /* FIXME payment_secret should be signature! */ - { - struct sha256 merkle; - - payment_secret = tal(cmd, struct secret); - merkle_tlv(b12->fields, &merkle); - memcpy(payment_secret, &merkle, sizeof(merkle)); - BUILD_ASSERT(sizeof(*payment_secret) == - sizeof(merkle)); - } - payment_metadata = NULL; - /* FIXME: blinded paths! */ - final_cltv = 18; - /* BOLT-offers #12: - * - if `relative_expiry` is present: - * - MUST reject the invoice if the current time since - * 1970-01-01 UTC is greater than `created_at` plus - * `seconds_from_creation`. - * - otherwise: - * - MUST reject the invoice if the current time since - * 1970-01-01 UTC is greater than `created_at` plus - * 7200. - */ - if (b12->invoice_relative_expiry) - invexpiry = *b12->invoice_created_at + *b12->invoice_relative_expiry; - else - invexpiry = *b12->invoice_created_at + BOLT12_DEFAULT_REL_EXPIRY; - } - - // set the payment amount - if (invmsat) { - // amount is written in the invoice - if (msat) { - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "amount_msat parameter unnecessary"); - } - msat = invmsat; - } else { - // amount is not written in the invoice - if (!msat) { - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "amount_msat parameter required"); - } - } - - /* Default max fee is 5 sats, or 0.5%, whichever is *higher* */ - if (!maxfee) { - struct amount_msat fee = amount_msat_div(*msat, 200); - if (amount_msat_less(fee, AMOUNT_MSAT(5000))) - fee = AMOUNT_MSAT(5000); - maxfee = tal_dup(tmpctx, struct amount_msat, &fee); - } - - const u64 now_sec = time_now().ts.tv_sec; - if (now_sec > invexpiry) - return command_fail(cmd, PAY_INVOICE_EXPIRED, "Invoice expired"); - - /* Payment is allocated off cmd to start, in case we fail cmd - * (e.g. already in progress, already succeeded). Once it's - * actually started, it persists beyond the command, so we - * tal_steal. */ - struct payment *payment = payment_new(cmd, - cmd, - take(invstr), - take(label), - take(description), - take(local_offer_id), - take(payment_secret), - take(payment_metadata), - take(routes), - &destination, - &payment_hash, - *msat, - *maxfee, - *maxdelay, - *retryfor, - final_cltv, - *base_fee_penalty, - *prob_cost_factor, - *riskfactor_millionths, - *min_prob_success_millionths, - use_shadow); - - /* We immediately add this payment to the payment list. */ - list_add_tail(&pay_plugin->payments, &payment->list); - tal_add_destructor(payment, destroy_payment); - - plugin_log(pay_plugin->plugin,LOG_DBG,"Starting renepay"); - bool gossmap_changed = gossmap_refresh(pay_plugin->gossmap, NULL); - - if (pay_plugin->gossmap == NULL) - plugin_err(pay_plugin->plugin, "Failed to refresh gossmap: %s", - strerror(errno)); - - /* Free parameters which would be considered "leaks" by our fussy memleak code */ - tal_free(msat); - tal_free(maxfee); - tal_free(maxdelay); - tal_free(retryfor); - - /* To construct the uncertainty network we need to perform the following - * steps: - * 1. check that there is a 1-to-1 map between channels in gossmap - * and the uncertainty network. We call `uncertainty_network_update` - * - * 2. add my local channels that could be private. - * We call `update_uncertainty_network_from_listpeerchannels`. - * - * 3. add hidden/private channels listed in the routehints. - * We call `uncertainty_network_add_routehints`. - * - * 4. check the uncertainty network invariants. - * */ - if(gossmap_changed) - uncertainty_network_update(pay_plugin->gossmap, - pay_plugin->chan_extra_map); - - - /* TODO(eduardo): We use a linear function to decide how to decay the - * channel information. Other shapes could be used. - * Also the choice of the proportional parameter TIMER_FORGET_SEC is - * arbitrary. - * Another idea is to measure time in blockheight. */ - const double fraction = (now_sec - pay_plugin->last_time)*1.0/TIMER_FORGET_SEC; - uncertainty_network_relax_fraction(pay_plugin->chan_extra_map, - fraction); - pay_plugin->last_time = now_sec; - - if(!uncertainty_network_check_invariants(pay_plugin->chan_extra_map)) - plugin_log(pay_plugin->plugin, - LOG_BROKEN, - "uncertainty network invariants are violated"); - - /* Next, request listsendpays for previous payments that use the same - * hash. */ - struct out_req *req - = jsonrpc_request_start(cmd->plugin, cmd, "listsendpays", - payment_listsendpays_previous, - payment_listsendpays_previous, payment); - - json_add_sha256(req->js, "payment_hash", &payment->payment_hash); - return send_outreq(cmd->plugin, req); -} - -/* Terminates flow */ -static struct pf_result *handle_sendpay_failure_payment(struct pay_flow *pf STEALS, - const char *message, - u32 erridx, - enum onion_wire onionerr, - const u8 *raw) -{ - struct short_channel_id errscid; - const u8 *update; - - assert(pf); - - /* Final node is usually a hard failure */ - if (erridx == tal_count(pf->path_scidds)) { - if (onionerr == WIRE_MPP_TIMEOUT) { - return pay_flow_failed(pf); - } - - payflow_note(pf, LOG_INFORM, - "final destination permanent failure"); - return pay_flow_failed_final(pf, PAY_DESTINATION_PERM_FAIL, message); - } - - errscid = pf->path_scidds[erridx].scid; - switch (onionerr) { - /* These definitely mean eliminate channel */ - case WIRE_PERMANENT_CHANNEL_FAILURE: - case WIRE_REQUIRED_CHANNEL_FEATURE_MISSING: - /* FIXME: lnd returns this for disconnected peer, so don't disable perm! */ - case WIRE_UNKNOWN_NEXT_PEER: - case WIRE_CHANNEL_DISABLED: - /* These mean node is weird, but we eliminate channel here too */ - case WIRE_INVALID_REALM: - case WIRE_TEMPORARY_NODE_FAILURE: - case WIRE_PERMANENT_NODE_FAILURE: - case WIRE_REQUIRED_NODE_FEATURE_MISSING: - /* These shouldn't happen, but eliminate channel */ - case WIRE_INVALID_ONION_VERSION: - case WIRE_INVALID_ONION_HMAC: - case WIRE_INVALID_ONION_KEY: - case WIRE_INVALID_ONION_PAYLOAD: - case WIRE_INVALID_ONION_BLINDING: - case WIRE_EXPIRY_TOO_FAR: - payflow_disable_chan(pf, errscid, LOG_UNUSUAL, - "%s", - onion_wire_name(onionerr)); - return pay_flow_failed(pf); - - /* These can be fixed (maybe) by applying the included channel_update */ - case WIRE_AMOUNT_BELOW_MINIMUM: - case WIRE_FEE_INSUFFICIENT: - case WIRE_INCORRECT_CLTV_EXPIRY: - case WIRE_EXPIRY_TOO_SOON: - plugin_log(pay_plugin->plugin,LOG_DBG,"sendpay_failure, apply channel_update"); - /* FIXME: Check scid! */ - // TODO(eduardo): check - update = channel_update_from_onion_error(tmpctx, raw); - if (update) - return submit_update(pf, update, errscid); - - payflow_disable_chan(pf, errscid, - LOG_UNUSUAL, "missing channel_update"); - return pay_flow_failed(pf); - - case WIRE_TEMPORARY_CHANNEL_FAILURE: - /* These also contain a channel_update, but in this case it's simply - * advisory, not necessary. */ - update = channel_update_from_onion_error(tmpctx, raw); - if (update) - return submit_update(pf, update, errscid); - - return pay_flow_failed(pf); - - /* These should only come from the final distination. */ - case WIRE_MPP_TIMEOUT: - case WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: - case WIRE_FINAL_INCORRECT_CLTV_EXPIRY: - case WIRE_FINAL_INCORRECT_HTLC_AMOUNT: - break; - } - - payflow_disable_chan(pf, errscid, - LOG_UNUSUAL, "unexpected error code %u", - onionerr); - return pay_flow_failed(pf); -} - -static void handle_sendpay_failure_flow(struct pay_flow *pf, - const char *msg, - u32 erridx, - u32 onionerr) -{ - assert(pf); - - /* we know that all channels before erridx where able to commit to this payment */ - uncertainty_network_channel_can_send( - pay_plugin->chan_extra_map, - pf, - erridx); - - /* Insufficient funds (not from final, that's weird!) */ - if((enum onion_wire)onionerr == WIRE_TEMPORARY_CHANNEL_FAILURE - && erridx < tal_count(pf->path_scidds)) - { - const char *old_state = - fmt_chan_extra_details(tmpctx, pay_plugin->chan_extra_map, - &pf->path_scidds[erridx]); - - char *fail; - if (!chan_extra_cannot_send(tmpctx, pay_plugin->chan_extra_map, - &pf->path_scidds[erridx], - &fail)) { - plugin_err(pay_plugin->plugin, - "chan_extra_cannot_send failed: %s", fail); - } - - payflow_note(pf, LOG_INFORM, - "Failure to forward amount %s in channel %s, " - "state change %s -> %s", - fmt_amount_msat(tmpctx, pf->amounts[erridx]), - fmt_short_channel_id_dir(tmpctx, - &pf->path_scidds[erridx]), - old_state, - fmt_chan_extra_details(tmpctx, - pay_plugin->chan_extra_map, - &pf->path_scidds[erridx])); - } -} - -/* See if this notification is about one of our flows. */ -static struct pay_flow *pay_flow_from_notification(const char *buf, - const jsmntok_t *obj) -{ - struct payflow_key key; - const char *err; - - /* Single part payment? No partid */ - key.partid = 0; - err = json_scan(tmpctx, buf, obj, "{partid?:%,groupid:%,payment_hash:%}", - JSON_SCAN(json_to_u64, &key.partid), - JSON_SCAN(json_to_u64, &key.groupid), - JSON_SCAN(json_to_sha256, &key.payment_hash)); - if (err) { - plugin_err(pay_plugin->plugin, - "Missing fields (%s) in notification: %.*s", - err, - json_tok_full_len(obj), - json_tok_full(buf, obj)); - } - - return payflow_map_get(pay_plugin->payflow_map, &key); -} - - - -static struct command_result *notification_sendpay_success( - struct command *cmd, - const char *buf, - const jsmntok_t *params) -{ - struct pay_flow *pf; - struct preimage preimage; - const char *err; - const jsmntok_t *sub = json_get_member(buf, params, "sendpay_success"); - - pf = pay_flow_from_notification(buf, sub); - if (!pf) - return notification_handled(cmd); - - err = json_scan(tmpctx, buf, sub, "{payment_preimage:%}", - JSON_SCAN(json_to_preimage, &preimage)); - if (err) { - plugin_err(pay_plugin->plugin, - "Bad payment_preimage (%s) in sendpay_success: %.*s", - err, - json_tok_full_len(params), - json_tok_full(buf, params)); - } - - payflow_note(pf, LOG_INFORM, "Success"); - - // 2. update information - uncertainty_network_flow_success(pay_plugin->chan_extra_map, pf); - - // 3. mark as success (frees pf) - pay_flow_succeeded(pf, &preimage); - - return notification_handled(cmd); -} - -/* Dummy return ensures all paths call pay_flow_* to close flow! */ -static struct pf_result *sendpay_failure(struct pay_flow *pf, - enum jsonrpc_errcode errcode, - const char *buf, - const jsmntok_t *sub) -{ - const char *msg, *err; - u32 erridx, onionerr; - const u8 *raw; - - /* Only one code is really actionable */ - switch (errcode) { - case PAY_UNPARSEABLE_ONION: - return handle_unhandleable_error(pf, "Unparsable onion reply"); - - case PAY_TRY_OTHER_ROUTE: - break; - case PAY_DESTINATION_PERM_FAIL: - break; - default: - return pay_flow_failed_final(pf, - errcode, - "Unexpected errorcode from sendpay_failure"); - } - - /* Extract remaining fields for feedback */ - raw = NULL; - err = json_scan(tmpctx, buf, sub, - "{message:%" - ",data:{erring_index:%" - ",failcode:%" - ",raw_message?:%}}", - JSON_SCAN_TAL(tmpctx, json_strdup, &msg), - JSON_SCAN(json_to_u32, &erridx), - JSON_SCAN(json_to_u32, &onionerr), - JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, &raw)); - if (err) - return handle_unhandleable_error(pf, err); - - /* Answer must be sane: but note, erridx can be final node! */ - if (erridx > tal_count(pf->path_scidds)) { - plugin_err(pay_plugin->plugin, - "Erring channel %u/%zu in path %s", - erridx, tal_count(pf->path_scidds), - flow_path_to_str(tmpctx, pf)); - } - - payflow_note(pf, LOG_INFORM, "Failed at node #%u (%s): %s", - erridx, onion_wire_name(onionerr), msg); - handle_sendpay_failure_flow(pf, msg, erridx, onionerr); - - return handle_sendpay_failure_payment(pf, msg, erridx, onionerr, raw); -} - -static struct command_result *notification_sendpay_failure( - struct command *cmd, - const char *buf, - const jsmntok_t *params) -{ - struct pay_flow *pf; - const char *err; - enum jsonrpc_errcode errcode; - const jsmntok_t *sub = json_get_member(buf, params, "sendpay_failure"); - - pf = pay_flow_from_notification(buf, json_get_member(buf, sub, "data")); - if (!pf) - return notification_handled(cmd); - - err = json_scan(tmpctx, buf, sub, "{code:%}", - JSON_SCAN(json_to_jsonrpc_errcode, &errcode)); - if (err) { - plugin_err(pay_plugin->plugin, - "Bad code (%s) in sendpay_failure: %.*s", - err, - json_tok_full_len(params), - json_tok_full(buf, params)); - } - - sendpay_failure(pf, errcode, buf, sub); - return notification_handled(cmd); -} - -static const struct plugin_command commands[] = { - { - "renepaystatus", - "payment", - "Detail status of attempts to pay {bolt11}, or all", - "Covers both old payments and current ones.", - json_paystatus - }, - { - "renepay", - "payment", - "Send payment specified by {invstring}", - "Attempt to pay an invoice.", - json_pay - }, -}; - -static const struct plugin_notification notifications[] = { - // { - // "shutdown", - // notification_shutdown, - // }, - { - "sendpay_success", - notification_sendpay_success, - }, - { - "sendpay_failure", - notification_sendpay_failure, - } -}; - -int main(int argc, char *argv[]) -{ - setup_locale(); - - /* Most gets initialized in init(), but set debug options here. */ - pay_plugin = tal(NULL, struct pay_plugin); - pay_plugin->debug_mcf = pay_plugin->debug_payflow = false; - - plugin_main( - argv, - init, - PLUGIN_RESTARTABLE, - /* init_rpc */ true, - /* features */ NULL, - commands, ARRAY_SIZE(commands), - notifications, ARRAY_SIZE(notifications), - /* hooks */ NULL, 0, - /* notification topics */ NULL, 0, - plugin_option("renepay-debug-mcf", "flag", - "Enable renepay MCF debug info.", - flag_option, &pay_plugin->debug_mcf), - plugin_option("renepay-debug-payflow", "flag", - "Enable renepay payment flows debug info.", - flag_option, &pay_plugin->debug_payflow), - NULL); - - return 0; -} From 1aa6083ae300140b86f150bc0d3a74321e5fe5f9 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 15:13:02 +0100 Subject: [PATCH 08/31] renepay: payment state machine (mods) Add a new implementation of the payment state machine. This is based in the `pay` plugin concept of payment modifiers, but here we take it to the next level. The payment goes through a virtual machine that includes calling functions and evaluating conditions. --- plugins/renepay/mods.c | 1048 ++++++++++++++++++++++++++++++++++++++++ plugins/renepay/mods.h | 36 ++ 2 files changed, 1084 insertions(+) create mode 100644 plugins/renepay/mods.c create mode 100644 plugins/renepay/mods.h diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c new file mode 100644 index 000000000000..890849f6fbfa --- /dev/null +++ b/plugins/renepay/mods.c @@ -0,0 +1,1048 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define INVALID_ID UINT32_MAX + +#define OP_NULL NULL +#define OP_CALL (void *)1 +#define OP_IF (void *)2 + +void *payment_virtual_program[]; + +/* Advance the payment virtual machine */ +struct command_result *payment_continue(struct payment *payment) +{ + assert(payment->exec_state != INVALID_STATE); + void *op = payment_virtual_program[payment->exec_state++]; + + if (op == OP_NULL) { + plugin_err(pay_plugin->plugin, + "payment_continue reached the end of the virtual " + "machine execution."); + } else if (op == OP_CALL) { + const struct payment_modifier *mod = + (const struct payment_modifier *) + payment_virtual_program[payment->exec_state++]; + + if (mod == NULL) + plugin_err(pay_plugin->plugin, + "payment_continue expected payment_modifier " + "but NULL found"); + + plugin_log(pay_plugin->plugin, LOG_DBG, "Calling modifier %s", + mod->name); + return mod->step_cb(payment); + } else if (op == OP_IF) { + const struct payment_condition *cond = + (const struct payment_condition *) + payment_virtual_program[payment->exec_state++]; + + if (cond == NULL) + plugin_err(pay_plugin->plugin, + "payment_continue expected pointer to " + "condition but NULL found"); + + plugin_log(pay_plugin->plugin, LOG_DBG, + "Calling payment condition %s", cond->name); + + const u64 position_iftrue = + (u64)payment_virtual_program[payment->exec_state++]; + + if (cond->condition_cb(payment)) + payment->exec_state = position_iftrue; + + return payment_continue(payment); + } + plugin_err(pay_plugin->plugin, "payment_continue op code not defined"); + return NULL; +} + + +/* Generic handler for RPC failures that should end up failing the payment. */ +static struct command_result *payment_rpc_failure(struct command *cmd, + const char *buffer, + const jsmntok_t *toks, + struct payment *payment) +{ + const jsmntok_t *codetok = json_get_member(buffer, toks, "code"); + u32 errcode; + if (codetok != NULL) + json_to_u32(buffer, codetok, &errcode); + else + errcode = LIGHTNINGD; + + return payment_fail( + payment, errcode, + "Failing a partial payment due to a failed RPC call: %.*s", + json_tok_full_len(toks), json_tok_full(buffer, toks)); +} + +/***************************************************************************** + * previous_sendpays + * + * Obtain a list of previous sendpay requests and check if + * the current payment hash has already being used in previous failed, pending + * or completed attempts. + */ + +static struct command_result *previous_sendpays_done(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct payment *payment) +{ + size_t i; + const char *err; + const jsmntok_t *t, *arr; + u32 max_group_id = 0; + + /* Data for pending payments, this will be the one + * who's result gets replayed if we end up suspending. */ + u32 pending_group_id = INVALID_ID; + u32 max_pending_partid = 0; + struct route **pending_routes = tal_arr(tmpctx, struct route*, 0); + assert(pending_routes); + + /* Data for a complete payment, if one exists. */ + u32 complete_parts = 0; + struct preimage complete_preimage; + u32 complete_created_at; + u32 complete_groupid = INVALID_ID; + struct amount_msat complete_sent = AMOUNT_MSAT(0), + complete_msat = AMOUNT_MSAT(0); + + arr = json_get_member(buf, result, "payments"); + if (!arr || arr->type != JSMN_ARRAY) { + return payment_fail( + payment, LIGHTNINGD, + "Unexpected non-array result from listsendpays: %.*s", + json_tok_full_len(result), json_tok_full(buf, result)); + } + + /* TODO: I think this has a bug. If there is a pending sendpay with some + * groupid we want to know the highest partid for all sendpays with that + * same groupid. Doing a single scan we might fail. Eg. suppose the + * groupid=1 has a partid=1 which is pending, but also partid=2 which + * failed, since there is no guaranteed order in this list we might + * first scan {groupid=1, partid=2, status=failed} and then {groupid=1, + * partid=1, status=pending}. */ + json_for_each_arr(i, t, arr) + { + u32 partid = 0, groupid; + struct amount_msat this_msat, this_sent; + const char *status; + + // FIXME we assume amount_msat is always present, but according + // to the documentation this field is optional. How do I + // interpret if amount_msat is missing? + err = json_scan(tmpctx, buf, t, + "{status:%" + ",partid?:%" + ",groupid:%" + ",amount_msat:%" + ",amount_sent_msat:%}", + JSON_SCAN_TAL(tmpctx, json_strdup, &status), + JSON_SCAN(json_to_u32, &partid), + JSON_SCAN(json_to_u32, &groupid), + JSON_SCAN(json_to_msat, &this_msat), + JSON_SCAN(json_to_msat, &this_sent)); + + if (err) + plugin_err(pay_plugin->plugin, + "%s json_scan of listsendpay returns the " + "following error: %s", + __PRETTY_FUNCTION__, err); + + /* If we decide to create a new group, we base it on + * max_group_id */ + if (groupid > max_group_id) + max_group_id = groupid; + + /* status could be completed, pending or failed */ + if (streq(status, "complete")) { + if (complete_groupid != INVALID_ID && + groupid != complete_groupid) { + return payment_fail( + payment, PAY_STATUS_UNEXPECTED, + "Multiple complete groupids for " + "this payment."); + } + complete_groupid = groupid; + /* Now we know the payment completed. */ + if (!amount_msat_add(&complete_msat, complete_msat, + this_msat) || + !amount_msat_add(&complete_sent, complete_sent, + this_sent)) + plugin_err(pay_plugin->plugin, + "%s (line %d) amount_msat overflow.", + __PRETTY_FUNCTION__, __LINE__); + err = json_scan( + tmpctx, buf, t, + "{created_at:%" + ",payment_preimage:%}", + JSON_SCAN(json_to_u32, &complete_created_at), + JSON_SCAN(json_to_preimage, &complete_preimage)); + + if (err) + plugin_err(pay_plugin->plugin, + "%s trying to parse created_at and " + "payment_preimage returns the " + "following error: %s", + __PRETTY_FUNCTION__, err); + // FIXME there is json_add_timeabs, but there isn't + // json_to_timeabs + complete_parts++; + } else if (streq(status, "pending")) { + /* If we have more than one pending group, something + * went wrong! */ + if (pending_group_id != INVALID_ID && + groupid != pending_group_id) { + return payment_fail( + payment, PAY_STATUS_UNEXPECTED, + "Multiple pending groups for this " + "payment."); + } + pending_group_id = groupid; + if (partid > max_pending_partid) + max_pending_partid = partid; + + /* FIXME: pending sendpays should be considered just as + * the routes that we send. Because when they succeed we + * succeed the payment, and when they fail we need to + * substract from the total. */ + + struct route *r = new_route( + pending_routes, payment, groupid, partid, + payment->payment_hash, this_msat, this_sent); + assert(r); + tal_arr_expand(&pending_routes, r); + } else + assert(streq(status, "failed")); + } + + if (complete_groupid != INVALID_ID) { + /* There are completed sendpays, we don't need to do anything + * but summarize the result. */ + payment->start_time.ts.tv_sec = complete_created_at; + payment->start_time.ts.tv_nsec = 0; + payment->total_delivering = complete_msat; + payment->total_sent = complete_sent; + payment->next_partid = complete_parts + 1; + payment->groupid = complete_groupid; + + payment_note(payment, LOG_DBG, + "Payment completed by a previous sendpay."); + return payment_success(payment, &complete_preimage); + } else if (pending_group_id != INVALID_ID) { + /* Continue where we left off? */ + payment->groupid = pending_group_id; + // TODO: there is a bug here, max_pending_partid is not the + // max_partid for the pending_group_id + payment->next_partid = max_pending_partid + 1; + + plugin_log(pay_plugin->plugin, LOG_DBG, + "There are pending sendpays to this invoice. " + "groupid = %" PRIu32 " " + "delivering = %s, " + "last_partid = %" PRIu32, + pending_group_id, + fmt_amount_msat(tmpctx, payment->total_delivering), + max_pending_partid); + + if (amount_msat_greater_eq(payment->total_delivering, + payment->amount)) { + /* Pending payment already pays the full amount, we + * better stop. */ + return payment_fail( + payment, PAY_IN_PROGRESS, + "Payment is pending with full amount " + "already commited"); + } + + for (size_t j = 0; j < tal_count(pending_routes); j++) { + route_pending(pending_routes[j]); + } + + } else { + /* There are no pending nor completed sendpays, get me the last + * sendpay group. */ + payment->groupid = max_group_id + 1; + payment->next_partid = 1; + payment->total_sent = AMOUNT_MSAT(0); + payment->total_delivering = AMOUNT_MSAT(0); + } + + return payment_continue(payment); +} + +static struct command_result *previous_sendpays_cb(struct payment *payment) +{ + struct command *cmd = payment_command(payment); + assert(cmd); + + struct out_req *req = jsonrpc_request_start( + cmd->plugin, cmd, "listsendpays", previous_sendpays_done, + payment_rpc_failure, payment); + + json_add_sha256(req->js, "payment_hash", &payment->payment_hash); + return send_outreq(cmd->plugin, req); +} + +REGISTER_PAYMENT_MODIFIER(previous_sendpays, previous_sendpays_cb); + +/***************************************************************************** + * initial_sanity_checks + * + * Some checks on a payment about to start. + */ +static struct command_result *initial_sanity_checks_cb(struct payment *payment) +{ + assert(amount_msat_zero(payment->total_sent)); + assert(amount_msat_zero(payment->total_delivering)); + assert(!payment->preimage); + assert(tal_count(payment->cmd_array) == 1); + + return payment_continue(payment); +} + +REGISTER_PAYMENT_MODIFIER(initial_sanity_checks, initial_sanity_checks_cb); + +/***************************************************************************** + * selfpay + * + * Checks if the payment destination is the sender's node and perform a self + * payment. + */ + +static struct command_result *selfpay_success(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct payment *payment) +{ + struct preimage preimage; + const char *err; + err = json_scan(tmpctx, buf, result, "{payment_preimage:%}", + JSON_SCAN(json_to_preimage, &preimage)); + if (err) + plugin_err( + cmd->plugin, "selfpay didn't have payment_preimage: %.*s", + json_tok_full_len(result), json_tok_full(buf, result)); + + payment_note(payment, LOG_DBG, "Paid with self-pay."); + /* FIXME: shouldn't we process selfpay in the same way a regular payment? */ + return payment_success(payment, &preimage); +} + +static void route_selfpaypending(const struct route *route) +{ + assert(route); + struct payment *payment = route->payment; + assert(payment); + struct routetracker *routetracker = payment->routetracker; + assert(routetracker); + + /* we already keep track of this route */ + assert(!route_map_get(routetracker->pending_routes, &route->key)); + route_map_add(routetracker->pending_routes, route); + tal_steal(routetracker, route); + + if (!amount_msat_add(&payment->total_sent, payment->total_sent, + payment->amount) || + !amount_msat_add(&payment->total_delivering, + payment->total_delivering, payment->amount)) { + plugin_err(pay_plugin->plugin, + "%s: amount_msat arithmetic overflow.", + __PRETTY_FUNCTION__); + } +} + +static struct command_result *selfpay_cb(struct payment *payment) +{ + if (!node_id_eq(&pay_plugin->my_id, &payment->destination)) { + return payment_continue(payment); + } + + struct command *cmd = payment_command(payment); + if (!cmd) + plugin_err(pay_plugin->plugin, + "Selfpay: cannot get a valid cmd."); + struct out_req *req; + req = + jsonrpc_request_start(cmd->plugin, cmd, "sendpay", selfpay_success, + payment_rpc_failure, payment); + + struct route *route = new_route(payment, payment, payment->groupid, + /*partid=*/0, payment->payment_hash, + payment->amount, payment->amount); + route->hops = tal_arr(route, struct route_hop, 0); + json_add_route(req->js, route); + + route_selfpaypending(route); + return send_outreq(cmd->plugin, req); +} + +REGISTER_PAYMENT_MODIFIER(selfpay, selfpay_cb); + +/***************************************************************************** + * getmychannels + * + * Calls listpeerchannels to get and updated state of the local channels. + */ + +static void +uncertainty_update_from_listpeerchannels(struct uncertainty *uncertainty, + const struct short_channel_id_dir *scidd, + struct amount_msat max, bool enabled, + const char *buf, const jsmntok_t *chantok) +{ + if (!enabled) + return; + + struct amount_msat capacity; + const char *errmsg = json_scan(tmpctx, buf, chantok, "{total_msat:%}", + JSON_SCAN(json_to_msat, &capacity)); + if (errmsg) + goto error; + + if (!uncertainty_add_channel(pay_plugin->uncertainty, scidd->scid, + capacity)) { + errmsg = tal_fmt( + tmpctx, + "Unable to find/add scid=%s in the uncertainty network", + fmt_short_channel_id(tmpctx, scidd->scid)); + goto error; + } + // FIXME this does not include pending HTLC of ongoing payments! + if (!uncertainty_set_liquidity(pay_plugin->uncertainty, scidd, max)) { + errmsg = tal_fmt( + tmpctx, + "Unable to set liquidity to channel scidd=%s in the " + "uncertainty network.", + fmt_short_channel_id_dir(tmpctx, scidd)); + goto error; + } + return; + +error: + plugin_log( + pay_plugin->plugin, LOG_UNUSUAL, + "Failed to update local channel %s from listpeerchannels rpc: %s", + fmt_short_channel_id(tmpctx, scidd->scid), + errmsg); +} + +static void gossmod_cb(struct gossmap_localmods *mods, + const struct node_id *self, + const struct node_id *peer, + const struct short_channel_id_dir *scidd, + struct amount_msat htlcmin, + struct amount_msat htlcmax, + struct amount_msat spendable, + struct amount_msat fee_base, + u32 fee_proportional, + u32 cltv_delta, + bool enabled, + bool is_local, + const char *buf, + const jsmntok_t *chantok, + struct payment *payment) +{ + struct amount_msat min, max; + + if (is_local) { + /* local channels can send up to what's spendable */ + min = AMOUNT_MSAT(0); + max = spendable; + } else { + /* remote channels can send up no more than spendable */ + min = htlcmin; + max = amount_msat_min(spendable, htlcmax); + } + + /* 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); + + /* Is it disabled? */ + if (!enabled) + payment_disable_chan(payment, scidd->scid, LOG_DBG, + "listpeerchannels says not enabled"); + + /* Also update the uncertainty network */ + uncertainty_update_from_listpeerchannels(pay_plugin->uncertainty, scidd, max, + enabled, buf, chantok); +} + +static struct command_result *getmychannels_done(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct payment *payment) +{ + // FIXME: should local gossmods be global (ie. member of pay_plugin) or + // local (ie. member of payment)? + payment->local_gossmods = gossmods_from_listpeerchannels( + payment, &pay_plugin->my_id, buf, result, /* zero_rates = */ true, + gossmod_cb, payment); + + return payment_continue(payment); +} + +static struct command_result *getmychannels_cb(struct payment *payment) +{ + struct command *cmd = payment_command(payment); + if (!cmd) + plugin_err(pay_plugin->plugin, + "getmychannels_pay_mod: cannot get a valid cmd."); + + struct out_req *req = jsonrpc_request_start( + cmd->plugin, cmd, "listpeerchannels", getmychannels_done, + payment_rpc_failure, payment); + return send_outreq(cmd->plugin, req); +} + +REGISTER_PAYMENT_MODIFIER(getmychannels, getmychannels_cb); + +/***************************************************************************** + * refreshgossmap + * + * Update the gossmap. + */ +static struct command_result * +refreshgossmap_done(struct command *cmd UNUSED, const char *buf UNUSED, + const jsmntok_t *result UNUSED, struct payment *payment) +{ + assert(pay_plugin->gossmap); // gossmap must be already initialized + + size_t num_channel_updates_rejected; + bool gossmap_changed = + gossmap_refresh(pay_plugin->gossmap, &num_channel_updates_rejected); + + if (gossmap_changed && num_channel_updates_rejected) + plugin_log(pay_plugin->plugin, LOG_DBG, + "gossmap ignored %zu channel updates", + num_channel_updates_rejected); + + if (gossmap_changed) + uncertainty_update(pay_plugin->uncertainty, pay_plugin->gossmap); + return payment_continue(payment); +} + +static struct command_result *refreshgossmap_cb(struct payment *payment) +{ + struct command *cmd = payment_command(payment); + assert(cmd); + struct out_req *req = jsonrpc_request_start( + cmd->plugin, cmd, "waitblockheight", refreshgossmap_done, + payment_rpc_failure, payment); + json_add_num(req->js, "blockheight", 0); + return send_outreq(cmd->plugin, req); +} + +REGISTER_PAYMENT_MODIFIER(refreshgossmap, refreshgossmap_cb); + +/***************************************************************************** + * routehints + * + * Use route hints from the invoice to update the local gossmods and uncertainty + * network. + */ +// TODO check how this is done in pay.c + +static void add_hintchan(struct payment *payment, const struct node_id *src, + const struct node_id *dst, u16 cltv_expiry_delta, + const struct short_channel_id scid, u32 fee_base_msat, + u32 fee_proportional_millionths) +{ + // TODO test this, simply make a payment through a private channel, this + // statement is either right or wrong. + int dir = node_id_cmp(src, dst) < 0 ? 0 : 1; + + const char *errmsg; + const struct chan_extra *ce = + uncertainty_find_channel(pay_plugin->uncertainty, scid); + + if (!ce) { + /* 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 + the capacity to amount and state to [amount,amount], but that + wouldn't work if the recepient provides more than one hints + telling us to partition the payment in multiple routes. */ + ce = uncertainty_add_channel(pay_plugin->uncertainty, scid, + MAX_CAPACITY); + if (!ce) { + errmsg = tal_fmt(tmpctx, + "Unable to find/add scid=%s in the " + "local uncertainty network", + fmt_short_channel_id(tmpctx, scid)); + goto function_error; + } + /* FIXME: features? */ + 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)) { + errmsg = tal_fmt( + tmpctx, + "Failed to update scid=%s in the local_gossmods.", + fmt_short_channel_id(tmpctx, scid)); + goto function_error; + } + } else { + /* The channel is pubic and we already keep track of it in the + * gossmap and uncertainty network. It would be wrong to assume + * that this channel has sufficient capacity to forward the + * entire payment! Doing so leads to knowledge updates in which + * the known min liquidity is greater than the channel's + * capacity. */ + } + + return; + +function_error: + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, + "Failed to update hint channel %s: %s", + fmt_short_channel_id(tmpctx, scid), + errmsg); +} + +static struct command_result *routehints_done(struct command *cmd UNUSED, + const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct payment *payment) +{ + // FIXME are there route hints for B12? + assert(payment->routehints); + const size_t nhints = tal_count(payment->routehints); + for (size_t i = 0; i < nhints; i++) { + /* Each one, presumably, leads to the destination */ + const struct route_info *r = payment->routehints[i]; + const struct node_id *end = &payment->destination; + + for (int j = tal_count(r) - 1; j >= 0; j--) { + add_hintchan(payment, &r[j].pubkey, end, + r[j].cltv_expiry_delta, + r[j].short_channel_id, r[j].fee_base_msat, + r[j].fee_proportional_millionths); + end = &r[j].pubkey; + } + } + return payment_continue(payment); +} + +static struct command_result *routehints_cb(struct payment *payment) +{ + struct command *cmd = payment_command(payment); + assert(cmd); + struct out_req *req = jsonrpc_request_start( + cmd->plugin, cmd, "waitblockheight", routehints_done, + payment_rpc_failure, payment); + json_add_num(req->js, "blockheight", 0); + return send_outreq(cmd->plugin, req); +} + +REGISTER_PAYMENT_MODIFIER(routehints, routehints_cb); + +/***************************************************************************** + * compute_routes + * + * Compute the payment routes. + */ + +static struct command_result * +compute_routes_done(struct command *cmd UNUSED, const char *buf UNUSED, + const jsmntok_t *result UNUSED, struct payment *payment) +{ + struct amount_msat feebudget, fees_spent, remaining; + + /* Total feebudget */ + if (!amount_msat_sub(&feebudget, payment->maxspend, payment->amount)) + plugin_err(pay_plugin->plugin, "%s: fee budget is negative?", + __PRETTY_FUNCTION__); + + /* Fees spent so far */ + if (!amount_msat_sub(&fees_spent, payment->total_sent, + payment->total_delivering)) + plugin_err(pay_plugin->plugin, + "%s: total_delivering is greater than total_sent?", + __PRETTY_FUNCTION__); + + /* Remaining fee budget. */ + if (!amount_msat_sub(&feebudget, feebudget, fees_spent)) + feebudget = AMOUNT_MSAT(0); + + /* How much are we still trying to send? */ + if (!amount_msat_sub(&remaining, payment->amount, + payment->total_delivering)) + plugin_err(pay_plugin->plugin, + "%s: total_delivering is greater than amount?", + __PRETTY_FUNCTION__); + + // FIXME think about the uncertainty network, we cannot afford to have a + // local uncertainty network for each payment because when a route + // thread returns some knowledge we need to update the uncertainty + // network and that information might be split among the local and the + // global. + // FIXME check that routes and the uncertainty network can talk to each + // other without the need of the gossmap, because some channels might be + // in the local gossmap. + + enum jsonrpc_errcode errcode; + const char *err_msg; + + gossmap_apply_localmods(pay_plugin->gossmap, payment->local_gossmods); + // TODO: add an algorithm selector here + /* We let this return an unlikely path, as it's better to try once than + * simply refuse. Plus, models are not truth! */ + if (payment->routes_computed) + plugin_err(pay_plugin->plugin, + "%s: no previously computed routes expected.", + __PRETTY_FUNCTION__); + + payment->routes_computed = get_routes( + payment, + payment, + &pay_plugin->my_id, + &payment->destination, + pay_plugin->gossmap, + pay_plugin->uncertainty, + remaining, + payment->final_cltv, + feebudget, + + &errcode, + &err_msg); + + gossmap_remove_localmods(pay_plugin->gossmap, payment->local_gossmods); + + /* Couldn't feasible route, we stop. */ + if (!payment->routes_computed) { + return payment_fail(payment, errcode, "%s", err_msg); + } + return payment_continue(payment); +} + +static struct command_result *compute_routes_cb(struct payment *payment) +{ + assert(payment->status == PAYMENT_PENDING); + + struct command *cmd = payment_command(payment); + assert(cmd); + struct out_req *req = jsonrpc_request_start( + cmd->plugin, cmd, "waitblockheight", compute_routes_done, + payment_rpc_failure, payment); + json_add_num(req->js, "blockheight", 0); + return send_outreq(cmd->plugin, req); +} + +REGISTER_PAYMENT_MODIFIER(compute_routes, compute_routes_cb); + +/***************************************************************************** + * send_routes + * + * This payment modifier takes the payment routes and starts the payment + * request calling sendpay. + */ + +static struct command_result *send_routes_done(struct command *cmd, + const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct payment *payment) +{ + for (size_t i = 0; i < tal_count(payment->routes_computed); i++) { + struct route *route = payment->routes_computed[i]; + + route_sendpay_request(cmd, route); + + payment_note(payment, LOG_INFORM, + "Sent route request: partid=%" PRIu64 + " amount=%s prob=%.3lf fees=%s delay=%u path=%s", + route->key.partid, + fmt_amount_msat(tmpctx, route_delivers(route)), + route->success_prob, + fmt_amount_msat(tmpctx, route_fees(route)), + route_delay(route), fmt_route_path(tmpctx, route)); + + } + payment->routes_computed = tal_free(payment->routes_computed); + + return payment_continue(payment); +} + +static struct command_result *send_routes_cb(struct payment *payment) +{ + struct command *cmd = payment_command(payment); + assert(cmd); + + payment->have_results = false; + payment->retry = false; + + struct out_req *req = jsonrpc_request_start( + cmd->plugin, cmd, "waitblockheight", send_routes_done, + payment_rpc_failure, payment); + json_add_num(req->js, "blockheight", 0); + return send_outreq(cmd->plugin, req); +} + +REGISTER_PAYMENT_MODIFIER(send_routes, send_routes_cb); + +/***************************************************************************** + * sleep + * + * The payment main thread sleeps for some time. + */ + +static void sleep_done(struct payment *payment) +{ + payment->waitresult_timer = NULL; + // TODO: is this compulsory? + timer_complete(pay_plugin->plugin); + payment_continue(payment); +} + +static struct command_result *sleep_cb(struct payment *payment) +{ + // FIXME time duration is hardcoded, we could have this as a + // plugin wide option with default value at 10 millisecons. + assert(payment->waitresult_timer == NULL); + payment->waitresult_timer = plugin_timer( + pay_plugin->plugin, time_from_msec(10), sleep_done, payment); + struct command *cmd = payment_command(payment); + assert(cmd); + return command_still_pending(cmd); +} + +REGISTER_PAYMENT_MODIFIER(sleep, sleep_cb); + +/***************************************************************************** + * collect_results + */ +static struct command_result * +collect_results_done(struct command *cmd UNUSED, const char *buf UNUSED, + const jsmntok_t *result UNUSED, struct payment *payment) +{ + payment->have_results = false; + payment->retry = false; + + /* pending sendpay callbacks should be zero */ + if (routetracker_count_sent(payment->routetracker)>0) + return payment_continue(payment); + + /* all sendpays have been sent, look for success */ + struct preimage *payment_preimage = NULL; + enum jsonrpc_errcode final_error = LIGHTNINGD; + const char *final_msg = NULL; + + payment_collect_results(payment, &payment_preimage, &final_error, &final_msg); + + if (payment_preimage) { + /* If we have the preimate that means one succeed, we + * inmediately finish the payment. */ + if (!amount_msat_greater_eq(payment->total_delivering, + payment->amount)) { + plugin_err( + pay_plugin->plugin, + "%s: received a success sendpay for this " + "payment but the total delivering amount %s " + "is less than the payment amount %s.", + __PRETTY_FUNCTION__, + fmt_amount_msat(tmpctx, payment->total_delivering), + fmt_amount_msat(tmpctx, payment->amount)); + } + return payment_success(payment, take(payment_preimage)); + } + if (final_msg) { + /* We received a sendpay result with a final error message, we + * inmediately finish the payment. */ + return payment_fail(payment, final_error, "%s", final_msg); + } + + if (amount_msat_greater_eq(payment->total_delivering, + payment->amount)) { + /* There are no succeeds but we are still pending delivering the + * entire payment. We still need to collect more results. */ + payment->have_results = false; + payment->retry = false; + } else { + /* We have some failures so that now we are short of + * total_delivering, we may retry. */ + payment->have_results = true; + + // FIXME: we seem to always retry here if we don't fail + // inmediately. But I am going to leave this variable here, + // cause we might decide in the future to put some conditions on + // retries, like a maximum number of retries. + payment->retry = true; + } + + // FIXME: do we need to check for timeout? We might endup in an + // infinite loop of collect results. + + return payment_continue(payment); +} +static struct command_result *collect_results_cb(struct payment *payment) +{ + // make a dummy call to waitblockheight to move the state + // machine by one step keeping the stack clean + struct command *cmd = payment_command(payment); + assert(cmd); + struct out_req *req = jsonrpc_request_start( + cmd->plugin, cmd, "waitblockheight", collect_results_done, + payment_rpc_failure, payment); + json_add_num(req->js, "blockheight", 0); + return send_outreq(cmd->plugin, req); +} + +REGISTER_PAYMENT_MODIFIER(collect_results, collect_results_cb); + +/***************************************************************************** + * end + * + * The default ending of a payment. + */ +static struct command_result *end_done(struct command *cmd UNUSED, + const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct payment *payment) +{ + return payment_fail(payment, PAY_STOPPED_RETRYING, + "Payment execution ended without success."); +} +static struct command_result *end_cb(struct payment *payment) +{ + struct command *cmd = payment_command(payment); + assert(cmd); + struct out_req *req = + jsonrpc_request_start(cmd->plugin, cmd, "waitblockheight", end_done, + payment_rpc_failure, payment); + json_add_num(req->js, "blockheight", 0); + return send_outreq(cmd->plugin, req); +} + +REGISTER_PAYMENT_MODIFIER(end, end_cb); + +/***************************************************************************** + * checktimeout + * + * Fail the payment if we have exceeded the timeout. + */ + +static struct command_result *checktimeout_done(struct command *cmd UNUSED, + const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct payment *payment) +{ + if (time_after(time_now(), payment->stop_time)) { + return payment_fail(payment, PAY_STOPPED_RETRYING, "Timed out"); + } + return payment_continue(payment); +} + +static struct command_result *checktimeout_cb(struct payment *payment) +{ + struct command *cmd = payment_command(payment); + assert(cmd); + struct out_req *req = jsonrpc_request_start( + cmd->plugin, cmd, "waitblockheight", checktimeout_done, + payment_rpc_failure, payment); + json_add_num(req->js, "blockheight", 0); + return send_outreq(cmd->plugin, req); +} + +REGISTER_PAYMENT_MODIFIER(checktimeout, checktimeout_cb); + +/***************************************************************************** + * alwaystrue + * + * A funny payment condition that always returns true. + */ +static bool alwaystrue_cb(const struct payment *payment) { return true; } + +REGISTER_PAYMENT_CONDITION(alwaystrue, alwaystrue_cb); + +/***************************************************************************** + * nothaveresults + * + * A payment condition that returns true if the payment has not yet + * collected enough results to decide whether the payment has succeed, + * failed or need retrying. + */ +static bool nothaveresults_cb(const struct payment *payment) +{ + return !payment->have_results; +} + +REGISTER_PAYMENT_CONDITION(nothaveresults, nothaveresults_cb); + +/***************************************************************************** + * retry + * + * A payment condition that returns true if we should retry the payment. + */ +static bool retry_cb(const struct payment *payment) { return payment->retry; } + +REGISTER_PAYMENT_CONDITION(retry, retry_cb); + +/***************************************************************************** + * Virtual machine + * + * The plugin API is based on function calls. This makes is difficult to + * summarize all payment steps into one function, because the workflow + * is distributed across multiple functions. The default pay plugin + * implements a "state machine" for each payment attempt/part and that + * improves a lot the code readability and modularity. Based on that + * idea renepay has its own state machine for the whole payment. We go + * one step further by adding not just function calls (or payment + * modifiers with OP_CALL) but also conditions with OP_IF that allows + * for instance to have loops. Renepay's "program" is nicely summarized + * in the following set of instructions: + */ +// TODO +// add shadow route +// add knowledge decay +// add check pre-approved invoice +void *payment_virtual_program[] = { + /*0*/ OP_CALL, &previous_sendpays_pay_mod, + /*2*/ OP_CALL, &selfpay_pay_mod, + /*4*/ OP_CALL, &getmychannels_pay_mod, + /*6*/ OP_CALL, &routehints_pay_mod, + // TODO: add a channel filter, for example disable channels that have + // htlcmax < 0.1% of payment amount, or base fee > 100msat, or + // proportional_fee > 10%, or capacity < 10% payment amount + // TODO shadow_additions + /* do */ + /*8*/ OP_CALL, &refreshgossmap_pay_mod, + /*10*/ OP_CALL, &checktimeout_pay_mod, + /*12*/ OP_CALL, &compute_routes_pay_mod, + /*14*/ OP_CALL, &send_routes_pay_mod, + /*do*/ + /*16*/ OP_CALL, &checktimeout_pay_mod, + /*18*/ OP_CALL, &sleep_pay_mod, + /*20*/ OP_CALL, &collect_results_pay_mod, + /*while*/ + /*22*/ OP_IF, ¬haveresults_pay_cond, (void *)16, + /* while */ + /*25*/ OP_IF, &retry_pay_cond, (void *)8, + /*28*/ OP_CALL, &end_pay_mod, /* safety net, default failure if reached */ + /*20*/ NULL}; diff --git a/plugins/renepay/mods.h b/plugins/renepay/mods.h new file mode 100644 index 000000000000..8f788f90d503 --- /dev/null +++ b/plugins/renepay/mods.h @@ -0,0 +1,36 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_MODS_H +#define LIGHTNING_PLUGINS_RENEPAY_MODS_H + +#include "config.h" + +struct payment; +struct command_result; + +struct payment_modifier { + const char *name; + struct command_result *(*step_cb)(struct payment *p); +}; + +struct payment_condition { + const char *name; + bool (*condition_cb)(const struct payment *p); +}; + +struct command_result *payment_continue(struct payment *p); + +#define REGISTER_PAYMENT_MODIFIER(name, step_cb) \ + struct payment_modifier name##_pay_mod = { \ + stringify(name), \ + typesafe_cb_cast(struct command_result * (*)(struct payment *), \ + struct command_result * (*)(struct payment *), \ + step_cb), \ + }; + +#define REGISTER_PAYMENT_CONDITION(name, condition_cb) \ + struct payment_condition name##_pay_cond = { \ + stringify(name), \ + typesafe_cb_cast(bool (*)(const struct payment *), \ + bool (*)(const struct payment *), condition_cb), \ + }; + +#endif /* LIGHTNING_PLUGINS_RENEPAY_MODS_H */ From 4bbad498e23f45e4d0fc83f150dbea16b0a55cdc Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 15:33:14 +0100 Subject: [PATCH 09/31] renepay: add routebuilder.c Routebuilder constitute the module that builds the routes we try in the payment. The public API is simply `get_routes`, it replaces the similar interface for pay_flows. --- plugins/renepay/routebuilder.c | 442 +++++++++++++++++++++++++++++++++ plugins/renepay/routebuilder.h | 22 ++ 2 files changed, 464 insertions(+) create mode 100644 plugins/renepay/routebuilder.c create mode 100644 plugins/renepay/routebuilder.h diff --git a/plugins/renepay/routebuilder.c b/plugins/renepay/routebuilder.c new file mode 100644 index 000000000000..d7b1e74e2602 --- /dev/null +++ b/plugins/renepay/routebuilder.c @@ -0,0 +1,442 @@ +#include +#include +#include + +static bitmap *make_disabled_bitmap(const tal_t *ctx, + const struct gossmap *gossmap, + const struct short_channel_id *scids) +{ + bitmap *disabled = + tal_arrz(ctx, bitmap, BITMAP_NWORDS(gossmap_max_chan_idx(gossmap))); + + for (size_t i = 0; i < tal_count(scids); i++) { + struct gossmap_chan *c = gossmap_find_chan(gossmap, &scids[i]); + if (c) + bitmap_set_bit(disabled, gossmap_chan_idx(gossmap, c)); + } + return disabled; +} + +// static void uncertainty_commit_routes(struct uncertainty *uncertainty, +// struct route **routes) +// { +// const size_t N = tal_count(routes); +// for (size_t i = 0; i < N; i++) +// uncertainty_commit_htlcs(uncertainty, routes[i]); +// } +static void uncertainty_remove_routes(struct uncertainty *uncertainty, + struct route **routes) +{ + const size_t N = tal_count(routes); + for (size_t i = 0; i < N; i++) + uncertainty_remove_htlcs(uncertainty, routes[i]); +} + +static void mark_chan_disabled(const struct gossmap_chan *chan, + struct short_channel_id **disabled_scids, + bitmap *disabled_bitmap, + struct gossmap *gossmap) +{ + struct short_channel_id scid = gossmap_chan_scid(gossmap, chan); + tal_arr_expand(disabled_scids, scid); + bitmap_set_bit(disabled_bitmap, gossmap_chan_idx(gossmap, chan)); +} + +// TODO: check +/* Shave-off amounts that do not meet the liquidity constraints. Disable + * channels that produce an htlc_max bottleneck. */ +static struct flow **flows_adjust_htlcmax_constraints( + const tal_t *ctx, struct flow **flows TAKES, struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct short_channel_id **disabled_scids, bitmap *disabled_bitmap) +{ + struct flow **new_flows = tal_arr(ctx, struct flow *, 0); + enum renepay_errorcode errorcode; + + for (size_t i = 0; i < tal_count(flows); i++) { + struct flow *f = flows[i]; + struct amount_msat max_deliverable; + const struct gossmap_chan *bad_channel; + + errorcode = flow_maximum_deliverable( + &max_deliverable, f, gossmap, chan_extra_map, &bad_channel); + + if (!errorcode) { + // no issues + f->amount = + amount_msat_min(flow_delivers(f), max_deliverable); + + tal_arr_expand(&new_flows, f); + } else if (errorcode == RENEPAY_BAD_CHANNEL) { + // this is a channel that we can disable + mark_chan_disabled(bad_channel, disabled_scids, + disabled_bitmap, gossmap); + continue; + } else { + // we had an unexpected error + goto function_fail; + } + } + + for (size_t i = 0; i < tal_count(new_flows); i++) { + tal_steal(new_flows, new_flows[i]); + } + + if (taken(flows)) + tal_free(flows); + return new_flows; + +function_fail: + if (taken(flows)) + tal_free(flows); + return tal_free(new_flows); +} + +// TODO: check +/* Disable channels that produce an htlc_min bottleneck. */ +static struct flow **flows_adjust_htlcmin_constraints( + const tal_t *ctx, struct flow **flows TAKES, struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct short_channel_id **disabled_scids, bitmap *disabled_bitmap) +{ + struct flow **new_flows = tal_arr(ctx, struct flow *, 0); + enum renepay_errorcode errorcode; + struct amount_msat max_deliverable; + + for (size_t i = 0; i < tal_count(flows); i++) { + struct flow *f = flows[i]; + const struct gossmap_chan *bad_channel; + + errorcode = flow_maximum_deliverable( + &max_deliverable, f, gossmap, chan_extra_map, &bad_channel); + + if (!errorcode) { + // no issues + f->amount = + amount_msat_min(flow_delivers(f), max_deliverable); + + tal_arr_expand(&new_flows, f); + } else if (errorcode == RENEPAY_BAD_CHANNEL) { + // this is a channel that we can disable + mark_chan_disabled(bad_channel, disabled_scids, + disabled_bitmap, gossmap); + continue; + } else { + // we had an unexpected error + goto function_fail; + } + } + + for (size_t i = 0; i < tal_count(new_flows); i++) { + tal_steal(new_flows, new_flows[i]); + } + + if (taken(flows)) + tal_free(flows); + return new_flows; + +function_fail: + if (taken(flows)) + tal_free(flows); + return tal_free(new_flows); +} + +/* Routes are computed and saved in the payment for later use. */ +struct route **get_routes(const tal_t *ctx, struct payment *payment, + + const struct node_id *source, + const struct node_id *destination, + struct gossmap *gossmap, struct uncertainty *uncertainty, + + struct amount_msat amount_to_deliver, + const u32 final_cltv, struct amount_msat feebudget, + + enum jsonrpc_errcode *ecode, const char **fail) +{ + assert(gossmap); + assert(uncertainty); + + const tal_t *this_ctx = tal(ctx, tal_t); + struct route **routes = tal_arr(ctx, struct route *, 0); + + double probability_budget = payment->min_prob_success; + double delay_feefactor = payment->delay_feefactor; + const double base_fee_penalty = payment->base_fee_penalty; + const double prob_cost_factor = payment->prob_cost_factor; + const unsigned int maxdelay = payment->maxdelay; + + bitmap *disabled_bitmap = + make_disabled_bitmap(this_ctx, gossmap, payment->disabled_scids); + + const struct gossmap_node *src, *dst; + src = gossmap_find_node(gossmap, source); + if (!src) { + if (ecode) + *ecode = PAY_ROUTE_NOT_FOUND; + if (fail) + *fail = tal_fmt(ctx, "We don't have any channels."); + goto function_fail; + } + dst = gossmap_find_node(gossmap, destination); + if (!dst) { + if (ecode) + *ecode = PAY_ROUTE_NOT_FOUND; + if (fail) + *fail = tal_fmt( + ctx, + "Destination is unknown in the network gossip."); + goto function_fail; + } + + char *errmsg; + + while (!amount_msat_zero(amount_to_deliver)) { + + /* TODO: choose an algorithm, could be something like + * payment->algorithm, that we set up based on command line + * options and that can be changed according to some conditions + * met during the payment process, eg. add "select_solver" pay + * mod. */ + /* TODO: use uncertainty instead of chan_extra */ + /* TODO: shall we add to possibility to blacklist nodes? */ + + /* Min. Cost Flow algorithm to find optimal flows. */ + struct flow **flows = + minflow(this_ctx, gossmap, src, dst, + uncertainty_get_chan_extra_map(uncertainty), + disabled_bitmap, amount_to_deliver, feebudget, + probability_budget, delay_feefactor, + base_fee_penalty, prob_cost_factor, &errmsg); + + if (!flows) { + if (ecode) + *ecode = PAY_ROUTE_NOT_FOUND; + + if (fail) + *fail = tal_fmt( + ctx, + "minflow couldn't find a feasible flow: %s", + errmsg); + goto function_fail; + } + + /* In previous implementations we would search for + * htlcmax/htlcmin violations and disable those channels and + * then redo the MCF computation. Now we instead remove only + * those flows for which there is a constraint violation and + * mark the involved channels as disabled for the next MCF + * iteration. */ + flows = flows_adjust_htlcmax_constraints( + this_ctx, take(flows), gossmap, + uncertainty_get_chan_extra_map(uncertainty), + &payment->disabled_scids, disabled_bitmap); + if (!flows) { + if (ecode) + *ecode = PAY_ROUTE_NOT_FOUND; + + if (fail) + *fail = tal_fmt( + ctx, + "failed to adjust htlcmax constraints."); + + goto function_fail; + } + + flows = flows_adjust_htlcmin_constraints( + this_ctx, take(flows), gossmap, + uncertainty_get_chan_extra_map(uncertainty), + &payment->disabled_scids, disabled_bitmap); + if (!flows) { + if (ecode) + *ecode = PAY_ROUTE_NOT_FOUND; + + if (fail) + *fail = tal_fmt( + ctx, + "failed to adjust htlcmin constraints."); + + goto function_fail; + } + // TODO: check issue #7136 + + /* Check the fee limits. */ + /* TODO: review this, only flows with non-zero amount */ + struct amount_msat fee; + if (!flowset_fee(&fee, flows)) { + if (ecode) + *ecode = PLUGIN_ERROR; + + if (fail) + *fail = tal_fmt(ctx, "flowset_fee failed"); + goto function_fail; + } + if (amount_msat_greater(fee, feebudget)) { + if (ecode) + *ecode = PAY_ROUTE_TOO_EXPENSIVE; + + if (fail) + *fail = tal_fmt( + ctx, + "Fee exceeds our fee budget, fee=%s " + "(feebudget=%s)", + fmt_amount_msat(this_ctx, fee), + fmt_amount_msat(this_ctx, feebudget)); + goto function_fail; + } + + /* Check the CLTV delay */ + /* TODO: review this, only flows with non-zero amounts */ + const u64 delay = flows_worst_delay(flows) + final_cltv; + if (delay > maxdelay) { + /* FIXME: What is a sane limit? */ + if (delay_feefactor > 1000) { + if (ecode) + *ecode = PAY_ROUTE_TOO_EXPENSIVE; + if (fail) + *fail = tal_fmt( + ctx, + "CLTV delay exceeds our CLTV " + "budget, delay=%" PRIu64 + "(maxdelay=%u)", + delay, maxdelay); + goto function_fail; + } + + delay_feefactor *= 2; + continue; // retry + } + + /* Compute the flows probability */ + /* TODO: review this, only flows with non-zero amounts */ + double prob = flowset_probability( + this_ctx, flows, gossmap, + uncertainty_get_chan_extra_map(uncertainty), NULL); + if (prob < 0) { + if (ecode) + *ecode = PLUGIN_ERROR; + if (fail) + *fail = + tal_fmt(ctx, "flowset_probability failed"); + goto function_fail; + } + + struct amount_msat delivering; + if (!flowset_delivers(&delivering, flows)) { + if (ecode) + *ecode = PLUGIN_ERROR; + + if (fail) + *fail = tal_fmt(ctx, "flowset_delivers failed"); + goto function_fail; + } + + /* OK, we are happy with these flows: convert to + * routes in the current payment. */ + delivering = AMOUNT_MSAT(0); + fee = AMOUNT_MSAT(0); + // TODO check ownership of these routes + for (size_t i = 0; i < tal_count(flows); i++) { + struct route *r = flow_to_route( + ctx, payment, payment->groupid, + payment->next_partid, payment->payment_hash, + final_cltv, gossmap, flows[i]); + if (!r) { + /* TODO: what could have gone wrong? */ + continue; + } + payment->next_partid++; + uncertainty_commit_htlcs(uncertainty, r); + tal_arr_expand(&routes, r); + + struct amount_msat route_fee = route_fees(r), + route_deliver = route_delivers(r); + + if (!amount_msat_add(&fee, fee, route_fee) || + !amount_msat_add(&delivering, delivering, + route_deliver)) { + if (ecode) + *ecode = PLUGIN_ERROR; + + if (fail) + *fail = tal_fmt( + ctx, + "%s (line %d) amount_msat " + "arithmetic overflow.", + __PRETTY_FUNCTION__, __LINE__); + goto function_fail; + } + } + + /* For the next iteration get me the amount_to_deliver */ + if (!amount_msat_sub(&amount_to_deliver, amount_to_deliver, + delivering)) { + /* In the next iteration we search routes that allocate + *amount_to_deliver - delivering If we have delivering > + *amount_to_deliver it means we have made a mistake + *somewhere. + */ + if (ecode) + *ecode = PLUGIN_ERROR; + + if (fail) + *fail = tal_fmt( + ctx, + "%s (line %d) delivering to destination " + "(%s) is more than requested (%s)", + __PRETTY_FUNCTION__, __LINE__, + fmt_amount_msat(this_ctx, delivering), + fmt_amount_msat(this_ctx, + amount_to_deliver)); + goto function_fail; + } + + /* For the next iteration get me the feebudget */ + if (!amount_msat_sub(&feebudget, feebudget, fee)) { + if (ecode) + *ecode = PLUGIN_ERROR; + + if (fail) + *fail = tal_fmt( + ctx, + "%s (line %d) routing fees (%s) exceed fee " + "budget (%s).", + __PRETTY_FUNCTION__, __LINE__, + fmt_amount_msat(this_ctx, fee), + fmt_amount_msat(this_ctx, feebudget)); + goto function_fail; + } + + /* For the next iteration get me the probability_budget */ + if (prob < 1e-10) { + /* this last flow probability is too small for division + */ + probability_budget = 1.0; + } else { + /* prob here is a conditional probability, the next + * round of flows will have a conditional probability + * prob2 and we would like that prob*prob2 >= + * probability_budget hence probability_budget/prob + * becomes the next iteration's target. */ + probability_budget = + MIN(1.0, probability_budget / prob); + } + } + + /* remove the temporary routes from the uncertainty network */ + uncertainty_remove_routes(uncertainty, routes); + + /* ownership */ + for (size_t i = 0; i < tal_count(routes); i++) + routes[i] = tal_steal(routes, routes[i]); + + tal_free(this_ctx); + return routes; + +function_fail: + /* remove the temporary routes from the uncertainty network */ + uncertainty_remove_routes(uncertainty, routes); + + /* Discard any routes we have constructed here. */ + tal_free(this_ctx); + return tal_free(routes); +} diff --git a/plugins/renepay/routebuilder.h b/plugins/renepay/routebuilder.h new file mode 100644 index 000000000000..e7d1e135010c --- /dev/null +++ b/plugins/renepay/routebuilder.h @@ -0,0 +1,22 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_ROUTEBUILDER_H +#define LIGHTNING_PLUGINS_RENEPAY_ROUTEBUILDER_H + +#include "config.h" +#include +#include +#include +#include +#include + +struct route **get_routes(const tal_t *ctx, struct payment *payment, + + const struct node_id *source, + const struct node_id *destination, + struct gossmap *gossmap, struct uncertainty *uncertainty, + + struct amount_msat amount_to_deliver, + const u32 final_cltv, struct amount_msat feebudget, + + enum jsonrpc_errcode *ecode, const char **fail); + +#endif /* LIGHTNING_PLUGINS_RENEPAY_ROUTEBUILDER_H */ From 8b5d7f681c37f98bbf4db3e3822aed55c49fe3e7 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 15:35:50 +0100 Subject: [PATCH 10/31] renepay: add routetracker.c Routetracker is a structure that is used to follow the progress of the route forwarding requests. --- plugins/renepay/routetracker.c | 357 +++++++++++++++++++++++++++++++++ plugins/renepay/routetracker.h | 53 +++++ 2 files changed, 410 insertions(+) create mode 100644 plugins/renepay/routetracker.c create mode 100644 plugins/renepay/routetracker.h diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c new file mode 100644 index 000000000000..0e5e32330cdf --- /dev/null +++ b/plugins/renepay/routetracker.c @@ -0,0 +1,357 @@ +#include +#include +#include +#include +#include +#include + +struct routetracker *new_routetracker(const tal_t *ctx) +{ + struct routetracker *rt = tal(ctx, struct routetracker); + + rt->sent_routes = tal(rt, struct route_map); + route_map_init(rt->sent_routes); + + rt->pending_routes = tal(rt, struct route_map); + route_map_init(rt->pending_routes); + + rt->finalized_routes = tal_arr(rt, struct route *, 0); + return rt; +} + +size_t routetracker_count_sent(struct routetracker *routetracker) +{ + return route_map_count(routetracker->sent_routes); +} + +void routetracker_cleanup(struct routetracker *routetracker) +{ + // TODO +} + +static void routetracker_add_to_final(struct routetracker *routetracker, + struct route *route) +{ + if (!route_map_del(routetracker->pending_routes, route)) + plugin_err(pay_plugin->plugin, + "%s: route with key %s is not in pending_routes", + __PRETTY_FUNCTION__, + fmt_routekey(tmpctx, &route->key)); + tal_arr_expand(&routetracker->finalized_routes, route); + tal_steal(routetracker, route); +} +static void route_is_success(struct route *route) +{ + routetracker_add_to_final(route->payment->routetracker, route); +} +void route_is_failure(struct route *route) +{ + routetracker_add_to_final(route->payment->routetracker, route); +} +static void route_sent(struct route *route) +{ + struct routetracker *routetracker = route->payment->routetracker; + route_map_add(routetracker->sent_routes, route); + tal_steal(routetracker, route); +} +static void route_sendpay_fail(struct route *route TAKES) +{ + struct routetracker *routetracker = route->payment->routetracker; + if (!route_map_del(routetracker->sent_routes, route)) + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, + "%s: route (%s) is not marked as sent", + __PRETTY_FUNCTION__, + fmt_routekey(tmpctx, &route->key)); + if (taken(route)) + tal_free(route); +} + +/* This route is pending, ie. locked in HTLCs. + * Called either: + * - after a sendpay is accepted, + * - or after listsendpays reveals some pending route that we didn't + * previously know about. */ +void route_pending(const struct route *route) +{ + assert(route); + struct payment *payment = route->payment; + assert(payment); + assert(payment->groupid == route->key.groupid); + struct routetracker *routetracker = payment->routetracker; + assert(routetracker); + + /* we already keep track of this route */ + if (route_map_get(routetracker->pending_routes, &route->key)) + return; + + if (!route_map_del(routetracker->sent_routes, route)) + plugin_log(pay_plugin->plugin, LOG_DBG, + "%s: tracking a route (%s) not computed by this " + "payment call", + __PRETTY_FUNCTION__, + fmt_routekey(tmpctx, &route->key)); + + uncertainty_commit_htlcs(pay_plugin->uncertainty, route); + route_map_add(routetracker->pending_routes, route); + tal_steal(routetracker, route); + + if (!amount_msat_add(&payment->total_sent, payment->total_sent, + route_sends(route)) || + !amount_msat_add(&payment->total_delivering, + payment->total_delivering, + route_delivers(route))) { + plugin_err(pay_plugin->plugin, + "%s: amount_msat arithmetic overflow.", + __PRETTY_FUNCTION__); + } +} + +static void route_result_collected(struct route *route TAKES) +{ + assert(route); + assert(route->result); + // TODO: also improve knowledge here? + uncertainty_remove_htlcs(pay_plugin->uncertainty, route); + + assert(route->payment); + struct payment *payment = route->payment; + assert(payment->groupid == route->key.groupid); + + if (route->result->status == SENDPAY_FAILED) { + if (!amount_msat_sub(&payment->total_delivering, + payment->total_delivering, + route_delivers(route)) || + !amount_msat_sub(&payment->total_sent, payment->total_sent, + route_sends(route))) { + plugin_err(pay_plugin->plugin, + "%s: routes do not add up to " + "payment total amount.", + __PRETTY_FUNCTION__); + } + } + if(taken(route)) + tal_free(route); +} + +/* Callback function for sendpay request success. */ +static struct command_result *sendpay_done(struct command *cmd, + const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct route *route) +{ + assert(route); + route_pending(route); + return command_still_pending(cmd); +} + +/* sendpay really only fails immediately in two ways: + * 1. We screwed up and misused the API. + * 2. The first peer is disconnected. + */ +static struct command_result *sendpay_failed(struct command *cmd, + const char *buf, + const jsmntok_t *tok, + struct route *route) +{ + assert(route); + assert(route->payment); + struct payment *payment = route->payment; + + enum jsonrpc_errcode errcode; + const char *msg; + const char *err; + + err = json_scan(tmpctx, buf, tok, "{code:%,message:%}", + JSON_SCAN(json_to_jsonrpc_errcode, &errcode), + JSON_SCAN_TAL(tmpctx, json_strdup, &msg)); + if (err) + plugin_err(pay_plugin->plugin, + "Unable to parse sendpay error: %s, json: %.*s", err, + json_tok_full_len(tok), json_tok_full(buf, tok)); + + payment_note(payment, LOG_INFORM, + "Sendpay failed: partid=%" PRIu64 + " errorcode:%d message=%s", + route->key.partid, errcode, msg); + + if (errcode != PAY_TRY_OTHER_ROUTE) { + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, + "Strange error from sendpay: %.*s", + json_tok_full_len(tok), json_tok_full(buf, tok)); + } + + /* There is no new knowledge from this kind of failure. + * We just disable this scid. */ + payment_disable_chan(payment, route->hops[0].scid, LOG_INFORM, + "sendpay didn't like first hop: %s", msg); + + route_sendpay_fail(take(route)); + return command_still_pending(cmd); +} + +void payment_collect_results(struct payment *payment, + struct preimage **payment_preimage, + enum jsonrpc_errcode *final_error, + const char **final_msg) +{ + assert(payment); + struct routetracker *routetracker = payment->routetracker; + assert(routetracker); + const size_t ncompleted = tal_count(routetracker->finalized_routes); + for (size_t i = 0; i < ncompleted; i++) { + struct route *r = routetracker->finalized_routes[i]; + assert(r); + assert(r->result); + + /* We should never start a new groupid while there are pending + * onions with a different groupid. */ + if (payment->groupid != r->key.groupid) { + plugin_err(pay_plugin->plugin, + "%s: current groupid=%" PRIu64 + ", but recieved a sendpay result with " + "groupid=%" PRIu64, + __PRETTY_FUNCTION__, payment->groupid, + r->key.groupid); + } + + assert(r->result->status == SENDPAY_COMPLETE || + r->result->status == SENDPAY_FAILED); + if (r->result->status == SENDPAY_COMPLETE && payment_preimage) { + assert(r->result->payment_preimage); + *payment_preimage = + tal_dup(payment, struct preimage, + r->result->payment_preimage); + } + + if (r->result->status == SENDPAY_FAILED) { + if (r->final_msg) { + if (final_error) + *final_error = r->final_error; + + if (final_msg) + *final_msg = + tal_strdup(tmpctx, r->final_msg); + } + } + route_result_collected(take(r)); + } + tal_resize(&routetracker->finalized_routes, 0); +} + +struct command_result *route_sendpay_request(struct command *cmd, + struct route *route) +{ + struct out_req *req = + jsonrpc_request_start(pay_plugin->plugin, cmd, "sendpay", + sendpay_done, sendpay_failed, route); + + json_add_route(req->js, route); + + route_sent(route); + return send_outreq(pay_plugin->plugin, req); +} + +struct command_result *notification_sendpay_failure(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + plugin_log(pay_plugin->plugin, LOG_DBG, + "sendpay_failure notification: %.*s", + json_tok_full_len(params), json_tok_full(buf, params)); + + // enum jsonrpc_errcode errcode; + const jsmntok_t *sub = json_get_member(buf, params, "sendpay_failure"); + + struct routekey *key = tal_routekey_from_json( + tmpctx, buf, json_get_member(buf, sub, "data")); + if (!key) + plugin_err(pay_plugin->plugin, + "Unable to get routekey from sendpay_failure: %.*s", + json_tok_full_len(sub), json_tok_full(buf, sub)); + + struct payment *payment = + payment_map_get(pay_plugin->payment_map, key->payment_hash); + + if (!payment) { + /* This sendpay is not linked to any route in our database, we + * skip it. */ + return notification_handled(cmd); + } + + assert(payment->routetracker); + struct route *route = + route_map_get(payment->routetracker->pending_routes, key); + if (!route) + plugin_err(pay_plugin->plugin, + "%s: key %s is not found in pending_routes", + __PRETTY_FUNCTION__, fmt_routekey(tmpctx, key)); + + assert(route->result == NULL); + route->result = tal_sendpay_result_from_json(route, buf, sub); + if (route->result == NULL) + plugin_err(pay_plugin->plugin, + "Unable to parse sendpay_failure: %.*s", + json_tok_full_len(sub), json_tok_full(buf, sub)); + + if (route->result->status != SENDPAY_FAILED) { + /* FIXME shouldn't this be always SENDPAY_FAILED? */ + const jsmntok_t *datatok = json_get_member(buf, sub, "data"); + const jsmntok_t *statustok = + json_get_member(buf, datatok, "status"); + const char *status_str = json_strdup(tmpctx, buf, statustok); + + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, + "sendpay_failure notification returned status=%s", + status_str); + route->result->status = SENDPAY_FAILED; + } + return routefail_start(route, route, cmd); +} + +struct command_result *notification_sendpay_success(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + plugin_log(pay_plugin->plugin, LOG_DBG, + "sendpay_success notification: %.*s", + json_tok_full_len(params), json_tok_full(buf, params)); + + const jsmntok_t *sub = json_get_member(buf, params, "sendpay_success"); + + struct routekey *key = tal_routekey_from_json(tmpctx, buf, sub); + if (!key) + plugin_err(pay_plugin->plugin, + "Unable to get routekey from sendpay_success: %.*s", + json_tok_full_len(sub), json_tok_full(buf, sub)); + + struct payment *payment = + payment_map_get(pay_plugin->payment_map, key->payment_hash); + + if (!payment) { + /* This sendpay is not linked to any route in our database, we + * skip it. */ + return notification_handled(cmd); + } + + assert(payment->routetracker); + struct route *route = + route_map_get(payment->routetracker->pending_routes, key); + if (!route) + plugin_err(pay_plugin->plugin, + "%s: key %s is not found in pending_routes", + __PRETTY_FUNCTION__, fmt_routekey(tmpctx, key)); + + assert(route->result == NULL); + route->result = tal_sendpay_result_from_json(route, buf, sub); + if (route->result == NULL) + plugin_err(pay_plugin->plugin, + "Unable to parse sendpay_success: %.*s", + json_tok_full_len(sub), json_tok_full(buf, sub)); + + assert(route->result->status == SENDPAY_COMPLETE); + + // FIXME: what happens when several success notification arrive for the + // same payment? Even after the payment has been resolved. + route_is_success(route); + return notification_handled(cmd); +} diff --git a/plugins/renepay/routetracker.h b/plugins/renepay/routetracker.h new file mode 100644 index 000000000000..9cc3de42e422 --- /dev/null +++ b/plugins/renepay/routetracker.h @@ -0,0 +1,53 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_ROUTETRACKER_H +#define LIGHTNING_PLUGINS_RENEPAY_ROUTETRACKER_H + +/* This module provides entry points for the management of a route thread. */ + +#include "config.h" +#include + +struct routetracker{ + struct route_map *sent_routes; + struct route_map *pending_routes; + struct route **finalized_routes; +}; + +struct routetracker *new_routetracker(const tal_t *ctx); +// bool routetracker_is_ready(const struct routetracker *routetracker); +void routetracker_cleanup(struct routetracker *routetracker); +size_t routetracker_count_sent(struct routetracker *routetracker); + +/* The payment has a list of route that have "returned". Calling this function + * payment will look through that list and process those routes' results: + * - update the commited amounts, + * - update the uncertainty network, + * - and free the allocated memory. */ +void payment_collect_results(struct payment *payment, + struct preimage **payment_preimage, + enum jsonrpc_errcode *final_error, + const char **final_msg); + +/* Announce that this route is pending and needs to be kept in the waiting list + * for notifications. */ +void route_pending(const struct route *route); + +/* Sends a sendpay request for this route. */ +struct command_result *route_sendpay_request(struct command *cmd, + struct route *route); + +struct command_result *notification_sendpay_failure(struct command *cmd, + const char *buf, + const jsmntok_t *params); + +struct command_result *notification_sendpay_success(struct command *cmd, + const char *buf, + const jsmntok_t *params); + +void route_is_failure(struct route *route); + +// FIXME: double-check that we actually get one notification for each sendpay, +// ie. that after some time we don't have yet pending sendpays for old failed or +// successful payments that we havent processed because we haven't received the +// notification + +#endif /* LIGHTNING_PLUGINS_RENEPAY_ROUTETRACKER_H */ From e83a4873302bc16428764e8069f355a829e2a255 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 15:37:33 +0100 Subject: [PATCH 11/31] renepay: add routefail.c Routefail consist mainly of an API `routefail_start` that handles the failure of a forwarding request (encoded in a route). Internally there is a routefail datastructure that goes through a series of execution steps, eg. updating the gossmap, updating the uncertainty network, etc. --- plugins/renepay/routefail.c | 381 ++++++++++++++++++++++++++++++++++++ plugins/renepay/routefail.h | 12 ++ 2 files changed, 393 insertions(+) create mode 100644 plugins/renepay/routefail.c create mode 100644 plugins/renepay/routefail.h diff --git a/plugins/renepay/routefail.c b/plugins/renepay/routefail.c new file mode 100644 index 000000000000..880f9d1171c9 --- /dev/null +++ b/plugins/renepay/routefail.c @@ -0,0 +1,381 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include + +struct routefail { + u64 exec_state; + struct command *cmd; + struct route *route; +}; + +struct routefail_modifier { + const char *name; + struct command_result *(*step_cb)(struct routefail *r); +}; + +#define REGISTER_ROUTEFAIL_MODIFIER(name, step_cb) \ + struct routefail_modifier name##_routefail_mod = { \ + stringify(name), \ + typesafe_cb_cast(struct command_result * (*)(struct routefail *), \ + struct command_result * (*)(struct routefail *), \ + step_cb), \ + }; + +static struct command_result *routefail_continue(struct routefail *r); + +struct command_result *routefail_start(const tal_t *ctx, struct route *route, + struct command *cmd) +{ + struct routefail *r = tal(ctx, struct routefail); + r->exec_state = 0; + r->route = route; + r->cmd = cmd; + assert(route->result); + return routefail_continue(r); +} + +void *routefail_virtual_program[]; +static struct command_result *routefail_continue(struct routefail *r) +{ + + assert(r->exec_state != INVALID_STATE); + const struct routefail_modifier *mod = + (const struct routefail_modifier *) + routefail_virtual_program[r->exec_state++]; + + if (mod == NULL) + plugin_err(pay_plugin->plugin, + "%s expected routefail_modifier " + "but NULL found", + __PRETTY_FUNCTION__); + + plugin_log(pay_plugin->plugin, LOG_DBG, "Calling routefail_modifier %s", + mod->name); + return mod->step_cb(r); +} + +/* Generic handler for RPC failures. */ +static struct command_result *routefail_rpc_failure(struct command *cmd, + const char *buffer, + const jsmntok_t *toks, + struct routefail *r) +{ + const jsmntok_t *codetok = json_get_member(buffer, toks, "code"); + u32 errcode; + if (codetok != NULL) + json_to_u32(buffer, codetok, &errcode); + else + errcode = LIGHTNINGD; + + plugin_err(r->cmd->plugin, + "routefail state machine has stopped due to a failed RPC " + "call: %.*s", + json_tok_full_len(toks), json_tok_full(buffer, toks)); + return command_still_pending(r->cmd); +} + +/***************************************************************************** + * end + * + * The default ending of routefail. + */ +static struct command_result *end_done(struct command *cmd, + const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct routefail *r) +{ + route_is_failure(r->route); + tal_free(r); + return notification_handled(cmd); +} +static struct command_result *end_cb(struct routefail *r) +{ + struct out_req *req = + jsonrpc_request_start(r->cmd->plugin, r->cmd, "waitblockheight", + end_done, routefail_rpc_failure, r); + json_add_num(req->js, "blockheight", 0); + return send_outreq(r->cmd->plugin, req); +} + +REGISTER_ROUTEFAIL_MODIFIER(end, end_cb); + +/***************************************************************************** + * update_gossip + * + * Update gossip from waitsendpay error message. + */ + +/* Fix up the channel_update to include the type if it doesn't currently have + * one. See ElementsProject/lightning#1730 and lightningnetwork/lnd#1599 for the + * in-depth discussion on why we break message parsing here... */ +static u8 *patch_channel_update(const tal_t *ctx, u8 *channel_update TAKES) +{ + u8 *fixed; + if (channel_update != NULL && + fromwire_peektype(channel_update) != WIRE_CHANNEL_UPDATE) { + /* This should be a channel_update, prefix with the + * WIRE_CHANNEL_UPDATE type, but isn't. Let's prefix it. */ + fixed = tal_arr(ctx, u8, 0); + towire_u16(&fixed, WIRE_CHANNEL_UPDATE); + towire(&fixed, channel_update, tal_bytelen(channel_update)); + if (taken(channel_update)) + tal_free(channel_update); + return fixed; + } else { + return tal_dup_talarr(ctx, u8, channel_update); + } +} + +/* Return NULL if the wrapped onion error message has no channel_update field, + * or return the embedded channel_update message otherwise. */ +static u8 *channel_update_from_onion_error(const tal_t *ctx, + const u8 *onion_message) +{ + u8 *channel_update = NULL; + struct amount_msat unused_msat; + u32 unused32; + + /* Identify failcodes that have some channel_update. + * + * TODO > BOLT 1.0: Add new failcodes when updating to a + * new BOLT version. */ + if (!fromwire_temporary_channel_failure(ctx, onion_message, + &channel_update) && + !fromwire_amount_below_minimum(ctx, onion_message, &unused_msat, + &channel_update) && + !fromwire_fee_insufficient(ctx, onion_message, &unused_msat, + &channel_update) && + !fromwire_incorrect_cltv_expiry(ctx, onion_message, &unused32, + &channel_update) && + !fromwire_expiry_too_soon(ctx, onion_message, &channel_update)) + /* No channel update. */ + return NULL; + + return patch_channel_update(ctx, take(channel_update)); +} + +static struct command_result *update_gossip_done(struct command *cmd UNUSED, + const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct routefail *r) +{ + return routefail_continue(r); +} + +static struct command_result *update_gossip_failure(struct command *cmd UNUSED, + const char *buf, + const jsmntok_t *result, + struct routefail *r) +{ + /* FIXME it might be too strong assumption that erring_channel should + * always be present here, but at least the documentation for + * waitsendpay says it is present in the case of error. */ + assert(r->route->result->erring_channel); + + /* TODO disable chan? what if we endup disabling a channel twice? maybe + * use a map instead of an array. */ + payment_disable_chan( + r->route->payment, *r->route->result->erring_channel, LOG_INFORM, + "addgossip failed (%.*s)", json_tok_full_len(result), + json_tok_full(buf, result)); + return routefail_continue(r); +} + +static struct command_result *update_gossip_cb(struct routefail *r) +{ + /* if there is no raw_message we continue */ + if (!r->route->result->raw_message) + goto skip_update_gossip; + + const u8 *update = channel_update_from_onion_error( + tmpctx, r->route->result->raw_message); + + if (!update) + goto skip_update_gossip; + + struct out_req *req = + jsonrpc_request_start(r->cmd->plugin, r->cmd, "addgossip", + update_gossip_done, update_gossip_failure, r); + json_add_hex_talarr(req->js, "message", update); + return send_outreq(r->cmd->plugin, req); + +skip_update_gossip: + return routefail_continue(r); +} + +REGISTER_ROUTEFAIL_MODIFIER(update_gossip, update_gossip_cb); + +/***************************************************************************** + * update_knowledge + * + * Update the uncertainty network from waitsendpay error message. + */ + +static struct command_result *update_knowledge_cb(struct routefail *r) +{ + const struct route *route = r->route; + const struct payment_result *result = route->result; + + /* FIXME: If we don't know the hops there isn't much we can infer, but + * a little bit we could. */ + if (!route->hops) + goto skip_update_network; + + uncertainty_channel_can_send(pay_plugin->uncertainty, r->route, + *result->erring_index); + + if (result->failcode == WIRE_TEMPORARY_CHANNEL_FAILURE && + *result->erring_index < tal_count(route->hops)) { + uncertainty_channel_cannot_send( + pay_plugin->uncertainty, + route->hops[*result->erring_index].scid, + route->hops[*result->erring_index].direction); + } + +skip_update_network: + return routefail_continue(r); +} + +REGISTER_ROUTEFAIL_MODIFIER(update_knowledge, update_knowledge_cb); + +/***************************************************************************** + * handle_cases + * + * Process the kind of error, we might decide that this payment cannot continue + * or is it worth continue trying. + */ + +/* Mark this as a final error. When read this route result will inmediately end + * the payment. */ +static void route_final_error(struct route *route, enum jsonrpc_errcode error, + const char *what) +{ + route->final_error = error; + route->final_msg = tal_strdup(route, what); +} + +static void handle_unhandleable_error(struct route *route, const char *what) +{ + if (!route->hops) + return; + size_t n = tal_count(route->hops); + + if (n == 1) { + /* This is a terminal error. */ + return route_final_error(route, PAY_UNPARSEABLE_ONION, what); + } + + /* Prefer a node not directly connected to either end. */ + if (n > 3) { + /* us ->0-> ourpeer ->1-> rando ->2-> theirpeer ->3-> dest */ + n = 1 + pseudorand(n - 2); + } else + /* Assume it's not the destination */ + n = pseudorand(n - 1); + + payment_disable_chan(route->payment, route->hops[n].scid, LOG_INFORM, + "randomly chosen"); +} + +static struct command_result *handle_cases_cb(struct routefail *r) +{ + struct route *route = r->route; + const struct payment_result *result = route->result; + + // TODO: i am not sure these are compulsory + assert(result->erring_index); + assert(result->erring_node); + + switch (result->code) { + case PAY_UNPARSEABLE_ONION: + handle_unhandleable_error( + route, "received PAY_UNPARSEABLE_ONION error"); + goto finish; + break; + case PAY_TRY_OTHER_ROUTE: + break; + case PAY_DESTINATION_PERM_FAIL: + default: + route_final_error(route, result->code, result->message); + goto finish; + } + + /* Final node is usually a hard failure */ + if (node_id_eq(result->erring_node, &route->payment->destination) && + result->failcode != WIRE_MPP_TIMEOUT) { + route_final_error(route, PAY_DESTINATION_PERM_FAIL, + "final destination permanent failure"); + goto finish; + } + + switch (result->failcode) { + /* These definitely mean eliminate channel */ + case WIRE_PERMANENT_CHANNEL_FAILURE: + case WIRE_REQUIRED_CHANNEL_FEATURE_MISSING: + /* FIXME: lnd returns this for disconnected peer, so don't disable perm! + */ + case WIRE_UNKNOWN_NEXT_PEER: + case WIRE_CHANNEL_DISABLED: + /* These mean node is weird, but we eliminate channel here too */ + case WIRE_INVALID_REALM: + case WIRE_TEMPORARY_NODE_FAILURE: + case WIRE_PERMANENT_NODE_FAILURE: + case WIRE_REQUIRED_NODE_FEATURE_MISSING: + /* These shouldn't happen, but eliminate channel */ + case WIRE_INVALID_ONION_VERSION: + case WIRE_INVALID_ONION_HMAC: + case WIRE_INVALID_ONION_KEY: + case WIRE_INVALID_ONION_PAYLOAD: + case WIRE_INVALID_ONION_BLINDING: + case WIRE_EXPIRY_TOO_FAR: + payment_disable_chan(route->payment, *result->erring_channel, + LOG_UNUSUAL, "%s", + onion_wire_name(result->failcode)); + break; + + /* These can be fixed (maybe) by applying the included channel_update */ + case WIRE_AMOUNT_BELOW_MINIMUM: + case WIRE_FEE_INSUFFICIENT: + case WIRE_INCORRECT_CLTV_EXPIRY: + case WIRE_EXPIRY_TOO_SOON: + case WIRE_TEMPORARY_CHANNEL_FAILURE: + break; + + /* These should only come from the final distination. */ + case WIRE_MPP_TIMEOUT: + case WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: + case WIRE_FINAL_INCORRECT_CLTV_EXPIRY: + case WIRE_FINAL_INCORRECT_HTLC_AMOUNT: + break; + + default: + /* FIXME: remember you might be disabling the same channel + * multiple times */ + payment_disable_chan(route->payment, *result->erring_channel, + LOG_UNUSUAL, "Unexpected error code %u", + result->failcode); + } + +finish: + return routefail_continue(r); +} + +REGISTER_ROUTEFAIL_MODIFIER(handle_cases, handle_cases_cb); + +/***************************************************************************** + * Virtual machine */ +// TODO: maybe I should make a single virtual machine interpreter (with +// templates and typesafety?) that is able to run on different static programs +// and types. One instance will execute the payment program and another instance +// will run the routefail program. + +void *routefail_virtual_program[] = { + &update_gossip_routefail_mod, + &update_knowledge_routefail_mod, + &handle_cases_routefail_mod, + &end_routefail_mod, + NULL}; diff --git a/plugins/renepay/routefail.h b/plugins/renepay/routefail.h new file mode 100644 index 000000000000..bad3aabef283 --- /dev/null +++ b/plugins/renepay/routefail.h @@ -0,0 +1,12 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_ROUTEFAIL_H +#define LIGHTNING_PLUGINS_RENEPAY_ROUTEFAIL_H + +/* This module provides the state machine for handling route failures. */ + +#include "config.h" +#include + +struct command_result *routefail_start(const tal_t *ctx, struct route *route, + struct command *cmd); + +#endif /* LIGHTNING_PLUGINS_RENEPAY_ROUTEFAIL_H */ From 2f5854b8e8728d7cdfcaf2830820992cda7d324a Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 15:41:18 +0100 Subject: [PATCH 12/31] renepay: add error codes Using enum renepay_errorcode simplifies the low level API of chan_extra and flow. We can extract information about the nature of a function call failure from its return value. --- plugins/renepay/errorcodes.c | 28 ++++++++++++++++++++++++++++ plugins/renepay/errorcodes.h | 18 ++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 plugins/renepay/errorcodes.c create mode 100644 plugins/renepay/errorcodes.h diff --git a/plugins/renepay/errorcodes.c b/plugins/renepay/errorcodes.c new file mode 100644 index 000000000000..a783c5216771 --- /dev/null +++ b/plugins/renepay/errorcodes.c @@ -0,0 +1,28 @@ +#include +#include +#include + +const char *renepay_errorcode_name(enum renepay_errorcode e) +{ + static char invalidbuf[sizeof("INVALID ") + STR_MAX_CHARS(e)]; + + switch (e) { + case RENEPAY_NOERROR: + return "RENEPAY_NOERROR"; + case RENEPAY_AMOUNT_OVERFLOW: + return "RENEPAY_AMOUNT_OVERFLOW"; + case RENEPAY_CHANNEL_NOT_FOUND: + return "RENEPAY_CHANNEL_NOT_FOUND"; + case RENEPAY_BAD_CHANNEL: + return "RENEPAY_BAD_CHANNEL"; + case RENEPAY_BAD_ALLOCATION: + return "RENEPAY_BAD_ALLOCATION"; + case RENEPAY_PRECONDITION_ERROR: + return "RENEPAY_PRECONDITION_ERROR"; + case RENEPAY_UNEXPECTED: + return "RENEPAY_UNEXPECTED"; + } + + snprintf(invalidbuf, sizeof(invalidbuf), "INVALID %i", e); + return invalidbuf; +} diff --git a/plugins/renepay/errorcodes.h b/plugins/renepay/errorcodes.h new file mode 100644 index 000000000000..2efdfa7caf2e --- /dev/null +++ b/plugins/renepay/errorcodes.h @@ -0,0 +1,18 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_ERRORCODES_H +#define LIGHTNING_PLUGINS_RENEPAY_ERRORCODES_H + +/* Common types of failures for low level functions in renepay. */ +enum renepay_errorcode { + RENEPAY_NOERROR = 0, + + RENEPAY_AMOUNT_OVERFLOW, + RENEPAY_CHANNEL_NOT_FOUND, + RENEPAY_BAD_CHANNEL, + RENEPAY_BAD_ALLOCATION, + RENEPAY_PRECONDITION_ERROR, + RENEPAY_UNEXPECTED, +}; + +const char *renepay_errorcode_name(enum renepay_errorcode e); + +#endif /* LIGHTNING_PLUGINS_RENEPAY_ERRORCODES_H */ From 5b0d84fad9573e671d068bfa9eb152d0f2bb829f Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 15:43:59 +0100 Subject: [PATCH 13/31] renepay: add a helper module json.c --- plugins/renepay/json.c | 281 +++++++++++++++++++++++++++++++++++++++++ plugins/renepay/json.h | 18 +++ 2 files changed, 299 insertions(+) create mode 100644 plugins/renepay/json.c create mode 100644 plugins/renepay/json.h diff --git a/plugins/renepay/json.c b/plugins/renepay/json.c new file mode 100644 index 000000000000..eaa200920a2c --- /dev/null +++ b/plugins/renepay/json.c @@ -0,0 +1,281 @@ +#include +#include + +/* See if this notification is about one of our flows. */ +struct routekey *tal_routekey_from_json(const tal_t *ctx, const char *buf, + const jsmntok_t *obj) +{ + struct routekey *key = tal(ctx, struct routekey); + + const jsmntok_t *hashtok = json_get_member(buf, obj, "payment_hash"); + const jsmntok_t *groupidtok = json_get_member(buf, obj, "groupid"); + const jsmntok_t *partidtok = json_get_member(buf, obj, "partid"); + + if (hashtok == NULL || groupidtok == NULL) + goto fail; + + if (!json_to_u64(buf, groupidtok, &key->groupid)) + goto fail; + if (!json_to_sha256(buf, hashtok, &key->payment_hash)) + goto fail; + if (partidtok == NULL) + key->partid = 0; + else if (!json_to_u64(buf, partidtok, &key->partid)) + goto fail; + + return key; +fail: + + return tal_free(key); +} +struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, + const char *buffer, + const jsmntok_t *toks) +{ + const jsmntok_t *idtok = json_get_member(buffer, toks, "id"); + const jsmntok_t *hashtok = + json_get_member(buffer, toks, "payment_hash"); + const jsmntok_t *senttok = + json_get_member(buffer, toks, "amount_sent_msat"); + const jsmntok_t *statustok = json_get_member(buffer, toks, "status"); + const jsmntok_t *preimagetok = + json_get_member(buffer, toks, "payment_preimage"); + const jsmntok_t *codetok = json_get_member(buffer, toks, "code"); + const jsmntok_t *datatok = json_get_member(buffer, toks, "data"); + const jsmntok_t *erridxtok, *msgtok, *failcodetok, *rawmsgtok, + *failcodenametok, *errchantok, *errnodetok, *errdirtok; + struct payment_result *result; + + /* Check if we have an error and need to descend into data to get + * details. */ + if (codetok != NULL && datatok != NULL) { + idtok = json_get_member(buffer, datatok, "id"); + hashtok = json_get_member(buffer, datatok, "payment_hash"); + senttok = json_get_member(buffer, datatok, "amount_sent_msat"); + statustok = json_get_member(buffer, datatok, "status"); + } + + /* Initial sanity checks, all these fields must exist. */ + if (idtok == NULL || idtok->type != JSMN_PRIMITIVE || hashtok == NULL || + hashtok->type != JSMN_STRING || senttok == NULL || + statustok == NULL || statustok->type != JSMN_STRING) { + return NULL; + } + + result = tal(ctx, struct payment_result); + + if (codetok != NULL) + // u32? isn't this an int? + // json_to_u32(buffer, codetok, &result->code); + json_to_int(buffer, codetok, &result->code); + else + result->code = 0; + + json_to_u64(buffer, idtok, &result->id); + json_to_msat(buffer, senttok, &result->amount_sent); + if (json_tok_streq(buffer, statustok, "pending")) { + result->status = SENDPAY_PENDING; + } else if (json_tok_streq(buffer, statustok, "complete")) { + result->status = SENDPAY_COMPLETE; + } else if (json_tok_streq(buffer, statustok, "failed")) { + result->status = SENDPAY_FAILED; + } else { + goto fail; + } + + if (preimagetok != NULL) { + result->payment_preimage = tal(result, struct preimage); + json_to_preimage(buffer, preimagetok, result->payment_preimage); + } + + /* Now extract the error details if the error code is not 0 */ + if (result->code != 0) { + erridxtok = json_get_member(buffer, datatok, "erring_index"); + errnodetok = json_get_member(buffer, datatok, "erring_node"); + errchantok = json_get_member(buffer, datatok, "erring_channel"); + errdirtok = + json_get_member(buffer, datatok, "erring_direction"); + failcodetok = json_get_member(buffer, datatok, "failcode"); + failcodenametok = + json_get_member(buffer, datatok, "failcodename"); + msgtok = json_get_member(buffer, toks, "message"); + rawmsgtok = json_get_member(buffer, datatok, "raw_message"); + if (failcodetok == NULL || + failcodetok->type != JSMN_PRIMITIVE || + (failcodenametok != NULL && + failcodenametok->type != JSMN_STRING) || + (erridxtok != NULL && erridxtok->type != JSMN_PRIMITIVE) || + (errnodetok != NULL && errnodetok->type != JSMN_STRING) || + (errchantok != NULL && errchantok->type != JSMN_STRING) || + (errdirtok != NULL && errdirtok->type != JSMN_PRIMITIVE) || + msgtok == NULL || msgtok->type != JSMN_STRING || + (rawmsgtok != NULL && rawmsgtok->type != JSMN_STRING)) + goto fail; + + if (rawmsgtok != NULL) + result->raw_message = + json_tok_bin_from_hex(result, buffer, rawmsgtok); + else + result->raw_message = NULL; + + if (failcodenametok != NULL) + result->failcodename = + json_strdup(result, buffer, failcodenametok); + else + result->failcodename = NULL; + + json_to_u32(buffer, failcodetok, &result->failcode); + result->message = json_strdup(result, buffer, msgtok); + + if (erridxtok != NULL) { + result->erring_index = tal(result, u32); + json_to_u32(buffer, erridxtok, result->erring_index); + } else { + result->erring_index = NULL; + } + + if (errdirtok != NULL) { + result->erring_direction = tal(result, int); + json_to_int(buffer, errdirtok, + result->erring_direction); + } else { + result->erring_direction = NULL; + } + + if (errnodetok != NULL) { + result->erring_node = tal(result, struct node_id); + json_to_node_id(buffer, errnodetok, + result->erring_node); + } else { + result->erring_node = NULL; + } + + if (errchantok != NULL) { + result->erring_channel = + tal(result, struct short_channel_id); + json_to_short_channel_id(buffer, errchantok, + result->erring_channel); + } else { + result->erring_channel = NULL; + } + } + + return result; +fail: + return tal_free(result); +} + +// TODO add verbose option to include more or less details or change the schema, +// checkout docs/schema/renepay.schema.json and +// docs/schema/renepaystatus.schema.json +void json_add_payment(struct json_stream *s, const struct payment *payment) +{ + assert(s); + assert(payment); + + if (payment->label != NULL) + json_add_string(s, "label", payment->label); + if (payment->invstr != NULL) + json_add_invstring(s, payment->invstr); + + json_add_amount_msat(s, "amount_msat", payment->amount); + json_add_sha256(s, "payment_hash", &payment->payment_hash); + json_add_node_id(s, "destination", &payment->destination); + + if (payment->description) + json_add_string(s, "description", payment->description); + + json_add_timeabs(s, "created_at", payment->start_time); + json_add_u64(s, "groupid", payment->groupid); + json_add_u64(s, "parts", payment->next_partid); + + switch (payment->status) { + case PAYMENT_SUCCESS: + assert(payment->preimage); + + json_add_string(s, "status", "complete"); + json_add_preimage(s, "payment_preimage", payment->preimage); + json_add_amount_msat(s, "amount_sent_msat", + payment->total_sent); + break; + case PAYMENT_FAIL: + json_add_string(s, "status", "failed"); + break; + case PAYMENT_PENDING: + json_add_string(s, "status", "pending"); + break; + } + + // FIXME: add more verbose outputs? + // json_array_start(s, "notes"); + // for (size_t i = 0; i < tal_count(payment->paynotes); i++) + // json_add_string(s, NULL, payment->paynotes[i]); + // json_array_end(s); + + // TODO(eduardo): maybe we should add also: + // - payment_secret? + // - payment_metadata? + // - number of parts? +} + +void json_add_route(struct json_stream *js, const struct route *route) +{ + assert(js); + assert(route); + + struct payment *payment = route->payment; + assert(payment); + + assert(route->hops); + const size_t pathlen = tal_count(route->hops); + + json_array_start(js, "route"); + /* An empty route means a payment to oneself, pathlen=0 */ + for (size_t j = 0; j < pathlen; j++) { + const struct route_hop *hop = &route->hops[j]; + + json_object_start(js, NULL); + json_add_node_id(js, "id", &hop->node_id); + json_add_short_channel_id(js, "channel", hop->scid); + json_add_amount_msat(js, "amount_msat", hop->amount); + json_add_num(js, "direction", hop->direction); + json_add_u32(js, "delay", hop->delay); + json_add_string(js, "style", "tlv"); + json_object_end(js); + } + json_array_end(js); + json_add_sha256(js, "payment_hash", &payment->payment_hash); + + if (payment->payment_secret) + json_add_secret(js, "payment_secret", payment->payment_secret); + + /* FIXME: sendpay has a check that we don't total more than + * the exact amount, if we're setting partid (i.e. MPP). + * However, we always set partid, and we add a shadow amount if + * we've only have one part, so we have to use that amount + * here. + * + * The spec was loosened so you are actually allowed + * to overpay, so this check is now overzealous. */ + if (pathlen > 0 && + amount_msat_greater(route_delivers(route), payment->amount)) { + json_add_amount_msat(js, "amount_msat", route_delivers(route)); + } else { + json_add_amount_msat(js, "amount_msat", payment->amount); + } + json_add_u64(js, "partid", route->key.partid); + json_add_u64(js, "groupid", route->key.groupid); + + /* FIXME: some of these fields might not be required for all + * payment parts. */ + json_add_string(js, "bolt11", payment->invstr); + + if (payment->payment_metadata) + json_add_hex_talarr(js, "payment_metadata", + payment->payment_metadata); + if (payment->label) + json_add_string(js, "label", payment->label); + if (payment->description) + json_add_string(js, "description", payment->description); + +} diff --git a/plugins/renepay/json.h b/plugins/renepay/json.h new file mode 100644 index 000000000000..ef1a483db82d --- /dev/null +++ b/plugins/renepay/json.h @@ -0,0 +1,18 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_JSON_H +#define LIGHTNING_PLUGINS_RENEPAY_JSON_H + +#include +#include + +struct routekey *tal_routekey_from_json(const tal_t *ctx, const char *buf, + const jsmntok_t *obj); + +struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, + const char *buffer, + const jsmntok_t *toks); + +void json_add_payment(struct json_stream *s, const struct payment *payment); + +void json_add_route(struct json_stream *s, const struct route *route); + +#endif /* LIGHTNING_PLUGINS_RENEPAY_JSON_H */ From e238ae831f50837dce7b553aee9286a38d66558c Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 15:44:39 +0100 Subject: [PATCH 14/31] renepay: update the Makefile --- plugins/renepay/Makefile | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/plugins/renepay/Makefile b/plugins/renepay/Makefile index dbbf40c5e85a..a470c00ac436 100644 --- a/plugins/renepay/Makefile +++ b/plugins/renepay/Makefile @@ -1,7 +1,35 @@ -PLUGIN_RENEPAY_SRC := plugins/renepay/pay.c plugins/renepay/pay_flow.c plugins/renepay/flow.c plugins/renepay/mcf.c plugins/renepay/dijkstra.c \ - plugins/renepay/payment.c plugins/renepay/uncertainty_network.c -PLUGIN_RENEPAY_HDRS := plugins/renepay/pay.h plugins/renepay/pay_flow.h plugins/renepay/flow.h plugins/renepay/mcf.h plugins/renepay/dijkstra.h \ - plugins/renepay/payment.h plugins/renepay/uncertainty_network.h +PLUGIN_RENEPAY_SRC := \ + plugins/renepay/main.c \ + plugins/renepay/flow.c \ + plugins/renepay/mcf.c \ + plugins/renepay/dijkstra.c \ + plugins/renepay/payment.c \ + plugins/renepay/chan_extra.c \ + plugins/renepay/route.c \ + plugins/renepay/routebuilder.c \ + plugins/renepay/routetracker.c \ + plugins/renepay/routefail.c \ + plugins/renepay/uncertainty.c \ + plugins/renepay/mods.c \ + plugins/renepay/errorcodes.c \ + plugins/renepay/json.c + +PLUGIN_RENEPAY_HDRS := \ + plugins/renepay/payplugin.h \ + plugins/renepay/flow.h \ + plugins/renepay/mcf.h \ + plugins/renepay/dijkstra.h \ + plugins/renepay/payment.h \ + plugins/renepay/chan_extra.h \ + plugins/renepay/route.h \ + plugins/renepay/routebuilder.h \ + plugins/renepay/routetracker.h \ + plugins/renepay/routefail.h \ + plugins/renepay/uncertainty.h \ + plugins/renepay/mods.h \ + plugins/renepay/errorcodes.h \ + plugins/renepay/json.c + PLUGIN_RENEPAY_OBJS := $(PLUGIN_RENEPAY_SRC:.c=.o) # Make sure these depend on everything. From aaab6b7c16967bb7c3d73dd639fed671622d2d01 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 15:45:35 +0100 Subject: [PATCH 15/31] renepay: add a test for concurrent payments --- tests/test_renepay.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test_renepay.py b/tests/test_renepay.py index 99f654a9a859..2aabfec44674 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -13,6 +13,7 @@ import json import subprocess import os +import re def test_simple(node_factory): @@ -681,3 +682,39 @@ def test_htlcmax0(node_factory): l1.wait_for_htlcs() invoice = only_one(l6.rpc.listinvoices("inv")["invoices"]) assert invoice["amount_received_msat"] >= Millisatoshi("600000sat") + + +def test_concurrency(node_factory): + l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True, opts=[{}, {}, {}]) + inv = l3.rpc.invoice("1000sat", "test_renepay", "description")["bolt11"] + p1 = subprocess.Popen( + [ + "cli/lightning-cli", + "--network={}".format(TEST_NETWORK), + "--lightning-dir={}".format(l1.daemon.lightning_dir), + "-k", + "renepay", + "invstring={}".format(inv), + ], + stdout=subprocess.PIPE, + ) + # make several other spurious requests + for i in range(4): + subprocess.Popen( + [ + "cli/lightning-cli", + "--network={}".format(TEST_NETWORK), + "--lightning-dir={}".format(l1.daemon.lightning_dir), + "-k", + "renepay", + "invstring={}".format(inv), + ], + stdout=subprocess.PIPE, + ) + p1.wait(timeout=60) + # remove comments from the output before parsing the json + out1 = json.loads(re.sub("#.*?\n", "", p1.stdout.read().decode())) + assert out1["status"] == "complete" + assert out1["amount_msat"] == Millisatoshi("1000sat") + invoice = only_one(l3.rpc.listinvoices("test_renepay")["invoices"]) + assert invoice["amount_received_msat"] >= Millisatoshi("1000sat") From a030c237bd996791c33eee527e9d47631d9b7e6b Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 15:50:12 +0100 Subject: [PATCH 16/31] renepay: update unit tests --- plugins/renepay/test/Makefile | 5 +- plugins/renepay/test/run-arc.c | 21 +--- plugins/renepay/test/run-mcf-diamond.c | 20 ++-- plugins/renepay/test/run-mcf.c | 73 +++++++------- plugins/renepay/test/run-payflow_map.c | 108 -------------------- plugins/renepay/test/run-route_map.c | 84 ++++++++++++++++ plugins/renepay/test/run-testflow.c | 134 ++++++++++++++----------- 7 files changed, 214 insertions(+), 231 deletions(-) delete mode 100644 plugins/renepay/test/run-payflow_map.c create mode 100644 plugins/renepay/test/run-route_map.c diff --git a/plugins/renepay/test/Makefile b/plugins/renepay/test/Makefile index 90f85de42579..f0f56d03f184 100644 --- a/plugins/renepay/test/Makefile +++ b/plugins/renepay/test/Makefile @@ -9,9 +9,10 @@ ALL_TEST_PROGRAMS += $(PLUGIN_RENEPAY_TEST_PROGRAMS) $(PLUGIN_RENEPAY_TEST_OBJS): $(PLUGIN_RENEPAY_SRC) PLUGIN_RENEPAY_TEST_COMMON_OBJS := \ - plugins/renepay/dijkstra.o + plugins/renepay/dijkstra.o \ + plugins/renepay/chan_extra.o -$(PLUGIN_RENEPAY_TEST_PROGRAMS): $(PLUGIN_RENEPAY_TEST_COMMON_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) bitcoin/chainparams.o common/gossmap.o common/fp16.o common/dijkstra.o common/bolt12.o common/bolt12_merkle.o wire/bolt12_wiregen.o +$(PLUGIN_RENEPAY_TEST_PROGRAMS): $(PLUGIN_RENEPAY_TEST_COMMON_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) bitcoin/chainparams.o common/gossmap.o common/fp16.o common/dijkstra.o check-renepay: $(PLUGIN_RENEPAY_TEST_PROGRAMS:%=unittest/%) diff --git a/plugins/renepay/test/run-arc.c b/plugins/renepay/test/run-arc.c index 4d7c63a1e1bc..cd5901aa2c39 100644 --- a/plugins/renepay/test/run-arc.c +++ b/plugins/renepay/test/run-arc.c @@ -10,32 +10,13 @@ #include #include +#include "../flow.c" #include "../mcf.c" /* AUTOGENERATED MOCKS START */ -/* Generated stub for flow_complete */ -bool flow_complete(const tal_t *ctx UNNEEDED, struct flow *flow UNNEEDED, - const struct gossmap *gossmap UNNEEDED, - struct chan_extra_map *chan_extra_map UNNEEDED, - struct amount_msat delivered UNNEEDED, char **fail UNNEEDED) -{ fprintf(stderr, "flow_complete called!\n"); abort(); } -/* Generated stub for flowset_fee */ -bool flowset_fee(struct amount_msat *fee UNNEEDED, struct flow **flows UNNEEDED) -{ fprintf(stderr, "flowset_fee called!\n"); abort(); } -/* Generated stub for flowset_probability */ -double flowset_probability(const tal_t *ctx UNNEEDED, struct flow **flows UNNEEDED, - const struct gossmap *const gossmap UNNEEDED, - struct chan_extra_map *chan_extra_map UNNEEDED, char **fail UNNEEDED) -{ fprintf(stderr, "flowset_probability called!\n"); abort(); } /* Generated stub for fromwire_blinded_path */ struct blinded_path *fromwire_blinded_path(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *plen UNNEEDED) { fprintf(stderr, "fromwire_blinded_path called!\n"); abort(); } -/* Generated stub for get_chan_extra_half_by_chan */ -struct chan_extra_half *get_chan_extra_half_by_chan(const struct gossmap *gossmap UNNEEDED, - struct chan_extra_map *chan_extra_map UNNEEDED, - const struct gossmap_chan *chan UNNEEDED, - int dir UNNEEDED) -{ fprintf(stderr, "get_chan_extra_half_by_chan called!\n"); abort(); } /* Generated stub for towire_blinded_path */ void towire_blinded_path(u8 **p UNNEEDED, const struct blinded_path *blinded_path UNNEEDED) { fprintf(stderr, "towire_blinded_path called!\n"); abort(); } diff --git a/plugins/renepay/test/run-mcf-diamond.c b/plugins/renepay/test/run-mcf-diamond.c index 1a1442a7b921..3b9866771134 100644 --- a/plugins/renepay/test/run-mcf-diamond.c +++ b/plugins/renepay/test/run-mcf-diamond.c @@ -3,7 +3,7 @@ #define RENEPAY_UNITTEST // logs are written in /tmp/debug.txt #include "../payment.c" #include "../flow.c" -#include "../uncertainty_network.c" +#include "../uncertainty.c" #include "../mcf.c" #include @@ -21,16 +21,20 @@ /* Generated stub for fromwire_blinded_path */ struct blinded_path *fromwire_blinded_path(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *plen UNNEEDED) { fprintf(stderr, "fromwire_blinded_path called!\n"); abort(); } +/* Generated stub for json_add_payment */ +void json_add_payment(struct json_stream *s UNNEEDED, const struct payment *payment UNNEEDED) +{ fprintf(stderr, "json_add_payment called!\n"); abort(); } +/* Generated stub for new_routetracker */ +struct routetracker *new_routetracker(const tal_t *ctx UNNEEDED) +{ fprintf(stderr, "new_routetracker called!\n"); abort(); } /* Generated stub for pay_plugin */ struct pay_plugin *pay_plugin; +/* Generated stub for routetracker_cleanup */ +void routetracker_cleanup(struct routetracker *routetracker UNNEEDED) +{ fprintf(stderr, "routetracker_cleanup called!\n"); abort(); } /* Generated stub for towire_blinded_path */ void towire_blinded_path(u8 **p UNNEEDED, const struct blinded_path *blinded_path UNNEEDED) { fprintf(stderr, "towire_blinded_path called!\n"); abort(); } -/* Generated stub for try_paying */ -const char *try_paying(const tal_t *ctx UNNEEDED, - struct payment *payment UNNEEDED, - enum jsonrpc_errcode *ecode UNNEEDED) -{ fprintf(stderr, "try_paying called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ static u8 empty_map[] = { @@ -59,8 +63,8 @@ static const char* print_flows( tal_append_fmt(&buff,"%s%s", j ? "->" : "", fmt_short_channel_id(this_ctx, scid)); } - delivered = flows[i]->amounts[tal_count(flows[i]->amounts)-1]; - if (!amount_msat_sub(&fee, flows[i]->amounts[0], delivered)) + delivered = flows[i]->amount; + if (!flow_fee(&fee, flows[i])) { abort(); } diff --git a/plugins/renepay/test/run-mcf.c b/plugins/renepay/test/run-mcf.c index a6fd478d19c0..f22f763caeb3 100644 --- a/plugins/renepay/test/run-mcf.c +++ b/plugins/renepay/test/run-mcf.c @@ -3,7 +3,8 @@ #define RENEPAY_UNITTEST // logs are written in /tmp/debug.txt #include "../payment.c" #include "../flow.c" -#include "../uncertainty_network.c" +#include "../route.c" +#include "../uncertainty.c" #include "../mcf.c" #include @@ -21,16 +22,20 @@ /* Generated stub for fromwire_blinded_path */ struct blinded_path *fromwire_blinded_path(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *plen UNNEEDED) { fprintf(stderr, "fromwire_blinded_path called!\n"); abort(); } +/* Generated stub for json_add_payment */ +void json_add_payment(struct json_stream *s UNNEEDED, const struct payment *payment UNNEEDED) +{ fprintf(stderr, "json_add_payment called!\n"); abort(); } +/* Generated stub for new_routetracker */ +struct routetracker *new_routetracker(const tal_t *ctx UNNEEDED) +{ fprintf(stderr, "new_routetracker called!\n"); abort(); } /* Generated stub for pay_plugin */ struct pay_plugin *pay_plugin; +/* Generated stub for routetracker_cleanup */ +void routetracker_cleanup(struct routetracker *routetracker UNNEEDED) +{ fprintf(stderr, "routetracker_cleanup called!\n"); abort(); } /* Generated stub for towire_blinded_path */ void towire_blinded_path(u8 **p UNNEEDED, const struct blinded_path *blinded_path UNNEEDED) { fprintf(stderr, "towire_blinded_path called!\n"); abort(); } -/* Generated stub for try_paying */ -const char *try_paying(const tal_t *ctx UNNEEDED, - struct payment *payment UNNEEDED, - enum jsonrpc_errcode *ecode UNNEEDED) -{ fprintf(stderr, "try_paying called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ static void swap(int *a, int *b) @@ -308,8 +313,8 @@ static const char *print_flows( tal_append_fmt(&buff,"%s%s", j ? "->" : "", fmt_short_channel_id(this_ctx, scid)); } - delivered = flows[i]->amounts[tal_count(flows[i]->amounts)-1]; - if (!amount_msat_sub(&fee, flows[i]->amounts[0], delivered)) + delivered = flows[i]->amount; + if (!flow_fee(&fee, flows[i])) abort(); tal_append_fmt(&buff," prob %.2f, %s delivered with fee %s\n", flows[i]->success_prob, @@ -327,8 +332,10 @@ int main(int argc, char *argv[]) struct gossmap *gossmap; struct node_id l1, l2, l3; struct flow **flows; + struct route **routes; struct short_channel_id scid12, scid23; - struct chan_extra_map *chan_extra_map; + struct sha256 payment_hash; + struct amount_msat *amounts; char *errmsg; @@ -348,16 +355,15 @@ int main(int argc, char *argv[]) assert(short_channel_id_from_str("110x1x0", 7, &scid12)); assert(short_channel_id_from_str("103x1x0", 7, &scid23)); - chan_extra_map = tal(tmpctx, struct chan_extra_map); - chan_extra_map_init(chan_extra_map); - uncertainty_network_update(gossmap,chan_extra_map); + struct uncertainty *uncertainty = uncertainty_new(tmpctx); + uncertainty_update(uncertainty, gossmap); printf("All set, now let's call minflow ...\n"); flows = minflow(tmpctx, gossmap, gossmap_find_node(gossmap, &l1), gossmap_find_node(gossmap, &l3), - chan_extra_map, NULL, + uncertainty_get_chan_extra_map(uncertainty), NULL, /* Half the capacity */ AMOUNT_MSAT(500000000), /* max_fee = */ AMOUNT_MSAT(1000000), // 1k sats @@ -372,28 +378,33 @@ int main(int argc, char *argv[]) printf("Minflow has failed with: %s", errmsg); assert(0 && "minflow failed"); } - if(commit_flowset(tmpctx, gossmap,chan_extra_map,flows,NULL)l2->l3", gossmap, flows)); printf("Checking results.\n"); /* Should go 1->2->3 */ + amounts = tal_flow_amounts(tmpctx, flows[0]); + assert(amounts); assert(tal_count(flows) == 1); assert(tal_count(flows[0]->path) == 2); assert(tal_count(flows[0]->dirs) == 2); - assert(tal_count(flows[0]->amounts) == 2); + assert(tal_count(amounts) == 2); assert(flows[0]->path[0] == gossmap_find_chan(gossmap, &scid12)); assert(flows[0]->path[1] == gossmap_find_chan(gossmap, &scid23)); assert(flows[0]->dirs[0] == 1); assert(flows[0]->dirs[1] == 0); - assert(amount_msat_eq(flows[0]->amounts[1], AMOUNT_MSAT(500000000))); + assert(amount_msat_eq(amounts[1], AMOUNT_MSAT(500000000))); /* fee_base_msat == 20, fee_proportional_millionths == 1000 */ - assert(amount_msat_eq(flows[0]->amounts[0], AMOUNT_MSAT(500000000 + 500000 + 20))); + assert(amount_msat_eq(amounts[0], AMOUNT_MSAT(500000000 + 500000 + 20))); /* Each one has probability ~ 0.5 */ assert(flows[0]->success_prob > 0.249); @@ -401,7 +412,7 @@ int main(int argc, char *argv[]) /* Should have filled in some extra data! */ - struct chan_extra *ce = chan_extra_map_get(chan_extra_map, scid12); + struct chan_extra *ce = uncertainty_find_channel(uncertainty, scid12); assert(short_channel_id_eq(ce->scid, scid12)); /* l1->l2 dir is 1 */ assert(ce->half[1].num_htlcs == 1); @@ -413,7 +424,7 @@ int main(int argc, char *argv[]) assert(amount_msat_eq(ce->half[0].known_min, AMOUNT_MSAT(0))); assert(amount_msat_eq(ce->half[0].known_max, AMOUNT_MSAT(1000000000))); - ce = chan_extra_map_get(chan_extra_map, scid23); + ce = uncertainty_find_channel(uncertainty, scid23); assert(short_channel_id_eq(ce->scid, scid23)); /* l2->l3 dir is 0 */ assert(ce->half[0].num_htlcs == 1); @@ -426,9 +437,8 @@ int main(int argc, char *argv[]) assert(amount_msat_eq(ce->half[1].known_max, AMOUNT_MSAT(1000000000))); /* Clear that */ - if(!remove_completed_flowset(tmpctx, gossmap, chan_extra_map, flows,NULL)) - { - assert(0 && "remove_completed_flowset failed"); + for (size_t i = 0; i < tal_count(routes); i++) { + uncertainty_remove_htlcs(uncertainty, routes[i]); } // /* Now try adding a local channel scid */ @@ -452,8 +462,7 @@ int main(int argc, char *argv[]) assert(local_chan); /* The local chans have no "capacity", so set it manually. */ - new_chan_extra(chan_extra_map, scid13, - AMOUNT_MSAT(400000000)); + uncertainty_add_channel(uncertainty, scid13, AMOUNT_MSAT(400000000)); // flows = minflow(tmpctx, gossmap, // gossmap_find_node(gossmap, &l1), @@ -518,7 +527,7 @@ int main(int argc, char *argv[]) struct flow **flows2 = minflow(tmpctx, gossmap, gossmap_find_node(gossmap, &l1), gossmap_find_node(gossmap, &l3), - chan_extra_map, NULL, + uncertainty_get_chan_extra_map(uncertainty), NULL, /* This will go 400000000 via 1->3, rest via 1-2-3. */ /* amount = */ AMOUNT_MSAT(500000000), //500k sats /* max_fee = */ AMOUNT_MSAT(1000000), // 1k sats @@ -553,16 +562,12 @@ int main(int argc, char *argv[]) assert(tal_count(flows2[ID2]->path) == 2); // /* Sends more via 1->3, since it's more expensive (but lower prob) */ - assert(amount_msat_greater(flows2[ID1]->amounts[0], flows2[ID2]->amounts[0])); + assert(amount_msat_greater(flows2[ID1]->amount, flows2[ID2]->amount)); assert(flows2[ID1]->success_prob < flows2[ID2]->success_prob); /* Delivered amount must be the total! */ - assert(flows2[ID1]->amounts[0].millisatoshis - + flows2[ID2]->amounts[1].millisatoshis == 500000000); - - // /* But in total it's more expensive! */ - assert(flows2[ID1]->amounts[0].millisatoshis + flows2[ID2]->amounts[0].millisatoshis - > flows2[ID1]->amounts[0].millisatoshis - flows2[ID2]->amounts[0].millisatoshis); + assert(flows2[ID1]->amount.millisatoshis + + flows2[ID2]->amount.millisatoshis == 500000000); common_shutdown(); } diff --git a/plugins/renepay/test/run-payflow_map.c b/plugins/renepay/test/run-payflow_map.c deleted file mode 100644 index ce4868640b1b..000000000000 --- a/plugins/renepay/test/run-payflow_map.c +++ /dev/null @@ -1,108 +0,0 @@ -/* Eduardo: testing payflow_map. - * */ - -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#define RENEPAY_UNITTEST -#include - -/* AUTOGENERATED MOCKS START */ -/* Generated stub for fromwire_blinded_path */ -struct blinded_path *fromwire_blinded_path(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *plen UNNEEDED) -{ fprintf(stderr, "fromwire_blinded_path called!\n"); abort(); } -/* Generated stub for towire_blinded_path */ -void towire_blinded_path(u8 **p UNNEEDED, const struct blinded_path *blinded_path UNNEEDED) -{ fprintf(stderr, "towire_blinded_path called!\n"); abort(); } -/* AUTOGENERATED MOCKS END */ - -static void destroy_payflow( - struct pay_flow *p, - struct payflow_map * map) -{ - printf("calling %s with %s\n", - __PRETTY_FUNCTION__, - fmt_payflow_key(tmpctx,&p->key)); - payflow_map_del(map, p); -} -static struct pay_flow* new_payflow( - const tal_t *ctx, - const struct sha256 * payment_hash, - u64 gid, - u64 pid) -{ - struct pay_flow *p = tal(ctx,struct pay_flow); - - p->payment=NULL; - p->key.payment_hash = *payment_hash; - p->key.groupid = gid; - p->key.partid = pid; - - return p; -} - -static void valgrind_ok1(void) -{ - const char seed[] = "seed"; - struct sha256 hash; - - sha256(&hash,seed,sizeof(seed)); - - tal_t *this_ctx = tal(tmpctx,tal_t); - - struct payflow_map *map - = tal(this_ctx, struct payflow_map); - - payflow_map_init(map); - - { - tal_t *local_ctx = tal(this_ctx,tal_t); - struct payflow_key key; - - struct pay_flow *p1 = new_payflow(local_ctx, - &hash,1,1); - struct pay_flow *p2 = new_payflow(local_ctx, - &hash,2,3); - - printf("key1 = %s\n",fmt_payflow_key(local_ctx,&p1->key)); - printf("key1 = %s\n",fmt_payflow_key(local_ctx,&p2->key)); - printf("key hash 1 = %zu\n",payflow_key_hash(&p1->key)); - printf("key hash 2 = %zu\n",payflow_key_hash(&p2->key)); - - payflow_map_add(map,p1); tal_add_destructor2(p1,destroy_payflow,map); - payflow_map_add(map,p2); tal_add_destructor2(p2,destroy_payflow,map); - - key = payflow_key(&hash,1,1); - struct pay_flow *q1 = payflow_map_get(map, &key); - key = payflow_key(&hash,2,3); - struct pay_flow *q2 = payflow_map_get(map, &key); - - assert(payflow_key_hash(&q1->key)==payflow_key_hash(&p1->key)); - assert(payflow_key_hash(&q2->key)==payflow_key_hash(&p2->key)); - - tal_free(local_ctx); - } - - tal_free(this_ctx); - -} -int main(int argc, char *argv[]) -{ - common_setup(argv[0]); - valgrind_ok1(); - common_shutdown(); -} - diff --git a/plugins/renepay/test/run-route_map.c b/plugins/renepay/test/run-route_map.c new file mode 100644 index 000000000000..2c53f0601ed2 --- /dev/null +++ b/plugins/renepay/test/run-route_map.c @@ -0,0 +1,84 @@ +/* Eduardo: testing route_map. + * */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define RENEPAY_UNITTEST +#include "../flow.c" +#include "../route.c" + +static void destroy_route( + struct route *route, + struct route_map * map) +{ + printf("calling %s with %s\n", + __PRETTY_FUNCTION__, + fmt_routekey(tmpctx,&route->key)); + route_map_del(map, route); +} + +static void valgrind_ok1(void) +{ + const char seed[] = "seed"; + struct sha256 hash; + + sha256(&hash,seed,sizeof(seed)); + + tal_t *this_ctx = tal(tmpctx,tal_t); + + struct route_map *map + = tal(this_ctx, struct route_map); + + route_map_init(map); + + { + tal_t *local_ctx = tal(this_ctx,tal_t); + struct routekey key; + + struct route *r1 = new_route(local_ctx, NULL, 1, 1, hash, + AMOUNT_MSAT(0), AMOUNT_MSAT(0)); + struct route *r2 = new_route(local_ctx, NULL, 2, 3, hash, + AMOUNT_MSAT(0), AMOUNT_MSAT(0)); + + printf("key1 = %s\n", fmt_routekey(local_ctx,&r1->key)); + printf("key1 = %s\n", fmt_routekey(local_ctx,&r2->key)); + printf("key hash 1 = %zu\n", routekey_hash(&r1->key)); + printf("key hash 2 = %zu\n", routekey_hash(&r2->key)); + + route_map_add(map,r1); tal_add_destructor2(r1, destroy_route, map); + route_map_add(map,r2); tal_add_destructor2(r2, destroy_route, map); + + key = routekey(&hash,1,1); + struct route *q1 = route_map_get(map, &key); + key = routekey(&hash,2,3); + struct route *q2 = route_map_get(map, &key); + + assert(routekey_hash(&q1->key)==routekey_hash(&r1->key)); + assert(routekey_hash(&q2->key)==routekey_hash(&r2->key)); + + tal_free(local_ctx); + } + + tal_free(this_ctx); + +} +int main(int argc, char *argv[]) +{ + common_setup(argv[0]); + valgrind_ok1(); + common_shutdown(); +} + diff --git a/plugins/renepay/test/run-testflow.c b/plugins/renepay/test/run-testflow.c index f94aee8a6397..7f2e0666bc03 100644 --- a/plugins/renepay/test/run-testflow.c +++ b/plugins/renepay/test/run-testflow.c @@ -14,23 +14,28 @@ #define RENEPAY_UNITTEST // logs are written in MYLOG #include "../payment.c" #include "../flow.c" -#include "../uncertainty_network.c" +#include "../route.c" +#include "../uncertainty.c" #include "../mcf.c" /* AUTOGENERATED MOCKS START */ /* Generated stub for fromwire_blinded_path */ struct blinded_path *fromwire_blinded_path(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *plen UNNEEDED) { fprintf(stderr, "fromwire_blinded_path called!\n"); abort(); } +/* Generated stub for json_add_payment */ +void json_add_payment(struct json_stream *s UNNEEDED, const struct payment *payment UNNEEDED) +{ fprintf(stderr, "json_add_payment called!\n"); abort(); } +/* Generated stub for new_routetracker */ +struct routetracker *new_routetracker(const tal_t *ctx UNNEEDED) +{ fprintf(stderr, "new_routetracker called!\n"); abort(); } /* Generated stub for pay_plugin */ struct pay_plugin *pay_plugin; +/* Generated stub for routetracker_cleanup */ +void routetracker_cleanup(struct routetracker *routetracker UNNEEDED) +{ fprintf(stderr, "routetracker_cleanup called!\n"); abort(); } /* Generated stub for towire_blinded_path */ void towire_blinded_path(u8 **p UNNEEDED, const struct blinded_path *blinded_path UNNEEDED) { fprintf(stderr, "towire_blinded_path called!\n"); abort(); } -/* Generated stub for try_paying */ -const char *try_paying(const tal_t *ctx UNNEEDED, - struct payment *payment UNNEEDED, - enum jsonrpc_errcode *ecode UNNEEDED) -{ fprintf(stderr, "try_paying called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ static const u8 canned_map[] = { @@ -401,40 +406,40 @@ static void test_edge_probability(void) { f.millisatoshis = i; // prob = 1 - assert(fabs(edge_probability(tmpctx,min,max,X,f, NULL)-1.0)< eps); + assert(fabs(edge_probability(min,max,X,f)-1.0)< eps); } for(int i=max.millisatoshis+1;i<=100;++i) { f.millisatoshis = i; // prob = 0 - assert(fabs(edge_probability(tmpctx,min,max,X,f, NULL))< eps); + assert(fabs(edge_probability(min,max,X,f))< eps); } f.millisatoshis=11; - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-0.9)< eps); + assert(fabs(edge_probability(min,max,X,f)-0.9)< eps); f.millisatoshis=12; - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-0.8)< eps); + assert(fabs(edge_probability(min,max,X,f)-0.8)< eps); f.millisatoshis=13; - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-0.7)< eps); + assert(fabs(edge_probability(min,max,X,f)-0.7)< eps); f.millisatoshis=14; - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-0.6)< eps); + assert(fabs(edge_probability(min,max,X,f)-0.6)< eps); f.millisatoshis=15; - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-0.5)< eps); + assert(fabs(edge_probability(min,max,X,f)-0.5)< eps); f.millisatoshis=16; - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-0.4)< eps); + assert(fabs(edge_probability(min,max,X,f)-0.4)< eps); f.millisatoshis=17; - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-0.3)< eps); + assert(fabs(edge_probability(min,max,X,f)-0.3)< eps); f.millisatoshis=18; - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-0.2)< eps); + assert(fabs(edge_probability(min,max,X,f)-0.2)< eps); f.millisatoshis=19; - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-0.1)< eps); + assert(fabs(edge_probability(min,max,X,f)-0.1)< eps); X = AMOUNT_MSAT(5); @@ -443,34 +448,34 @@ static void test_edge_probability(void) { f.millisatoshis = i; // prob = 1 - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-1.0)< eps); + assert(fabs(edge_probability(min,max,X,f)-1.0)< eps); } // X=B-X for(int i=15;i<100;++i) { f.millisatoshis = i; // prob = 0 - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL))< eps); + assert(fabs(edge_probability(min,max,X,f))< eps); } // X=A, 0<=f<=B-X f.millisatoshis=0; - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-1.0)< eps); + assert(fabs(edge_probability(min,max,X,f)-1.0)< eps); f.millisatoshis=1; - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-0.8)< eps); + assert(fabs(edge_probability(min,max,X,f)-0.8)< eps); f.millisatoshis=2; - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-0.6)< eps); + assert(fabs(edge_probability(min,max,X,f)-0.6)< eps); f.millisatoshis=3; - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-0.4)< eps); + assert(fabs(edge_probability(min,max,X,f)-0.4)< eps); f.millisatoshis=4; - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-0.2)< eps); + assert(fabs(edge_probability(min,max,X,f)-0.2)< eps); f.millisatoshis=5; - assert(fabs(edge_probability(tmpctx,min,max,X,f,NULL)-0.0)< eps); + assert(fabs(edge_probability(min,max,X,f)-0.0)< eps); } static void remove_file(char *fname) @@ -501,7 +506,7 @@ static void remove_file(char *fname) assert(!remove(fname)); } -static void test_flow_complete(void) +static void test_flow_to_route(void) { const double eps = 1e-8; @@ -651,22 +656,27 @@ static void test_flow_complete(void) assert(amount_msat_eq_sat(sum_min1_max0,cap)); assert(amount_msat_eq_sat(sum_min0_max1,cap)); - struct flow *F = tal(this_ctx,struct flow); + struct flow *F; + struct route *route; + struct sha256 payment_hash; struct amount_msat deliver; // flow 1->2 + F = tal(this_ctx, struct flow); F->path = tal_arr(F,const struct gossmap_chan *,1); F->dirs = tal_arr(F,int,1); F->path[0]=gossmap_find_chan(gossmap,&scid12); F->dirs[0]=0; deliver = AMOUNT_MSAT(250000000); - if (!flow_complete(tmpctx, F, gossmap, chan_extra_map, deliver, NULL)) { - assert(0 && "flow_complete fail"); - } - assert(amount_msat_eq(F->amounts[0],deliver)); - assert(fabs(F->success_prob - 0.5)amount = deliver; + route = flow_to_route(this_ctx, NULL, 1, 1, payment_hash, 0, gossmap, F); + assert(route); + + assert(amount_msat_eq(route->hops[0].amount, deliver)); + assert(fabs(flow_probability(F, gossmap, chan_extra_map) - 0.5)4->5 + F = tal(this_ctx, struct flow); F->path = tal_arr(F,const struct gossmap_chan *,2); F->dirs = tal_arr(F,int,2); F->path[0]=gossmap_find_chan(gossmap,&scid34); @@ -674,13 +684,15 @@ static void test_flow_complete(void) F->dirs[0]=0; F->dirs[1]=0; deliver = AMOUNT_MSAT(250000000); - if (!flow_complete(tmpctx, F, gossmap, chan_extra_map, deliver, NULL)) { - assert(0 && "flow_complete fail"); - } - assert(amount_msat_eq(F->amounts[0],amount_msat(250050016))); - assert(fabs(F->success_prob - 1.)amount=deliver; + route = flow_to_route(this_ctx, NULL, 1, 1, payment_hash, 0, gossmap, F); + assert(route); + + assert(amount_msat_eq(route->hops[0].amount, amount_msat(250050016))); + assert(fabs(flow_probability(F, gossmap, chan_extra_map) - 1.)3->4->5 + F = tal(this_ctx, struct flow); F->path = tal_arr(F,const struct gossmap_chan *,3); F->dirs = tal_arr(F,int,3); F->path[0]=gossmap_find_chan(gossmap,&scid23); @@ -690,13 +702,15 @@ static void test_flow_complete(void) F->dirs[1]=0; F->dirs[2]=0; deliver = AMOUNT_MSAT(250000000); - if (!flow_complete(tmpctx, F, gossmap, chan_extra_map, deliver, NULL)) { - assert(0 && "flow_complete fail"); - } - assert(amount_msat_eq(F->amounts[0],amount_msat(250087534))); - assert(fabs(F->success_prob - 1. + 250.087534/2000)amount=deliver; + route = flow_to_route(this_ctx, NULL, 1, 1, payment_hash, 0, gossmap, F); + assert(route); + + assert(amount_msat_eq(route->hops[0].amount, amount_msat(250087534))); + assert(fabs(flow_probability(F, gossmap, chan_extra_map) - 1. + 250.087534/2000)2->3->4->5 + F = tal(this_ctx, struct flow); F->path = tal_arr(F,const struct gossmap_chan *,4); F->dirs = tal_arr(F,int,4); F->path[0]=gossmap_find_chan(gossmap,&scid12); @@ -708,11 +722,12 @@ static void test_flow_complete(void) F->dirs[2]=0; F->dirs[3]=0; deliver = AMOUNT_MSAT(250000000); - if (!flow_complete(tmpctx, F, gossmap, chan_extra_map, deliver, NULL)) { - assert(0 && "flow_complete fail"); - } - assert(amount_msat_eq(F->amounts[0],amount_msat(250112544))); - assert(fabs(F->success_prob - 0.43728117)amount=deliver; + route = flow_to_route(this_ctx, NULL, 1, 1, payment_hash, 0, gossmap, F); + assert(route); + + assert(amount_msat_eq(route->hops[0].amount, amount_msat(250112544))); + assert(fabs(flow_probability(F, gossmap, chan_extra_map) - 0.43728117)half[0].proportional_fee = ppm; struct amount_msat out; - assert(channel_maximum_forward(&out, c, 0, in)); + assert(channel_maximum_forward( + &out, c, 0, in) == RENEPAY_NOERROR); // do we satisfy the fee constraint? assert(check_fee_inequality(in, out, basefee, @@ -796,7 +812,7 @@ int main(int argc, char *argv[]) common_setup(argv[0]); test_edge_probability(); - test_flow_complete(); + test_flow_to_route(); test_channel_maximum_forward(); common_shutdown(); From 408ba779abeb9202778791053e07488accc4a852 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 16:06:48 +0100 Subject: [PATCH 17/31] renepay: minor fixes --- plugins/renepay/chan_extra.c | 3 +- plugins/renepay/errorcodes.c | 1 + plugins/renepay/errorcodes.h | 2 ++ plugins/renepay/json.c | 1 + plugins/renepay/json.h | 1 + plugins/renepay/mods.c | 5 ++- plugins/renepay/payment.c | 2 ++ plugins/renepay/route.c | 1 - plugins/renepay/routebuilder.c | 1 + plugins/renepay/routefail.c | 55 +++++++++++++++++++++-------- plugins/renepay/routetracker.c | 24 +++++++------ plugins/renepay/routetracker.h | 5 +-- plugins/renepay/test/run-mcf.c | 7 +++- plugins/renepay/test/run-testflow.c | 14 +++++--- plugins/renepay/uncertainty.c | 2 +- plugins/renepay/uncertainty.h | 9 ++--- 16 files changed, 89 insertions(+), 44 deletions(-) diff --git a/plugins/renepay/chan_extra.c b/plugins/renepay/chan_extra.c index c5120431f2f2..1caa17a249ef 100644 --- a/plugins/renepay/chan_extra.c +++ b/plugins/renepay/chan_extra.c @@ -490,8 +490,7 @@ enum renepay_errorcode chan_extra_relax_fraction(struct chan_extra *ce, fraction = fabs(fraction); // this number is always non-negative fraction = MIN(1.0, fraction); // this number cannot be greater than 1. struct amount_msat delta = - amount_msat(ce->capacity.millisatoshis * - fraction); /* Raw: get a fraction of the capacity */ + amount_msat(ce->capacity.millisatoshis*fraction); /* Raw: get a fraction of the capacity */ /* The direction here is not important because the 'down' and the 'up' * limits are changed by the same amount. diff --git a/plugins/renepay/errorcodes.c b/plugins/renepay/errorcodes.c index a783c5216771..fcf21b15f898 100644 --- a/plugins/renepay/errorcodes.c +++ b/plugins/renepay/errorcodes.c @@ -1,3 +1,4 @@ +#include "config.h" #include #include #include diff --git a/plugins/renepay/errorcodes.h b/plugins/renepay/errorcodes.h index 2efdfa7caf2e..6b9c09db7752 100644 --- a/plugins/renepay/errorcodes.h +++ b/plugins/renepay/errorcodes.h @@ -1,6 +1,8 @@ #ifndef LIGHTNING_PLUGINS_RENEPAY_ERRORCODES_H #define LIGHTNING_PLUGINS_RENEPAY_ERRORCODES_H +#include "config.h" + /* Common types of failures for low level functions in renepay. */ enum renepay_errorcode { RENEPAY_NOERROR = 0, diff --git a/plugins/renepay/json.c b/plugins/renepay/json.c index eaa200920a2c..065c66723d54 100644 --- a/plugins/renepay/json.c +++ b/plugins/renepay/json.c @@ -1,3 +1,4 @@ +#include "config.h" #include #include diff --git a/plugins/renepay/json.h b/plugins/renepay/json.h index ef1a483db82d..3658a370878f 100644 --- a/plugins/renepay/json.h +++ b/plugins/renepay/json.h @@ -1,6 +1,7 @@ #ifndef LIGHTNING_PLUGINS_RENEPAY_JSON_H #define LIGHTNING_PLUGINS_RENEPAY_JSON_H +#include "config.h" #include #include diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c index 890849f6fbfa..b443092eb838 100644 --- a/plugins/renepay/mods.c +++ b/plugins/renepay/mods.c @@ -271,7 +271,7 @@ static struct command_result *previous_sendpays_done(struct command *cmd, } for (size_t j = 0; j < tal_count(pending_routes); j++) { - route_pending(pending_routes[j]); + route_pending_register(pending_routes[j]); } } else { @@ -453,14 +453,13 @@ static void gossmod_cb(struct gossmap_localmods *mods, u32 fee_proportional, u32 cltv_delta, bool enabled, - bool is_local, const char *buf, const jsmntok_t *chantok, struct payment *payment) { struct amount_msat min, max; - if (is_local) { + if (scidd->dir == node_id_idx(self, peer)) { /* local channels can send up to what's spendable */ min = AMOUNT_MSAT(0); max = spendable; diff --git a/plugins/renepay/payment.c b/plugins/renepay/payment.c index 7c7f33044511..2c1f17e1fdd9 100644 --- a/plugins/renepay/payment.c +++ b/plugins/renepay/payment.c @@ -347,6 +347,8 @@ static struct command_result *payment_finish(struct payment *p) return my_command_finish(p, cmd); } +/* FIXME: disabled_scids should be a set rather than an array, so that we don't + * have to worry about disabling the same channel multiple times. */ void payment_disable_chan(struct payment *p, struct short_channel_id scid, enum log_level lvl, const char *fmt, ...) { diff --git a/plugins/renepay/route.c b/plugins/renepay/route.c index 6e8498139d3d..986a5ef5d1f9 100644 --- a/plugins/renepay/route.c +++ b/plugins/renepay/route.c @@ -1,5 +1,4 @@ #include "config.h" -#include #include struct route *new_route(const tal_t *ctx, struct payment *payment, u32 groupid, diff --git a/plugins/renepay/routebuilder.c b/plugins/renepay/routebuilder.c index d7b1e74e2602..743e4c39eb8c 100644 --- a/plugins/renepay/routebuilder.c +++ b/plugins/renepay/routebuilder.c @@ -1,3 +1,4 @@ +#include "config.h" #include #include #include diff --git a/plugins/renepay/routefail.c b/plugins/renepay/routefail.c index 880f9d1171c9..70a0137c4739 100644 --- a/plugins/renepay/routefail.c +++ b/plugins/renepay/routefail.c @@ -75,7 +75,7 @@ static struct command_result *routefail_rpc_failure(struct command *cmd, "routefail state machine has stopped due to a failed RPC " "call: %.*s", json_tok_full_len(toks), json_tok_full(buffer, toks)); - return command_still_pending(r->cmd); + return notification_handled(r->cmd); } /***************************************************************************** @@ -88,7 +88,9 @@ static struct command_result *end_done(struct command *cmd, const jsmntok_t *result UNUSED, struct routefail *r) { - route_is_failure(r->route); + /* Notify the tracker that route has failed and routefail have completed + * handling all possible errors cases. */ + route_failure_register(r->route); tal_free(r); return notification_handled(cmd); } @@ -176,8 +178,6 @@ static struct command_result *update_gossip_failure(struct command *cmd UNUSED, * waitsendpay says it is present in the case of error. */ assert(r->route->result->erring_channel); - /* TODO disable chan? what if we endup disabling a channel twice? maybe - * use a map instead of an array. */ payment_disable_chan( r->route->payment, *r->route->result->erring_channel, LOG_INFORM, "addgossip failed (%.*s)", json_tok_full_len(result), @@ -222,10 +222,10 @@ static struct command_result *update_knowledge_cb(struct routefail *r) /* FIXME: If we don't know the hops there isn't much we can infer, but * a little bit we could. */ - if (!route->hops) + if (!route->hops || !result->erring_index) goto skip_update_network; - uncertainty_channel_can_send(pay_plugin->uncertainty, r->route, + uncertainty_channel_can_send(pay_plugin->uncertainty, route, *result->erring_index); if (result->failcode == WIRE_TEMPORARY_CHANNEL_FAILURE && @@ -236,6 +236,8 @@ static struct command_result *update_knowledge_cb(struct routefail *r) route->hops[*result->erring_index].direction); } + uncertainty_remove_htlcs(pay_plugin->uncertainty, route); + skip_update_network: return routefail_continue(r); } @@ -258,11 +260,20 @@ static void route_final_error(struct route *route, enum jsonrpc_errcode error, route->final_msg = tal_strdup(route, what); } -static void handle_unhandleable_error(struct route *route, const char *what) +static void handle_unhandleable_error(struct route *route, const char *fmt, ...) { if (!route->hops) return; size_t n = tal_count(route->hops); + if (n == 0) + return; + + va_list ap; + const char *what; + + va_start(ap, fmt); + what = tal_vfmt(tmpctx, fmt, ap); + va_end(ap); if (n == 1) { /* This is a terminal error. */ @@ -332,9 +343,17 @@ static struct command_result *handle_cases_cb(struct routefail *r) case WIRE_INVALID_ONION_PAYLOAD: case WIRE_INVALID_ONION_BLINDING: case WIRE_EXPIRY_TOO_FAR: - payment_disable_chan(route->payment, *result->erring_channel, - LOG_UNUSUAL, "%s", - onion_wire_name(result->failcode)); + if (result->erring_channel) + payment_disable_chan(route->payment, + *result->erring_channel, + LOG_UNUSUAL, "%s", + onion_wire_name(result->failcode)); + else + handle_unhandleable_error( + route, + "received %s error, but don't have an " + "erring_channel", + onion_wire_name(result->failcode)); break; /* These can be fixed (maybe) by applying the included channel_update */ @@ -353,11 +372,17 @@ static struct command_result *handle_cases_cb(struct routefail *r) break; default: - /* FIXME: remember you might be disabling the same channel - * multiple times */ - payment_disable_chan(route->payment, *result->erring_channel, - LOG_UNUSUAL, "Unexpected error code %u", - result->failcode); + if (result->erring_channel) + payment_disable_chan( + route->payment, *result->erring_channel, + LOG_UNUSUAL, "Unexpected error code %u", + result->failcode); + else + handle_unhandleable_error( + route, + "received %s error, but don't have an " + "erring_channel", + onion_wire_name(result->failcode)); } finish: diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c index 0e5e32330cdf..5bde6287a1d2 100644 --- a/plugins/renepay/routetracker.c +++ b/plugins/renepay/routetracker.c @@ -1,3 +1,4 @@ +#include "config.h" #include #include #include @@ -40,15 +41,15 @@ static void routetracker_add_to_final(struct routetracker *routetracker, tal_arr_expand(&routetracker->finalized_routes, route); tal_steal(routetracker, route); } -static void route_is_success(struct route *route) +static void route_success_register(struct route *route) { routetracker_add_to_final(route->payment->routetracker, route); } -void route_is_failure(struct route *route) +void route_failure_register(struct route *route) { routetracker_add_to_final(route->payment->routetracker, route); } -static void route_sent(struct route *route) +static void route_sent_register(struct route *route) { struct routetracker *routetracker = route->payment->routetracker; route_map_add(routetracker->sent_routes, route); @@ -71,7 +72,7 @@ static void route_sendpay_fail(struct route *route TAKES) * - after a sendpay is accepted, * - or after listsendpays reveals some pending route that we didn't * previously know about. */ -void route_pending(const struct route *route) +void route_pending_register(const struct route *route) { assert(route); struct payment *payment = route->payment; @@ -92,8 +93,11 @@ void route_pending(const struct route *route) fmt_routekey(tmpctx, &route->key)); uncertainty_commit_htlcs(pay_plugin->uncertainty, route); - route_map_add(routetracker->pending_routes, route); - tal_steal(routetracker, route); + + if (!route_map_add(routetracker->pending_routes, route) || + !tal_steal(routetracker, route)) + plugin_err(pay_plugin->plugin, "%s: failed to register route.", + __PRETTY_FUNCTION__); if (!amount_msat_add(&payment->total_sent, payment->total_sent, route_sends(route)) || @@ -110,8 +114,6 @@ static void route_result_collected(struct route *route TAKES) { assert(route); assert(route->result); - // TODO: also improve knowledge here? - uncertainty_remove_htlcs(pay_plugin->uncertainty, route); assert(route->payment); struct payment *payment = route->payment; @@ -140,7 +142,7 @@ static struct command_result *sendpay_done(struct command *cmd, struct route *route) { assert(route); - route_pending(route); + route_pending_register(route); return command_still_pending(cmd); } @@ -247,7 +249,7 @@ struct command_result *route_sendpay_request(struct command *cmd, json_add_route(req->js, route); - route_sent(route); + route_sent_register(route); return send_outreq(pay_plugin->plugin, req); } @@ -352,6 +354,6 @@ struct command_result *notification_sendpay_success(struct command *cmd, // FIXME: what happens when several success notification arrive for the // same payment? Even after the payment has been resolved. - route_is_success(route); + route_success_register(route); return notification_handled(cmd); } diff --git a/plugins/renepay/routetracker.h b/plugins/renepay/routetracker.h index 9cc3de42e422..840ce75bb745 100644 --- a/plugins/renepay/routetracker.h +++ b/plugins/renepay/routetracker.h @@ -29,7 +29,7 @@ void payment_collect_results(struct payment *payment, /* Announce that this route is pending and needs to be kept in the waiting list * for notifications. */ -void route_pending(const struct route *route); +void route_pending_register(const struct route *route); /* Sends a sendpay request for this route. */ struct command_result *route_sendpay_request(struct command *cmd, @@ -43,7 +43,8 @@ struct command_result *notification_sendpay_success(struct command *cmd, const char *buf, const jsmntok_t *params); -void route_is_failure(struct route *route); +/* Notify the tracker that this route has failed. */ +void route_failure_register(struct route *route); // FIXME: double-check that we actually get one notification for each sendpay, // ie. that after some time we don't have yet pending sendpays for old failed or diff --git a/plugins/renepay/test/run-mcf.c b/plugins/renepay/test/run-mcf.c index f22f763caeb3..65e442f2660c 100644 --- a/plugins/renepay/test/run-mcf.c +++ b/plugins/renepay/test/run-mcf.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -336,9 +337,13 @@ int main(int argc, char *argv[]) struct short_channel_id scid12, scid23; struct sha256 payment_hash; struct amount_msat *amounts; - char *errmsg; + if (!hex_decode("0001020304050607080900010203040506070809000102030405060708090102", + strlen("0001020304050607080900010203040506070809000102030405060708090102"), + &payment_hash, sizeof(payment_hash))) + abort(); + common_setup(argv[0]); fd = tmpdir_mkstemp(tmpctx, "run-not_mcf.XXXXXX", &gossfile); diff --git a/plugins/renepay/test/run-testflow.c b/plugins/renepay/test/run-testflow.c index 7f2e0666bc03..de075d66cb02 100644 --- a/plugins/renepay/test/run-testflow.c +++ b/plugins/renepay/test/run-testflow.c @@ -1,13 +1,14 @@ #include "config.h" -#include #include -#include +#include +#include #include #include +#include #include #include -#include -#include +#include +#include #include #define MYLOG "/tmp/debug.txt" @@ -661,6 +662,11 @@ static void test_flow_to_route(void) struct sha256 payment_hash; struct amount_msat deliver; + if (!hex_decode("0001020304050607080900010203040506070809000102030405060708090102", + strlen("0001020304050607080900010203040506070809000102030405060708090102"), + &payment_hash, sizeof(payment_hash))) + abort(); + // flow 1->2 F = tal(this_ctx, struct flow); F->path = tal_arr(F,const struct gossmap_chan *,1); diff --git a/plugins/renepay/uncertainty.c b/plugins/renepay/uncertainty.c index 9d4bdd84e1f6..6046c5f0f45a 100644 --- a/plugins/renepay/uncertainty.c +++ b/plugins/renepay/uncertainty.c @@ -55,7 +55,7 @@ void uncertainty_commit_htlcs(struct uncertainty *uncertainty, } void uncertainty_channel_can_send(struct uncertainty *uncertainty, - struct route *route, u32 erridx) + const struct route *route, u32 erridx) { if (!route->hops) return; diff --git a/plugins/renepay/uncertainty.h b/plugins/renepay/uncertainty.h index c0807caf6dfd..dabdc0b66bb6 100644 --- a/plugins/renepay/uncertainty.h +++ b/plugins/renepay/uncertainty.h @@ -1,5 +1,5 @@ -#ifndef LIGHTNING_PLUGINS_RENEPAY_UNETWORK_H -#define LIGHTNING_PLUGINS_RENEPAY_UNETWORK_H +#ifndef LIGHTNING_PLUGINS_RENEPAY_UNCERTAINTY_H +#define LIGHTNING_PLUGINS_RENEPAY_UNCERTAINTY_H #include "config.h" #include #include @@ -19,6 +19,7 @@ struct uncertainty { struct chan_extra_map *chan_extra_map; }; +/* FIXME: add bool return value and WARN_UNUSED_RESULT */ void uncertainty_route_success(struct uncertainty *uncertainty, const struct route *route); void uncertainty_remove_htlcs(struct uncertainty *uncertainty, @@ -28,7 +29,7 @@ void uncertainty_commit_htlcs(struct uncertainty *uncertainty, const struct route *route); void uncertainty_channel_can_send(struct uncertainty *uncertainty, - struct route *route, u32 erridx); + const struct route *route, u32 erridx); void uncertainty_channel_cannot_send(struct uncertainty *uncertainty, struct short_channel_id scid, @@ -54,4 +55,4 @@ bool uncertainty_set_liquidity(struct uncertainty *uncertainty, struct chan_extra *uncertainty_find_channel(struct uncertainty *uncertainty, const struct short_channel_id scid); -#endif /* LIGHTNING_PLUGINS_RENEPAY_UNETWORK_H */ +#endif /* LIGHTNING_PLUGINS_RENEPAY_UNCERTAINTY_H */ From fc84adb95ae0106105a2357c732994864f5b15de Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Wed, 10 Apr 2024 11:19:42 +0100 Subject: [PATCH 18/31] renepay: pay selfpayments on spot Resolve a selfpayment right from the result of `sendpay` instead of waiting for the notification. --- plugins/renepay/mods.c | 49 ++++++++++++++-------------------- plugins/renepay/routetracker.c | 16 ++++++++--- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c index b443092eb838..d8c6bd03aa6d 100644 --- a/plugins/renepay/mods.c +++ b/plugins/renepay/mods.c @@ -327,44 +327,38 @@ REGISTER_PAYMENT_MODIFIER(initial_sanity_checks, initial_sanity_checks_cb); static struct command_result *selfpay_success(struct command *cmd, const char *buf, - const jsmntok_t *result, - struct payment *payment) + const jsmntok_t *tok, + struct route *route) { + struct payment *payment = route->payment; + assert(payment); struct preimage preimage; const char *err; - err = json_scan(tmpctx, buf, result, "{payment_preimage:%}", + err = json_scan(tmpctx, buf, tok, "{payment_preimage:%}", JSON_SCAN(json_to_preimage, &preimage)); if (err) plugin_err( cmd->plugin, "selfpay didn't have payment_preimage: %.*s", - json_tok_full_len(result), json_tok_full(buf, result)); + json_tok_full_len(tok), json_tok_full(buf, tok)); + payment_note(payment, LOG_DBG, "Paid with self-pay."); - /* FIXME: shouldn't we process selfpay in the same way a regular payment? */ return payment_success(payment, &preimage); } - -static void route_selfpaypending(const struct route *route) +static struct command_result *selfpay_failure(struct command *cmd, + const char *buf, + const jsmntok_t *tok, + struct route *route) { - assert(route); struct payment *payment = route->payment; assert(payment); - struct routetracker *routetracker = payment->routetracker; - assert(routetracker); - - /* we already keep track of this route */ - assert(!route_map_get(routetracker->pending_routes, &route->key)); - route_map_add(routetracker->pending_routes, route); - tal_steal(routetracker, route); - - if (!amount_msat_add(&payment->total_sent, payment->total_sent, - payment->amount) || - !amount_msat_add(&payment->total_delivering, - payment->total_delivering, payment->amount)) { + struct payment_result *result = tal_sendpay_result_from_json(tmpctx, buf, tok); + if (result == NULL) plugin_err(pay_plugin->plugin, - "%s: amount_msat arithmetic overflow.", - __PRETTY_FUNCTION__); - } + "Unable to parse sendpay failure: %.*s", + json_tok_full_len(tok), json_tok_full(buf, tok)); + + return payment_fail(payment, result->code, "%s", result->message); } static struct command_result *selfpay_cb(struct payment *payment) @@ -377,18 +371,15 @@ static struct command_result *selfpay_cb(struct payment *payment) if (!cmd) plugin_err(pay_plugin->plugin, "Selfpay: cannot get a valid cmd."); - struct out_req *req; - req = - jsonrpc_request_start(cmd->plugin, cmd, "sendpay", selfpay_success, - payment_rpc_failure, payment); struct route *route = new_route(payment, payment, payment->groupid, /*partid=*/0, payment->payment_hash, payment->amount, payment->amount); + struct out_req *req; + req = jsonrpc_request_start(cmd->plugin, cmd, "sendpay", + selfpay_success, selfpay_failure, route); route->hops = tal_arr(route, struct route_hop, 0); json_add_route(req->js, route); - - route_selfpaypending(route); return send_outreq(cmd->plugin, req); } diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c index 5bde6287a1d2..177cfed19c88 100644 --- a/plugins/renepay/routetracker.c +++ b/plugins/renepay/routetracker.c @@ -283,10 +283,14 @@ struct command_result *notification_sendpay_failure(struct command *cmd, assert(payment->routetracker); struct route *route = route_map_get(payment->routetracker->pending_routes, key); - if (!route) - plugin_err(pay_plugin->plugin, + if (!route) { + /* This can happen if payment is first tried with renepay and + * then retried using another payment plugin. */ + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, "%s: key %s is not found in pending_routes", __PRETTY_FUNCTION__, fmt_routekey(tmpctx, key)); + return notification_handled(cmd); + } assert(route->result == NULL); route->result = tal_sendpay_result_from_json(route, buf, sub); @@ -338,10 +342,14 @@ struct command_result *notification_sendpay_success(struct command *cmd, assert(payment->routetracker); struct route *route = route_map_get(payment->routetracker->pending_routes, key); - if (!route) - plugin_err(pay_plugin->plugin, + if (!route) { + /* This can happen if payment is first tried with renepay and + * then retried using another payment plugin. */ + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, "%s: key %s is not found in pending_routes", __PRETTY_FUNCTION__, fmt_routekey(tmpctx, key)); + return notification_handled(cmd); + } assert(route->result == NULL); route->result = tal_sendpay_result_from_json(route, buf, sub); From 69c3a1a3ae2ce36476ed36cb376a0d008af124d1 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Wed, 10 Apr 2024 20:02:34 +0100 Subject: [PATCH 19/31] renepay: uncertainty network update revisited - Update the uncertainty network with the gossmap+local_gossmods, - ignore channels that fail to give their capacity. --- plugins/renepay/main.c | 8 ++++++- plugins/renepay/mods.c | 36 ++++++++++++++++++++++++++-- plugins/renepay/test/run-mcf.c | 3 ++- plugins/renepay/uncertainty.c | 43 ++++++++++++++++++++++++++-------- plugins/renepay/uncertainty.h | 4 ++-- 5 files changed, 78 insertions(+), 16 deletions(-) diff --git a/plugins/renepay/main.c b/plugins/renepay/main.c index ee009a749e41..bb18dc29f26e 100644 --- a/plugins/renepay/main.c +++ b/plugins/renepay/main.c @@ -76,7 +76,13 @@ static const char *init(struct plugin *p, "gossmap ignored %zu channel updates", num_channel_updates_rejected); pay_plugin->uncertainty = uncertainty_new(pay_plugin); - uncertainty_update(pay_plugin->uncertainty, pay_plugin->gossmap); + int skipped_count = + uncertainty_update(pay_plugin->uncertainty, pay_plugin->gossmap); + if (skipped_count) + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, + "%s: uncertainty was updated but %d channels have " + "been ignored.", + __PRETTY_FUNCTION__, skipped_count); plugin_set_memleak_handler(p, memleak_mark); return NULL; diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c index d8c6bd03aa6d..3d8a4d22379e 100644 --- a/plugins/renepay/mods.c +++ b/plugins/renepay/mods.c @@ -519,6 +519,8 @@ refreshgossmap_done(struct command *cmd UNUSED, const char *buf UNUSED, const jsmntok_t *result UNUSED, struct payment *payment) { assert(pay_plugin->gossmap); // gossmap must be already initialized + assert(payment); + assert(payment->local_gossmods); size_t num_channel_updates_rejected; bool gossmap_changed = @@ -529,8 +531,20 @@ refreshgossmap_done(struct command *cmd UNUSED, const char *buf UNUSED, "gossmap ignored %zu channel updates", num_channel_updates_rejected); - if (gossmap_changed) - uncertainty_update(pay_plugin->uncertainty, pay_plugin->gossmap); + if (gossmap_changed) { + gossmap_apply_localmods(pay_plugin->gossmap, + payment->local_gossmods); + int skipped_count = uncertainty_update(pay_plugin->uncertainty, + pay_plugin->gossmap); + gossmap_remove_localmods(pay_plugin->gossmap, + payment->local_gossmods); + if (skipped_count) + plugin_log( + pay_plugin->plugin, LOG_UNUSUAL, + "%s: uncertainty was updated but %d channels have " + "been ignored.", + __PRETTY_FUNCTION__, skipped_count); + } return payment_continue(payment); } @@ -560,6 +574,9 @@ static void add_hintchan(struct payment *payment, const struct node_id *src, const struct short_channel_id scid, u32 fee_base_msat, u32 fee_proportional_millionths) { + assert(payment); + assert(payment->local_gossmods); + // TODO test this, simply make a payment through a private channel, this // statement is either right or wrong. int dir = node_id_cmp(src, dst) < 0 ? 0 : 1; @@ -623,8 +640,11 @@ static struct command_result *routehints_done(struct command *cmd UNUSED, struct payment *payment) { // FIXME are there route hints for B12? + assert(payment); + assert(payment->local_gossmods); assert(payment->routehints); const size_t nhints = tal_count(payment->routehints); + /* Hints are added to the local_gossmods. */ for (size_t i = 0; i < nhints; i++) { /* Each one, presumably, leads to the destination */ const struct route_info *r = payment->routehints[i]; @@ -638,6 +658,18 @@ static struct command_result *routehints_done(struct command *cmd UNUSED, end = &r[j].pubkey; } } + + /* Add hints to the uncertainty network. */ + gossmap_apply_localmods(pay_plugin->gossmap, payment->local_gossmods); + int skipped_count = + uncertainty_update(pay_plugin->uncertainty, pay_plugin->gossmap); + gossmap_remove_localmods(pay_plugin->gossmap, payment->local_gossmods); + if (skipped_count) + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, + "%s: uncertainty was updated but %d channels have " + "been ignored.", + __PRETTY_FUNCTION__, skipped_count); + return payment_continue(payment); } diff --git a/plugins/renepay/test/run-mcf.c b/plugins/renepay/test/run-mcf.c index 65e442f2660c..1d8e74dd5c0e 100644 --- a/plugins/renepay/test/run-mcf.c +++ b/plugins/renepay/test/run-mcf.c @@ -361,7 +361,8 @@ int main(int argc, char *argv[]) assert(short_channel_id_from_str("103x1x0", 7, &scid23)); struct uncertainty *uncertainty = uncertainty_new(tmpctx); - uncertainty_update(uncertainty, gossmap); + int skipped_count = uncertainty_update(uncertainty, gossmap); + assert(skipped_count == 0); printf("All set, now let's call minflow ...\n"); diff --git a/plugins/renepay/uncertainty.c b/plugins/renepay/uncertainty.c index 6046c5f0f45a..891f9a679570 100644 --- a/plugins/renepay/uncertainty.c +++ b/plugins/renepay/uncertainty.c @@ -78,33 +78,56 @@ void uncertainty_channel_cannot_send(struct uncertainty *uncertainty, chan_extra_cannot_send(uncertainty->chan_extra_map, &scidd); } -void uncertainty_update(struct uncertainty *uncertainty, - struct gossmap *gossmap) +int uncertainty_update(struct uncertainty *uncertainty, struct gossmap *gossmap) { - // FIXME: after running for some time we might find some channels in - // chan_extra_map that are not needed and do not exist in the gossmap - // for being private or closed. + /* Each channel in chan_extra_map should be either in gossmap or in + * local_gossmods. */ + assert(uncertainty); + struct chan_extra_map *chan_extra_map = uncertainty->chan_extra_map; + assert(chan_extra_map); + struct chan_extra **del_list = tal_arr(NULL, struct chan_extra*, 0); + struct chan_extra_map_iter it; + for (struct chan_extra *ch = chan_extra_map_first(chan_extra_map, &it); + ch; ch = chan_extra_map_next(chan_extra_map, &it)) { + + /* If we cannot find that channel in the gossmap, add it to the + * delete list. */ + if (!gossmap_find_chan(gossmap, &ch->scid)) + tal_arr_expand(&del_list, ch); + } + for(size_t i=0;ichan_extra_map, + chan_extra_map_get(chan_extra_map, gossmap_chan_scid(gossmap, chan)); if (!ce) { struct amount_sat cap; struct amount_msat cap_msat; - // FIXME: check errors if (!gossmap_chan_get_capacity(gossmap, chan, &cap) || !amount_sat_to_msat(&cap_msat, cap) || - !new_chan_extra(uncertainty->chan_extra_map, scid, - cap_msat)) - return; + !new_chan_extra(chan_extra_map, scid, + cap_msat)) { + /* If the new chan_extra cannot be created we + * skip this channel. */ + skipped_count++; + continue; + } } } + assert(chan_extra_map_count(chan_extra_map) + skipped_count == + gossmap_num_chans(gossmap)); + return skipped_count; } struct uncertainty *uncertainty_new(const tal_t *ctx) diff --git a/plugins/renepay/uncertainty.h b/plugins/renepay/uncertainty.h index dabdc0b66bb6..2f13c51f1503 100644 --- a/plugins/renepay/uncertainty.h +++ b/plugins/renepay/uncertainty.h @@ -35,8 +35,8 @@ void uncertainty_channel_cannot_send(struct uncertainty *uncertainty, struct short_channel_id scid, int direction); -void uncertainty_update(struct uncertainty *uncertainty, - struct gossmap *gossmap); +WARN_UNUSED_RESULT int uncertainty_update(struct uncertainty *uncertainty, + struct gossmap *gossmap); struct uncertainty *uncertainty_new(const tal_t *ctx); From 83a17bd3b4a4ea173b6e73ffe53d6c5244722b31 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Thu, 11 Apr 2024 07:57:03 +0100 Subject: [PATCH 20/31] renepay: add a test for routehints --- plugins/renepay/mods.c | 4 +--- tests/test_renepay.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c index 3d8a4d22379e..468a1ab16cb1 100644 --- a/plugins/renepay/mods.c +++ b/plugins/renepay/mods.c @@ -577,9 +577,7 @@ static void add_hintchan(struct payment *payment, const struct node_id *src, assert(payment); assert(payment->local_gossmods); - // TODO test this, simply make a payment through a private channel, this - // statement is either right or wrong. - int dir = node_id_cmp(src, dst) < 0 ? 0 : 1; + int dir = node_id_idx(src, dst); const char *errmsg; const struct chan_extra *ce = diff --git a/tests/test_renepay.py b/tests/test_renepay.py index 2aabfec44674..644451a21c03 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -718,3 +718,43 @@ def test_concurrency(node_factory): assert out1["amount_msat"] == Millisatoshi("1000sat") invoice = only_one(l3.rpc.listinvoices("test_renepay")["invoices"]) assert invoice["amount_received_msat"] >= Millisatoshi("1000sat") + + +def test_privatechan(node_factory, bitcoind): + """ + Topology: + 1----2----3----4 + Tests if a payment can get through a private channel. + """ + opts = [ + {"disable-mpp": None, "fee-base": 0, "fee-per-satoshi": 0}, + {"disable-mpp": None, "fee-base": 0, "fee-per-satoshi": 0}, + {"disable-mpp": None, "fee-base": 0, "fee-per-satoshi": 100}, + {"disable-mpp": None, "fee-base": 0, "fee-per-satoshi": 0}, + ] + l1, l2, l3, l4 = node_factory.get_nodes(4, opts=opts) + + l1.rpc.connect(l2.info["id"], "localhost", l2.port) + l2.rpc.connect(l3.info["id"], "localhost", l3.port) + l3.rpc.connect(l4.info["id"], "localhost", l4.port) + + c12, _ = l1.fundchannel(l2, 10**6) + c23, _ = l2.fundchannel(l3, 10**6) + c34, _ = l3.fundchannel(l4, 10**6, announce_channel=False) + + mine_funding_to_announce(bitcoind, [l1, l2, l3, l4]) + l1.wait_channel_active(c12) + l2.wait_channel_active(c23) + l3.wait_local_channel_active(c34) + + wait_for(lambda: len(l1.rpc.listchannels()["channels"]) == 4) + wait_for(lambda: len(l2.rpc.listchannels()["channels"]) == 4) + wait_for(lambda: len(l3.rpc.listchannels()["channels"]) == 4) + wait_for(lambda: len(l4.rpc.listchannels()["channels"]) == 4) + + inv = l4.rpc.invoice("1000sat", "inv", "description") + + l1.rpc.call("renepay", {"invstring": inv["bolt11"]}) + l1.wait_for_htlcs() + invoice = only_one(l4.rpc.listinvoices("inv")["invoices"]) + assert invoice["amount_received_msat"] >= Millisatoshi("1000sat") From 5fb5ff1aad6d865789f59e287b4b4b7e4e18d08d Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Thu, 11 Apr 2024 12:45:51 +0100 Subject: [PATCH 21/31] renepay: disable channels not in chan_extra_map Expand the disabled set to include channels that are not present in the chan_extra_map/uncertainty network. --- plugins/renepay/mcf.c | 77 +++++++++++++++++++------- plugins/renepay/routebuilder.c | 36 ++++++++++-- plugins/renepay/test/run-mcf-diamond.c | 6 +- plugins/renepay/test/run-mcf.c | 9 ++- plugins/renepay/uncertainty.c | 3 +- 5 files changed, 101 insertions(+), 30 deletions(-) diff --git a/plugins/renepay/mcf.c b/plugins/renepay/mcf.c index c83bc5bbf932..883057d91adb 100644 --- a/plugins/renepay/mcf.c +++ b/plugins/renepay/mcf.c @@ -442,6 +442,17 @@ static struct arc node_adjacency_next( return linear_network->node_adjacency_next_arc[arc.idx]; } +static bool channel_is_available(const struct gossmap_chan *c, int dir, + const struct gossmap *gossmap, + const bitmap *disabled) +{ + if (!gossmap_chan_set(c, dir)) + return false; + + const u32 chan_id = gossmap_chan_idx(gossmap, c); + return !bitmap_test_bit(disabled, chan_id); +} + // TODO(eduardo): unit test this /* Split a directed channel into parts with linear cost function. */ static bool linearize_channel(const struct pay_parameters *params, @@ -706,21 +717,16 @@ init_linear_network(const tal_t *ctx, const struct pay_parameters *params, for(size_t j=0;jnum_chans;++j) { - - int half; const struct gossmap_chan *c = gossmap_nth_chan(params->gossmap, node, j, &half); - if (!gossmap_chan_set(c,half)) + if (!channel_is_available(c, half, params->gossmap, + params->disabled)) continue; const u32 chan_id = gossmap_chan_idx(params->gossmap, c); - if (params->disabled && bitmap_test_bit(params->disabled,chan_id)) - continue; - - const struct gossmap_node *next = gossmap_nth_node(params->gossmap, c,!half); @@ -1166,6 +1172,7 @@ struct chan_flow * positive balance. */ static u32 find_positive_balance( const struct gossmap *gossmap, + const bitmap *disabled, const struct chan_flow *chan_flow, const u32 start_idx, const s64 *balance, @@ -1198,7 +1205,7 @@ static u32 find_positive_balance( = gossmap_nth_chan(gossmap, cur,i,&dir); - if (!gossmap_chan_set(c,dir)) + if (!channel_is_available(c, dir, gossmap, disabled)) continue; const u32 c_idx = gossmap_chan_idx(gossmap,c); @@ -1246,6 +1253,7 @@ static inline uint64_t pseudorand_interval(uint64_t a, uint64_t b) * gossmap that corresponds to this flow. */ static struct flow ** get_flow_paths(const tal_t *ctx, const struct gossmap *gossmap, + const bitmap *disabled, // chan_extra_map cannot be const because we use it to keep // track of htlcs and in_flight sats. @@ -1322,8 +1330,9 @@ get_flow_paths(const tal_t *ctx, const struct gossmap *gossmap, while(balance[node_idx]<0) { prev_chan[node_idx]=NULL; - u32 final_idx = find_positive_balance(gossmap,chan_flow,node_idx,balance, - prev_chan,prev_dir,prev_idx); + u32 final_idx = find_positive_balance( + gossmap, disabled, chan_flow, node_idx, balance, + prev_chan, prev_dir, prev_idx); /* For each route we will compute the highest htlc_min * and the smallest htlc_max and use those to constraint @@ -1550,6 +1559,32 @@ static bool is_better( return amount_msat_less_eq(A_fee,B_fee); } +/* Channels that are not in the chan_extra_map should be disabled. */ +static bool check_disabled(const bitmap *disabled, + const struct gossmap *gossmap, + const struct chan_extra_map *chan_extra_map) +{ + assert(disabled); + assert(gossmap); + assert(chan_extra_map); + + if(tal_bytelen(disabled) != bitmap_sizeof(gossmap_max_chan_idx(gossmap))) + return false; + + for (struct gossmap_chan *chan = gossmap_first_chan(gossmap); chan; + chan = gossmap_next_chan(gossmap, chan)) { + const u32 chan_id = gossmap_chan_idx(gossmap, chan); + if (bitmap_test_bit(disabled, chan_id)) + continue; + + struct short_channel_id scid = gossmap_chan_scid(gossmap, chan); + struct chan_extra *ce = + chan_extra_map_get(chan_extra_map, scid); + if (!ce) + return false; + } + return true; +} // TODO(eduardo): choose some default values for the minflow parameters /* eduardo: I think it should be clear that this module deals with linear @@ -1561,7 +1596,6 @@ static bool is_better( * TODO(eduardo): notice that we don't pay fees to forward payments with local * channels and we can tell with absolute certainty the liquidity on them. * Check that local channels have fee costs = 0 and bounds with certainty (min=max). */ - // TODO(eduardo): we should LOG_DBG the process of finding the MCF while // adjusting the frugality factor. struct flow **minflow(const tal_t *ctx, struct gossmap *gossmap, @@ -1586,8 +1620,12 @@ struct flow **minflow(const tal_t *ctx, struct gossmap *gossmap, params->chan_extra_map = chan_extra_map; params->disabled = disabled; - assert(!disabled - || tal_bytelen(disabled) == bitmap_sizeof(gossmap_max_chan_idx(gossmap))); + + if (!check_disabled(disabled, gossmap, chan_extra_map)) { + if (fail) + *fail = tal_fmt(ctx, "Invalid disabled bitmap."); + goto function_fail; + } params->amount = amount; @@ -1670,9 +1708,9 @@ struct flow **minflow(const tal_t *ctx, struct gossmap *gossmap, } // first flow found - best_flow_paths = - get_flow_paths(this_ctx, params->gossmap, params->chan_extra_map, - linear_network, residual_network, excess, &errmsg); + best_flow_paths = get_flow_paths( + this_ctx, params->gossmap, params->disabled, params->chan_extra_map, + linear_network, residual_network, excess, &errmsg); if (!best_flow_paths) { if (fail) *fail = @@ -1724,9 +1762,10 @@ struct flow **minflow(const tal_t *ctx, struct gossmap *gossmap, /* We dissect the solution of the MCF into payment routes. * Actual amounts considering fees are computed for every * channel in the routes. */ - flow_paths = get_flow_paths( - this_ctx, params->gossmap, params->chan_extra_map, - linear_network, residual_network, excess, &errmsg); + flow_paths = + get_flow_paths(this_ctx, params->gossmap, params->disabled, + params->chan_extra_map, linear_network, + residual_network, excess, &errmsg); if(!flow_paths) { // get_flow_paths doesn't fail unless there is a bug. diff --git a/plugins/renepay/routebuilder.c b/plugins/renepay/routebuilder.c index 743e4c39eb8c..a53c2b499670 100644 --- a/plugins/renepay/routebuilder.c +++ b/plugins/renepay/routebuilder.c @@ -3,18 +3,33 @@ #include #include -static bitmap *make_disabled_bitmap(const tal_t *ctx, - const struct gossmap *gossmap, - const struct short_channel_id *scids) +static bitmap * +make_disabled_bitmap(const tal_t *ctx, const struct gossmap *gossmap, + const struct chan_extra_map *chan_extra_map, + const struct short_channel_id *disabled_scids) { bitmap *disabled = tal_arrz(ctx, bitmap, BITMAP_NWORDS(gossmap_max_chan_idx(gossmap))); + if (!disabled) + return NULL; - for (size_t i = 0; i < tal_count(scids); i++) { - struct gossmap_chan *c = gossmap_find_chan(gossmap, &scids[i]); + /* Disable every channel in the list of disabled scids. */ + for (size_t i = 0; i < tal_count(disabled_scids); i++) { + struct gossmap_chan *c = + gossmap_find_chan(gossmap, &disabled_scids[i]); if (c) bitmap_set_bit(disabled, gossmap_chan_idx(gossmap, c)); } + /* Also disable every channel that we don't have in the chan_extra_map. */ + for (struct gossmap_chan *chan = gossmap_first_chan(gossmap); chan; + chan = gossmap_next_chan(gossmap, chan)) { + const u32 chan_id = gossmap_chan_idx(gossmap, chan); + struct short_channel_id scid = gossmap_chan_scid(gossmap, chan); + struct chan_extra *ce = + chan_extra_map_get(chan_extra_map, scid); + if (!ce) + bitmap_set_bit(disabled, chan_id); + } return disabled; } @@ -167,7 +182,16 @@ struct route **get_routes(const tal_t *ctx, struct payment *payment, const unsigned int maxdelay = payment->maxdelay; bitmap *disabled_bitmap = - make_disabled_bitmap(this_ctx, gossmap, payment->disabled_scids); + make_disabled_bitmap(this_ctx, gossmap, uncertainty->chan_extra_map, + payment->disabled_scids); + if (!disabled_bitmap) { + if (ecode) + *ecode = PLUGIN_ERROR; + if (fail) + *fail = + tal_fmt(ctx, "Failed to build disabled_bitmap."); + goto function_fail; + } const struct gossmap_node *src, *dst; src = gossmap_find_node(gossmap, source); diff --git a/plugins/renepay/test/run-mcf-diamond.c b/plugins/renepay/test/run-mcf-diamond.c index 3b9866771134..d02b0790e165 100644 --- a/plugins/renepay/test/run-mcf-diamond.c +++ b/plugins/renepay/test/run-mcf-diamond.c @@ -159,11 +159,15 @@ int main(int argc, char *argv[]) scid34, AMOUNT_MSAT(5000000)); + bitmap *disabled = + tal_arrz(tmpctx, bitmap, BITMAP_NWORDS(gossmap_max_chan_idx(gossmap))); + struct flow **flows; flows = minflow(tmpctx, gossmap, gossmap_find_node(gossmap, &l1), gossmap_find_node(gossmap, &l4), - chan_extra_map, NULL, + chan_extra_map, + disabled, /* Half the capacity */ AMOUNT_MSAT(1000000), // 1000 sats /* max_fee = */ AMOUNT_MSAT(10000), // 10 sats diff --git a/plugins/renepay/test/run-mcf.c b/plugins/renepay/test/run-mcf.c index 1d8e74dd5c0e..c5ae8c3d105f 100644 --- a/plugins/renepay/test/run-mcf.c +++ b/plugins/renepay/test/run-mcf.c @@ -364,12 +364,16 @@ int main(int argc, char *argv[]) int skipped_count = uncertainty_update(uncertainty, gossmap); assert(skipped_count == 0); + bitmap *disabled = + tal_arrz(tmpctx, bitmap, BITMAP_NWORDS(gossmap_max_chan_idx(gossmap))); + printf("All set, now let's call minflow ...\n"); flows = minflow(tmpctx, gossmap, gossmap_find_node(gossmap, &l1), gossmap_find_node(gossmap, &l3), - uncertainty_get_chan_extra_map(uncertainty), NULL, + uncertainty_get_chan_extra_map(uncertainty), + disabled, /* Half the capacity */ AMOUNT_MSAT(500000000), /* max_fee = */ AMOUNT_MSAT(1000000), // 1k sats @@ -533,7 +537,8 @@ int main(int argc, char *argv[]) struct flow **flows2 = minflow(tmpctx, gossmap, gossmap_find_node(gossmap, &l1), gossmap_find_node(gossmap, &l3), - uncertainty_get_chan_extra_map(uncertainty), NULL, + uncertainty_get_chan_extra_map(uncertainty), + disabled, /* This will go 400000000 via 1->3, rest via 1-2-3. */ /* amount = */ AMOUNT_MSAT(500000000), //500k sats /* max_fee = */ AMOUNT_MSAT(1000000), // 1k sats diff --git a/plugins/renepay/uncertainty.c b/plugins/renepay/uncertainty.c index 891f9a679570..ca5f4d403b93 100644 --- a/plugins/renepay/uncertainty.c +++ b/plugins/renepay/uncertainty.c @@ -108,8 +108,7 @@ int uncertainty_update(struct uncertainty *uncertainty, struct gossmap *gossmap) chan = gossmap_next_chan(gossmap, chan)) { struct short_channel_id scid = gossmap_chan_scid(gossmap, chan); struct chan_extra *ce = - chan_extra_map_get(chan_extra_map, - gossmap_chan_scid(gossmap, chan)); + chan_extra_map_get(chan_extra_map, scid); if (!ce) { struct amount_sat cap; struct amount_msat cap_msat; From 423e6566b6a0fb692e707f4d97ae7b782e621c5d Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Thu, 25 Apr 2024 08:41:32 +0100 Subject: [PATCH 22/31] renepay: refactor routefail - use switch case over all possible WIRE_* errors, - remove the virtual machine for routefail, use a simple two step solution: 1. update the gossip and 2. handle error cases --- plugins/renepay/payment.c | 55 ++++ plugins/renepay/payment.h | 13 + plugins/renepay/routebuilder.c | 38 ++- plugins/renepay/routefail.c | 487 +++++++++++++++++---------------- plugins/renepay/uncertainty.c | 16 +- plugins/renepay/uncertainty.h | 2 +- 6 files changed, 359 insertions(+), 252 deletions(-) diff --git a/plugins/renepay/payment.c b/plugins/renepay/payment.c index 2c1f17e1fdd9..60cdf5757415 100644 --- a/plugins/renepay/payment.c +++ b/plugins/renepay/payment.c @@ -99,6 +99,8 @@ struct payment *payment_new( p->cmd_array = tal_arr(p, struct command *, 0); p->local_gossmods = NULL; p->disabled_scids = tal_arr(p, struct short_channel_id, 0); + p->warned_scids = tal_arr(p, struct short_channel_id, 0); + p->disabled_nodes = tal_arr(p, struct node_id, 0); p->have_results = false; p->retry = false; @@ -116,6 +118,8 @@ static void payment_cleanup(struct payment *p) tal_resize(&p->cmd_array, 0); p->local_gossmods = tal_free(p->local_gossmods); tal_resize(&p->disabled_scids, 0); + tal_resize(&p->warned_scids, 0); + tal_resize(&p->disabled_nodes, 0); p->waitresult_timer = tal_free(p->waitresult_timer); p->routes_computed = tal_free(p->routes_computed); @@ -186,6 +190,12 @@ bool payment_update( assert(p->disabled_scids); tal_resize(&p->disabled_scids, 0); + + assert(p->warned_scids); + tal_resize(&p->warned_scids, 0); + + assert(p->disabled_nodes); + tal_resize(&p->disabled_nodes, 0); p->have_results = false; p->retry = false; @@ -365,3 +375,48 @@ void payment_disable_chan(struct payment *p, struct short_channel_id scid, str); tal_arr_expand(&p->disabled_scids, scid); } + +/* FIXME use a map instead of a array here. */ +void payment_warn_chan(struct payment *p, struct short_channel_id scid, + enum log_level lvl, const char *fmt, ...) +{ + assert(p); + assert(p->warned_scids); + va_list ap; + const char *str; + + va_start(ap, fmt); + str = tal_vfmt(tmpctx, fmt, ap); + va_end(ap); + + for (size_t i = 0; i < tal_count(p->warned_scids); i++) { + if (short_channel_id_eq(scid, p->warned_scids[i])) { + payment_disable_chan(p, scid, lvl, + "%s, channel warned twice", str); + return; + } + } + + payment_note( + p, lvl, "flagged for warning %s: %s, next time it will be disabled", + fmt_short_channel_id(tmpctx, scid), str); + tal_arr_expand(&p->warned_scids, scid); +} + +/* FIXME use a map instead of a array here. */ +void payment_disable_node(struct payment *p, struct node_id node, + enum log_level lvl, const char *fmt, ...) +{ + assert(p); + assert(p->disabled_nodes); + va_list ap; + const char *str; + + va_start(ap, fmt); + str = tal_vfmt(tmpctx, fmt, ap); + va_end(ap); + payment_note(p, lvl, "disabling node %s: %s", + fmt_node_id(tmpctx, &node), + str); + tal_arr_expand(&p->disabled_nodes, node); +} diff --git a/plugins/renepay/payment.h b/plugins/renepay/payment.h index 01a53a3a5901..31d151e6512a 100644 --- a/plugins/renepay/payment.h +++ b/plugins/renepay/payment.h @@ -129,6 +129,13 @@ struct payment { /* Channels we decided to disable for various reasons. */ struct short_channel_id *disabled_scids; + + /* Channels that we flagged for failures. If warned two times we will + * disable it. */ + struct short_channel_id *warned_scids; + + /* nodes we disable */ + struct node_id *disabled_nodes; /* Flag to indicate wether we have collected enough results to make a @@ -237,4 +244,10 @@ void payment_note(struct payment *p, enum log_level lvl, const char *fmt, ...); void payment_disable_chan(struct payment *p, struct short_channel_id scid, enum log_level lvl, const char *fmt, ...); +void payment_warn_chan(struct payment *p, struct short_channel_id scid, + enum log_level lvl, const char *fmt, ...); + +void payment_disable_node(struct payment *p, struct node_id node, + enum log_level lvl, const char *fmt, ...); + #endif /* LIGHTNING_PLUGINS_RENEPAY_PAYMENT_H */ diff --git a/plugins/renepay/routebuilder.c b/plugins/renepay/routebuilder.c index a53c2b499670..60c2cf2cb419 100644 --- a/plugins/renepay/routebuilder.c +++ b/plugins/renepay/routebuilder.c @@ -4,9 +4,9 @@ #include static bitmap * -make_disabled_bitmap(const tal_t *ctx, const struct gossmap *gossmap, - const struct chan_extra_map *chan_extra_map, - const struct short_channel_id *disabled_scids) +make_disabled_channels_bitmap(const tal_t *ctx, const struct gossmap *gossmap, + const struct chan_extra_map *chan_extra_map, + const struct short_channel_id *disabled_scids) { bitmap *disabled = tal_arrz(ctx, bitmap, BITMAP_NWORDS(gossmap_max_chan_idx(gossmap))); @@ -20,7 +20,8 @@ make_disabled_bitmap(const tal_t *ctx, const struct gossmap *gossmap, if (c) bitmap_set_bit(disabled, gossmap_chan_idx(gossmap, c)); } - /* Also disable every channel that we don't have in the chan_extra_map. */ + /* Also disable every channel that we don't have in the chan_extra_map. + */ for (struct gossmap_chan *chan = gossmap_first_chan(gossmap); chan; chan = gossmap_next_chan(gossmap, chan)) { const u32 chan_id = gossmap_chan_idx(gossmap, chan); @@ -33,6 +34,26 @@ make_disabled_bitmap(const tal_t *ctx, const struct gossmap *gossmap, return disabled; } +/* Disable all channels that lead to a disabled node. */ +static bool make_disabled_nodes(const struct gossmap *gossmap, + const struct node_id *disabled_nodes, + bitmap *disabled) +{ + /* Disable every channel in the list of disabled scids. */ + for (size_t i = 0; i < tal_count(disabled_nodes); i++) { + const struct gossmap_node *node = + gossmap_find_node(gossmap, &disabled_nodes[i]); + + for (size_t j = 0; j < node->num_chans; j++) { + int half; + const struct gossmap_chan *c = + gossmap_nth_chan(gossmap, node, j, &half); + bitmap_set_bit(disabled, gossmap_chan_idx(gossmap, c)); + } + } + return true; +} + // static void uncertainty_commit_routes(struct uncertainty *uncertainty, // struct route **routes) // { @@ -181,9 +202,12 @@ struct route **get_routes(const tal_t *ctx, struct payment *payment, const double prob_cost_factor = payment->prob_cost_factor; const unsigned int maxdelay = payment->maxdelay; - bitmap *disabled_bitmap = - make_disabled_bitmap(this_ctx, gossmap, uncertainty->chan_extra_map, - payment->disabled_scids); + bitmap *disabled_bitmap = make_disabled_channels_bitmap( + this_ctx, gossmap, uncertainty->chan_extra_map, + payment->disabled_scids); + + make_disabled_nodes(gossmap, payment->disabled_nodes, disabled_bitmap); + if (!disabled_bitmap) { if (ecode) *ecode = PLUGIN_ERROR; diff --git a/plugins/renepay/routefail.c b/plugins/renepay/routefail.c index 70a0137c4739..ca12d197eb66 100644 --- a/plugins/renepay/routefail.c +++ b/plugins/renepay/routefail.c @@ -6,104 +6,40 @@ #include #include +enum node_type { + FINAL_NODE, + INTERMEDIATE_NODE, + ORIGIN_NODE, + UNKNOWN_NODE +}; + struct routefail { - u64 exec_state; struct command *cmd; struct route *route; }; -struct routefail_modifier { - const char *name; - struct command_result *(*step_cb)(struct routefail *r); -}; - -#define REGISTER_ROUTEFAIL_MODIFIER(name, step_cb) \ - struct routefail_modifier name##_routefail_mod = { \ - stringify(name), \ - typesafe_cb_cast(struct command_result * (*)(struct routefail *), \ - struct command_result * (*)(struct routefail *), \ - step_cb), \ - }; - -static struct command_result *routefail_continue(struct routefail *r); +static struct command_result *update_gossip(struct routefail *r); +static struct command_result *handle_failure(struct routefail *r); struct command_result *routefail_start(const tal_t *ctx, struct route *route, struct command *cmd) { struct routefail *r = tal(ctx, struct routefail); - r->exec_state = 0; r->route = route; r->cmd = cmd; assert(route->result); - return routefail_continue(r); -} - -void *routefail_virtual_program[]; -static struct command_result *routefail_continue(struct routefail *r) -{ - - assert(r->exec_state != INVALID_STATE); - const struct routefail_modifier *mod = - (const struct routefail_modifier *) - routefail_virtual_program[r->exec_state++]; - - if (mod == NULL) - plugin_err(pay_plugin->plugin, - "%s expected routefail_modifier " - "but NULL found", - __PRETTY_FUNCTION__); - - plugin_log(pay_plugin->plugin, LOG_DBG, "Calling routefail_modifier %s", - mod->name); - return mod->step_cb(r); + return update_gossip(r); } -/* Generic handler for RPC failures. */ -static struct command_result *routefail_rpc_failure(struct command *cmd, - const char *buffer, - const jsmntok_t *toks, - struct routefail *r) -{ - const jsmntok_t *codetok = json_get_member(buffer, toks, "code"); - u32 errcode; - if (codetok != NULL) - json_to_u32(buffer, codetok, &errcode); - else - errcode = LIGHTNINGD; - - plugin_err(r->cmd->plugin, - "routefail state machine has stopped due to a failed RPC " - "call: %.*s", - json_tok_full_len(toks), json_tok_full(buffer, toks)); - return notification_handled(r->cmd); -} - -/***************************************************************************** - * end - * - * The default ending of routefail. - */ -static struct command_result *end_done(struct command *cmd, - const char *buf UNUSED, - const jsmntok_t *result UNUSED, - struct routefail *r) +static struct command_result *routefail_end(struct routefail *r) { /* Notify the tracker that route has failed and routefail have completed * handling all possible errors cases. */ + struct command *cmd = r->cmd; route_failure_register(r->route); tal_free(r); return notification_handled(cmd); } -static struct command_result *end_cb(struct routefail *r) -{ - struct out_req *req = - jsonrpc_request_start(r->cmd->plugin, r->cmd, "waitblockheight", - end_done, routefail_rpc_failure, r); - json_add_num(req->js, "blockheight", 0); - return send_outreq(r->cmd->plugin, req); -} - -REGISTER_ROUTEFAIL_MODIFIER(end, end_cb); /***************************************************************************** * update_gossip @@ -165,7 +101,7 @@ static struct command_result *update_gossip_done(struct command *cmd UNUSED, const jsmntok_t *result UNUSED, struct routefail *r) { - return routefail_continue(r); + return handle_failure(r); } static struct command_result *update_gossip_failure(struct command *cmd UNUSED, @@ -182,10 +118,10 @@ static struct command_result *update_gossip_failure(struct command *cmd UNUSED, r->route->payment, *r->route->result->erring_channel, LOG_INFORM, "addgossip failed (%.*s)", json_tok_full_len(result), json_tok_full(buf, result)); - return routefail_continue(r); + return update_gossip_done(cmd, buf, result, r); } -static struct command_result *update_gossip_cb(struct routefail *r) +static struct command_result *update_gossip(struct routefail *r) { /* if there is no raw_message we continue */ if (!r->route->result->raw_message) @@ -204,46 +140,9 @@ static struct command_result *update_gossip_cb(struct routefail *r) return send_outreq(r->cmd->plugin, req); skip_update_gossip: - return routefail_continue(r); -} - -REGISTER_ROUTEFAIL_MODIFIER(update_gossip, update_gossip_cb); - -/***************************************************************************** - * update_knowledge - * - * Update the uncertainty network from waitsendpay error message. - */ - -static struct command_result *update_knowledge_cb(struct routefail *r) -{ - const struct route *route = r->route; - const struct payment_result *result = route->result; - - /* FIXME: If we don't know the hops there isn't much we can infer, but - * a little bit we could. */ - if (!route->hops || !result->erring_index) - goto skip_update_network; - - uncertainty_channel_can_send(pay_plugin->uncertainty, route, - *result->erring_index); - - if (result->failcode == WIRE_TEMPORARY_CHANNEL_FAILURE && - *result->erring_index < tal_count(route->hops)) { - uncertainty_channel_cannot_send( - pay_plugin->uncertainty, - route->hops[*result->erring_index].scid, - route->hops[*result->erring_index].direction); - } - - uncertainty_remove_htlcs(pay_plugin->uncertainty, route); - -skip_update_network: - return routefail_continue(r); + return handle_failure(r); } -REGISTER_ROUTEFAIL_MODIFIER(update_knowledge, update_knowledge_cb); - /***************************************************************************** * handle_cases * @@ -254,19 +153,9 @@ REGISTER_ROUTEFAIL_MODIFIER(update_knowledge, update_knowledge_cb); /* Mark this as a final error. When read this route result will inmediately end * the payment. */ static void route_final_error(struct route *route, enum jsonrpc_errcode error, - const char *what) -{ - route->final_error = error; - route->final_msg = tal_strdup(route, what); -} - -static void handle_unhandleable_error(struct route *route, const char *fmt, ...) + const char *fmt, ...) { - if (!route->hops) - return; - size_t n = tal_count(route->hops); - if (n == 0) - return; + assert(route); va_list ap; const char *what; @@ -275,132 +164,266 @@ static void handle_unhandleable_error(struct route *route, const char *fmt, ...) what = tal_vfmt(tmpctx, fmt, ap); va_end(ap); - if (n == 1) { - /* This is a terminal error. */ - return route_final_error(route, PAY_UNPARSEABLE_ONION, what); - } - - /* Prefer a node not directly connected to either end. */ - if (n > 3) { - /* us ->0-> ourpeer ->1-> rando ->2-> theirpeer ->3-> dest */ - n = 1 + pseudorand(n - 2); - } else - /* Assume it's not the destination */ - n = pseudorand(n - 1); - - payment_disable_chan(route->payment, route->hops[n].scid, LOG_INFORM, - "randomly chosen"); + route->final_error = error; + route->final_msg = tal_strdup(route, what); } -static struct command_result *handle_cases_cb(struct routefail *r) +static struct command_result *handle_failure(struct routefail *r) { + /* BOLT #4: + * + * A _forwarding node_ MAY, but a _final node_ MUST NOT: + * - return an `invalid_onion_version` error. + * - return an `invalid_onion_hmac` error. + * - return an `invalid_onion_key` error. + * - return a `temporary_channel_failure` error. + * - return a `permanent_channel_failure` error. + * - return a `required_channel_feature_missing` error. + * - return an `unknown_next_peer` error. + * - return an `amount_below_minimum` error. + * - return a `fee_insufficient` error. + * - return an `incorrect_cltv_expiry` error. + * - return an `expiry_too_soon` error. + * - return an `expiry_too_far` error. + * - return a `channel_disabled` error. + * + * An _intermediate hop_ MUST NOT, but the _final node_: + * - MUST return an `incorrect_or_unknown_payment_details` error. + * - MUST return `final_incorrect_cltv_expiry` error. + * - MUST return a `final_incorrect_htlc_amount` error. + */ + + assert(r); struct route *route = r->route; - const struct payment_result *result = route->result; + assert(route); + struct payment_result *result = route->result; + assert(result); + struct payment *payment = route->payment; + assert(payment); - // TODO: i am not sure these are compulsory - assert(result->erring_index); - assert(result->erring_node); + u32 path_len = 0; + if (route->hops) + path_len = tal_count(route->hops); - switch (result->code) { - case PAY_UNPARSEABLE_ONION: - handle_unhandleable_error( - route, "received PAY_UNPARSEABLE_ONION error"); - goto finish; - break; - case PAY_TRY_OTHER_ROUTE: - break; - case PAY_DESTINATION_PERM_FAIL: - default: - route_final_error(route, result->code, result->message); - goto finish; + assert(result->erring_index); + /* index of the last channel before the erring node */ + const int last_good_channel = *result->erring_index - 1; + + enum node_type node_type = UNKNOWN_NODE; + if (route->hops) { + if (*result->erring_index == path_len) + node_type = FINAL_NODE; + else if (*result->erring_index == 0) + node_type = ORIGIN_NODE; + else + node_type = INTERMEDIATE_NODE; } - /* Final node is usually a hard failure */ - if (node_id_eq(result->erring_node, &route->payment->destination) && - result->failcode != WIRE_MPP_TIMEOUT) { - route_final_error(route, PAY_DESTINATION_PERM_FAIL, - "final destination permanent failure"); - goto finish; - } + assert(result->erring_node); switch (result->failcode) { - /* These definitely mean eliminate channel */ - case WIRE_PERMANENT_CHANNEL_FAILURE: - case WIRE_REQUIRED_CHANNEL_FEATURE_MISSING: - /* FIXME: lnd returns this for disconnected peer, so don't disable perm! - */ - case WIRE_UNKNOWN_NEXT_PEER: - case WIRE_CHANNEL_DISABLED: - /* These mean node is weird, but we eliminate channel here too */ - case WIRE_INVALID_REALM: - case WIRE_TEMPORARY_NODE_FAILURE: - case WIRE_PERMANENT_NODE_FAILURE: - case WIRE_REQUIRED_NODE_FEATURE_MISSING: - /* These shouldn't happen, but eliminate channel */ + // intermediate only case WIRE_INVALID_ONION_VERSION: case WIRE_INVALID_ONION_HMAC: case WIRE_INVALID_ONION_KEY: - case WIRE_INVALID_ONION_PAYLOAD: + if (node_type == FINAL_NODE) + payment_note(payment, LOG_UNUSUAL, + "Final node %s reported strange " + "error code %04x (%s)", + fmt_node_id(tmpctx, result->erring_node), + result->failcode, + onion_wire_name(result->failcode)); + case WIRE_INVALID_ONION_BLINDING: - case WIRE_EXPIRY_TOO_FAR: - if (result->erring_channel) - payment_disable_chan(route->payment, - *result->erring_channel, - LOG_UNUSUAL, "%s", - onion_wire_name(result->failcode)); - else - handle_unhandleable_error( - route, - "received %s error, but don't have an " - "erring_channel", + if (node_type == FINAL_NODE) { + /* these errors from a final node mean a permanent + * failure */ + route_final_error( + route, PAY_DESTINATION_PERM_FAIL, + "Received error code %04x (%s) at final node.", + result->failcode, onion_wire_name(result->failcode)); + } else if (node_type == INTERMEDIATE_NODE || + node_type == ORIGIN_NODE) { + /* we disable the next node in the hop */ + assert(*result->erring_index < path_len); + payment_disable_node( + route->payment, + route->hops[*result->erring_index].node_id, LOG_DBG, + "received %s from previous hop", + onion_wire_name(result->failcode)); + } break; - /* These can be fixed (maybe) by applying the included channel_update */ - case WIRE_AMOUNT_BELOW_MINIMUM: - case WIRE_FEE_INSUFFICIENT: - case WIRE_INCORRECT_CLTV_EXPIRY: - case WIRE_EXPIRY_TOO_SOON: - case WIRE_TEMPORARY_CHANNEL_FAILURE: - break; - - /* These should only come from the final distination. */ - case WIRE_MPP_TIMEOUT: + // final only case WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: - case WIRE_FINAL_INCORRECT_CLTV_EXPIRY: case WIRE_FINAL_INCORRECT_HTLC_AMOUNT: + case WIRE_FINAL_INCORRECT_CLTV_EXPIRY: + if (node_type == INTERMEDIATE_NODE) + payment_note(payment, LOG_UNUSUAL, + "Intermediate node %s reported strange " + "error code %04x (%s)", + fmt_node_id(tmpctx, result->erring_node), + result->failcode, + onion_wire_name(result->failcode)); + + case WIRE_PERMANENT_NODE_FAILURE: + case WIRE_REQUIRED_NODE_FEATURE_MISSING: + case WIRE_TEMPORARY_NODE_FAILURE: + case WIRE_INVALID_REALM: + case WIRE_INVALID_ONION_PAYLOAD: + + if (node_type == FINAL_NODE) { + route_final_error( + route, PAY_DESTINATION_PERM_FAIL, + "Received error code %04x (%s) at final node.", + result->failcode, + onion_wire_name(result->failcode)); + } else if (node_type == ORIGIN_NODE) { + route_final_error( + route, PAY_UNSPECIFIED_ERROR, + "Error code %04x (%s) reported at the origin.", + result->failcode, + onion_wire_name(result->failcode)); + } else { + payment_disable_node(route->payment, + *result->erring_node, LOG_INFORM, + "received error %s", + onion_wire_name(result->failcode)); + } break; - default: - if (result->erring_channel) + // intermediate only + case WIRE_PERMANENT_CHANNEL_FAILURE: + case WIRE_REQUIRED_CHANNEL_FEATURE_MISSING: + case WIRE_UNKNOWN_NEXT_PEER: + case WIRE_EXPIRY_TOO_FAR: + case WIRE_CHANNEL_DISABLED: + if (node_type == FINAL_NODE) { + payment_note(payment, LOG_UNUSUAL, + "Final node %s reported strange " + "error code %04x (%s)", + fmt_node_id(tmpctx, result->erring_node), + result->failcode, + onion_wire_name(result->failcode)); + + route_final_error( + route, PAY_DESTINATION_PERM_FAIL, + "Received error code %04x (%s) at final node.", + result->failcode, + onion_wire_name(result->failcode)); + + } else { + assert(result->erring_channel); payment_disable_chan( - route->payment, *result->erring_channel, - LOG_UNUSUAL, "Unexpected error code %u", - result->failcode); - else - handle_unhandleable_error( - route, - "received %s error, but don't have an " - "erring_channel", + route->payment, *result->erring_channel, LOG_INFORM, + "%s", onion_wire_name(result->failcode)); + } + break; + // final only + case WIRE_MPP_TIMEOUT: + + if (node_type == INTERMEDIATE_NODE) { + /* Normally WIRE_MPP_TIMEOUT is raised by the final + * node. If this is not the final node, then something + * wrong is going on. We report it and disable that + * node. */ + payment_note(payment, LOG_UNUSUAL, + "Intermediate node %s reported strange " + "error code %04x (%s)", + fmt_node_id(tmpctx, result->erring_node), + result->failcode, + onion_wire_name(result->failcode)); + + payment_disable_node(route->payment, + *result->erring_node, LOG_INFORM, + "received error %s", + onion_wire_name(result->failcode)); + } + break; + + // intermediate only + case WIRE_EXPIRY_TOO_SOON: + case WIRE_INCORRECT_CLTV_EXPIRY: + case WIRE_FEE_INSUFFICIENT: + case WIRE_AMOUNT_BELOW_MINIMUM: + + if (node_type == FINAL_NODE) { + payment_note(payment, LOG_UNUSUAL, + "Final node %s reported strange " + "error code %04x (%s)", + fmt_node_id(tmpctx, result->erring_node), + result->failcode, + onion_wire_name(result->failcode)); + + route_final_error( + route, PAY_DESTINATION_PERM_FAIL, + "Received error code %04x (%s) at final node.", + result->failcode, onion_wire_name(result->failcode)); - } -finish: - return routefail_continue(r); -} + } else { + /* Usually this means we need to update the channel + * information and try again. To avoid hitting this + * error again with the same channel we flag it. */ + assert(result->erring_channel); + payment_warn_chan(route->payment, + *result->erring_channel, LOG_INFORM, + "received error %s", + onion_wire_name(result->failcode)); + } -REGISTER_ROUTEFAIL_MODIFIER(handle_cases, handle_cases_cb); + break; + // intermediate only + case WIRE_TEMPORARY_CHANNEL_FAILURE: -/***************************************************************************** - * Virtual machine */ -// TODO: maybe I should make a single virtual machine interpreter (with -// templates and typesafety?) that is able to run on different static programs -// and types. One instance will execute the payment program and another instance -// will run the routefail program. - -void *routefail_virtual_program[] = { - &update_gossip_routefail_mod, - &update_knowledge_routefail_mod, - &handle_cases_routefail_mod, - &end_routefail_mod, - NULL}; + if (node_type == FINAL_NODE) { + /* WIRE_TEMPORARY_CHANNEL_FAILURE could mean that the + * next channel has not enough outbound liquidity or + * cannot add another HTLC. A final node cannot raise + * this error. */ + payment_note(payment, LOG_UNUSUAL, + "Final node %s reported strange " + "error code %04x (%s)", + fmt_node_id(tmpctx, result->erring_node), + result->failcode, + onion_wire_name(result->failcode)); + + route_final_error( + route, PAY_DESTINATION_PERM_FAIL, + "Received error code %04x (%s) at final node.", + result->failcode, + onion_wire_name(result->failcode)); + } + + break; + } + + /* Update the knowledge in the uncertaity network. */ + if (route->hops) { + assert(last_good_channel < path_len); + + /* All channels before the erring node could forward the + * payment. */ + for (int i = 0; i <= last_good_channel; i++) { + uncertainty_channel_can_send(pay_plugin->uncertainty, + route->hops[i].scid, + route->hops[i].direction); + } + + if (result->failcode == WIRE_TEMPORARY_CHANNEL_FAILURE && + (last_good_channel + 1) < path_len) { + /* A WIRE_TEMPORARY_CHANNEL_FAILURE could mean not + * enough liquidity to forward the payment or cannot add + * one more HTLC. + */ + uncertainty_channel_cannot_send( + pay_plugin->uncertainty, + route->hops[last_good_channel + 1].scid, + route->hops[last_good_channel + 1].direction); + } + uncertainty_remove_htlcs(pay_plugin->uncertainty, route); + } + + return routefail_end(r); +} diff --git a/plugins/renepay/uncertainty.c b/plugins/renepay/uncertainty.c index ca5f4d403b93..37529431a549 100644 --- a/plugins/renepay/uncertainty.c +++ b/plugins/renepay/uncertainty.c @@ -55,19 +55,11 @@ void uncertainty_commit_htlcs(struct uncertainty *uncertainty, } void uncertainty_channel_can_send(struct uncertainty *uncertainty, - const struct route *route, u32 erridx) + struct short_channel_id scid, int direction) { - if (!route->hops) - return; - - const size_t pathlen = tal_count(route->hops); - for (size_t i = 0; i < erridx && i < pathlen; i++) { - const struct route_hop *hop = &route->hops[i]; - struct short_channel_id_dir scidd = {hop->scid, hop->direction}; - - // FIXME: check error - chan_extra_can_send(uncertainty->chan_extra_map, &scidd); - } + struct short_channel_id_dir scidd = {scid, direction}; + // FIXME: check error + chan_extra_can_send(uncertainty->chan_extra_map, &scidd); } void uncertainty_channel_cannot_send(struct uncertainty *uncertainty, struct short_channel_id scid, diff --git a/plugins/renepay/uncertainty.h b/plugins/renepay/uncertainty.h index 2f13c51f1503..a591ece4773e 100644 --- a/plugins/renepay/uncertainty.h +++ b/plugins/renepay/uncertainty.h @@ -29,7 +29,7 @@ void uncertainty_commit_htlcs(struct uncertainty *uncertainty, const struct route *route); void uncertainty_channel_can_send(struct uncertainty *uncertainty, - const struct route *route, u32 erridx); + struct short_channel_id scid, int direction); void uncertainty_channel_cannot_send(struct uncertainty *uncertainty, struct short_channel_id scid, From 1d24d3b2f5e30aaa541e8c98a02b4662529b0c07 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Sat, 4 May 2024 10:36:45 +0100 Subject: [PATCH 23/31] renepay: add disabledmap abstraction Define a new object called disabledmap that carries information about the disabled channels and nodes. --- plugins/renepay/Makefile | 2 + plugins/renepay/disabledmap.c | 86 +++++++++++++++++++++++++++++++++++ plugins/renepay/disabledmap.h | 37 +++++++++++++++ plugins/renepay/payment.c | 49 ++++++++------------ plugins/renepay/payment.h | 12 +---- 5 files changed, 146 insertions(+), 40 deletions(-) create mode 100644 plugins/renepay/disabledmap.c create mode 100644 plugins/renepay/disabledmap.h diff --git a/plugins/renepay/Makefile b/plugins/renepay/Makefile index a470c00ac436..69e6be665db0 100644 --- a/plugins/renepay/Makefile +++ b/plugins/renepay/Makefile @@ -3,6 +3,7 @@ PLUGIN_RENEPAY_SRC := \ plugins/renepay/flow.c \ plugins/renepay/mcf.c \ plugins/renepay/dijkstra.c \ + plugins/renepay/disabledmap.c \ plugins/renepay/payment.c \ plugins/renepay/chan_extra.c \ plugins/renepay/route.c \ @@ -19,6 +20,7 @@ PLUGIN_RENEPAY_HDRS := \ plugins/renepay/flow.h \ plugins/renepay/mcf.h \ plugins/renepay/dijkstra.h \ + plugins/renepay/disabledmap.h \ plugins/renepay/payment.h \ plugins/renepay/chan_extra.h \ plugins/renepay/route.h \ diff --git a/plugins/renepay/disabledmap.c b/plugins/renepay/disabledmap.c new file mode 100644 index 000000000000..58e41d3285ad --- /dev/null +++ b/plugins/renepay/disabledmap.c @@ -0,0 +1,86 @@ +#include "config.h" +#include + +struct disabledmap *disabledmap_new(const tal_t *ctx) +{ + struct disabledmap *obj = tal(ctx, struct disabledmap); + if (!obj) + return NULL; + + obj->disabled_scids = tal_arr(obj, struct short_channel_id, 0); + obj->warned_scids = tal_arr(obj, struct short_channel_id, 0); + obj->disabled_nodes = tal_arr(obj, struct node_id, 0); + + if (!obj->disabled_scids || !obj->warned_scids || !obj->disabled_nodes) + return tal_free(obj); + return obj; +} + +// FIXME: check success +void disabledmap_reset(struct disabledmap *p) +{ + tal_resize(&p->disabled_scids, 0); + tal_resize(&p->warned_scids, 0); + tal_resize(&p->disabled_nodes, 0); +} + +// FIXME: check success +void disabledmap_add_channel(struct disabledmap *p, + struct short_channel_id scid) +{ + tal_arr_expand(&p->disabled_scids, scid); +} + +// FIXME: check success +void disabledmap_warn_channel(struct disabledmap *p, + struct short_channel_id scid) +{ + tal_arr_expand(&p->warned_scids, scid); +} + +// FIXME: check success +void disabledmap_add_node(struct disabledmap *p, struct node_id node) +{ + tal_arr_expand(&p->disabled_nodes, node); +} + +bool disabledmap_channel_is_warned(struct disabledmap *p, + struct short_channel_id scid) +{ + for (size_t i = 0; i < tal_count(p->warned_scids); i++) { + if (short_channel_id_eq(scid, p->warned_scids[i])) + return true; + } + return false; +} + +bitmap *tal_disabledmap_get_bitmap(const tal_t *ctx, struct disabledmap *p, + const struct gossmap *gossmap) +{ + bitmap *disabled = + tal_arrz(ctx, bitmap, BITMAP_NWORDS(gossmap_max_chan_idx(gossmap))); + if (!disabled) + return NULL; + + /* Disable every channel in the list of disabled scids. */ + for (size_t i = 0; i < tal_count(p->disabled_scids); i++) { + struct gossmap_chan *c = + gossmap_find_chan(gossmap, &p->disabled_scids[i]); + if (c) + bitmap_set_bit(disabled, gossmap_chan_idx(gossmap, c)); + } + + /* Disable all channels that lead to a disabled node. */ + for (size_t i = 0; i < tal_count(p->disabled_nodes); i++) { + const struct gossmap_node *node = + gossmap_find_node(gossmap, &p->disabled_nodes[i]); + + for (size_t j = 0; j < node->num_chans; j++) { + int half; + const struct gossmap_chan *c = + gossmap_nth_chan(gossmap, node, j, &half); + bitmap_set_bit(disabled, gossmap_chan_idx(gossmap, c)); + } + } + return disabled; +} diff --git a/plugins/renepay/disabledmap.h b/plugins/renepay/disabledmap.h new file mode 100644 index 000000000000..17230b59b65f --- /dev/null +++ b/plugins/renepay/disabledmap.h @@ -0,0 +1,37 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_DISABLEDMAP_H +#define LIGHTNING_PLUGINS_RENEPAY_DISABLEDMAP_H + +#include "config.h" +#include +#include +#include +#include + +struct disabledmap { + /* Channels we decided to disable for various reasons. */ + /* FIXME: disabled_scids should be a set rather than an array, so that + * we don't have to worry about disabling the same channel multiple + * times. */ + struct short_channel_id *disabled_scids; + + /* Channels that we flagged for failures. If warned two times we will + * disable it. */ + struct short_channel_id *warned_scids; + + /* nodes we disable */ + struct node_id *disabled_nodes; +}; + +void disabledmap_reset(struct disabledmap *p); +struct disabledmap *disabledmap_new(const tal_t *ctx); +void disabledmap_add_channel(struct disabledmap *p, + struct short_channel_id scid); +void disabledmap_warn_channel(struct disabledmap *p, + struct short_channel_id scid); +void disabledmap_add_node(struct disabledmap *p, struct node_id node); +bool disabledmap_channel_is_warned(struct disabledmap *p, + struct short_channel_id scid); +bitmap *tal_disabledmap_get_bitmap(const tal_t *ctx, struct disabledmap *p, + const struct gossmap *gossmap); + +#endif /* LIGHTNING_PLUGINS_RENEPAY_DISABLEDMAP_H */ diff --git a/plugins/renepay/payment.c b/plugins/renepay/payment.c index 60cdf5757415..7ade4e74127c 100644 --- a/plugins/renepay/payment.c +++ b/plugins/renepay/payment.c @@ -98,9 +98,7 @@ struct payment *payment_new( p->next_partid = 1; p->cmd_array = tal_arr(p, struct command *, 0); p->local_gossmods = NULL; - p->disabled_scids = tal_arr(p, struct short_channel_id, 0); - p->warned_scids = tal_arr(p, struct short_channel_id, 0); - p->disabled_nodes = tal_arr(p, struct node_id, 0); + p->disabledmap = disabledmap_new(p); p->have_results = false; p->retry = false; @@ -117,9 +115,12 @@ static void payment_cleanup(struct payment *p) p->exec_state = INVALID_STATE; tal_resize(&p->cmd_array, 0); p->local_gossmods = tal_free(p->local_gossmods); - tal_resize(&p->disabled_scids, 0); - tal_resize(&p->warned_scids, 0); - tal_resize(&p->disabled_nodes, 0); + + /* FIXME: for optimization, a cleanup should prune all the data that has + * no use after a payent is completed. The entire disablemap structure + * is no longer needed, hence I guess we should free it not just reset + * it. */ + disabledmap_reset(p->disabledmap); p->waitresult_timer = tal_free(p->waitresult_timer); p->routes_computed = tal_free(p->routes_computed); @@ -188,14 +189,8 @@ bool payment_update( p->local_gossmods = tal_free(p->local_gossmods); - assert(p->disabled_scids); - tal_resize(&p->disabled_scids, 0); - - assert(p->warned_scids); - tal_resize(&p->warned_scids, 0); - - assert(p->disabled_nodes); - tal_resize(&p->disabled_nodes, 0); + assert(p->disabledmap); + disabledmap_reset(p->disabledmap); p->have_results = false; p->retry = false; @@ -357,13 +352,11 @@ static struct command_result *payment_finish(struct payment *p) return my_command_finish(p, cmd); } -/* FIXME: disabled_scids should be a set rather than an array, so that we don't - * have to worry about disabling the same channel multiple times. */ void payment_disable_chan(struct payment *p, struct short_channel_id scid, enum log_level lvl, const char *fmt, ...) { assert(p); - assert(p->disabled_scids); + assert(p->disabledmap); va_list ap; const char *str; @@ -373,15 +366,14 @@ void payment_disable_chan(struct payment *p, struct short_channel_id scid, payment_note(p, lvl, "disabling %s: %s", fmt_short_channel_id(tmpctx, scid), str); - tal_arr_expand(&p->disabled_scids, scid); + disabledmap_add_channel(p->disabledmap, scid); } -/* FIXME use a map instead of a array here. */ void payment_warn_chan(struct payment *p, struct short_channel_id scid, enum log_level lvl, const char *fmt, ...) { assert(p); - assert(p->warned_scids); + assert(p->disabledmap); va_list ap; const char *str; @@ -389,26 +381,23 @@ void payment_warn_chan(struct payment *p, struct short_channel_id scid, str = tal_vfmt(tmpctx, fmt, ap); va_end(ap); - for (size_t i = 0; i < tal_count(p->warned_scids); i++) { - if (short_channel_id_eq(scid, p->warned_scids[i])) { - payment_disable_chan(p, scid, lvl, - "%s, channel warned twice", str); - return; - } + if (disabledmap_channel_is_warned(p->disabledmap, scid)) { + payment_disable_chan(p, scid, lvl, "%s, channel warned twice", + str); + return; } payment_note( p, lvl, "flagged for warning %s: %s, next time it will be disabled", fmt_short_channel_id(tmpctx, scid), str); - tal_arr_expand(&p->warned_scids, scid); + disabledmap_warn_channel(p->disabledmap, scid); } -/* FIXME use a map instead of a array here. */ void payment_disable_node(struct payment *p, struct node_id node, enum log_level lvl, const char *fmt, ...) { assert(p); - assert(p->disabled_nodes); + assert(p->disabledmap); va_list ap; const char *str; @@ -418,5 +407,5 @@ void payment_disable_node(struct payment *p, struct node_id node, payment_note(p, lvl, "disabling node %s: %s", fmt_node_id(tmpctx, &node), str); - tal_arr_expand(&p->disabled_nodes, node); + disabledmap_add_node(p->disabledmap, node); } diff --git a/plugins/renepay/payment.h b/plugins/renepay/payment.h index 31d151e6512a..cda65d6a6c8f 100644 --- a/plugins/renepay/payment.h +++ b/plugins/renepay/payment.h @@ -3,6 +3,7 @@ #include "config.h" #include #include +#include enum payment_status { PAYMENT_PENDING, PAYMENT_SUCCESS, PAYMENT_FAIL }; @@ -127,16 +128,7 @@ struct payment { /* Localmods to apply to gossip_map for our own use. */ struct gossmap_localmods *local_gossmods; - /* Channels we decided to disable for various reasons. */ - struct short_channel_id *disabled_scids; - - /* Channels that we flagged for failures. If warned two times we will - * disable it. */ - struct short_channel_id *warned_scids; - - /* nodes we disable */ - struct node_id *disabled_nodes; - + struct disabledmap *disabledmap; /* Flag to indicate wether we have collected enough results to make a * decision on the payment progress. */ From 6846d04e413562af9c8a67970b4d23dfc3b5676e Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Sat, 4 May 2024 10:48:13 +0100 Subject: [PATCH 24/31] renepay: remove payment from route Routes contain only routing information and the payment they're linked to can be obtained through the payment_hash. We remove the dependency of route building routines from the payment itself. In order to make plain payment information available we define a payment_info structure. --- plugins/renepay/Makefile | 1 + plugins/renepay/json.c | 52 +++++++------ plugins/renepay/json.h | 3 +- plugins/renepay/mods.c | 75 +++++++++++------- plugins/renepay/payment.c | 76 +++++++++--------- plugins/renepay/payment.h | 95 ++++------------------- plugins/renepay/payment_info.h | 82 ++++++++++++++++++++ plugins/renepay/route.c | 12 ++- plugins/renepay/route.h | 9 +-- plugins/renepay/routebuilder.c | 136 ++++++++++++--------------------- plugins/renepay/routebuilder.h | 18 +++-- plugins/renepay/routefail.c | 41 +++++++--- plugins/renepay/routetracker.c | 72 ++++++++++------- plugins/renepay/routetracker.h | 12 ++- 14 files changed, 363 insertions(+), 321 deletions(-) create mode 100644 plugins/renepay/payment_info.h diff --git a/plugins/renepay/Makefile b/plugins/renepay/Makefile index 69e6be665db0..8d74d0a8ee39 100644 --- a/plugins/renepay/Makefile +++ b/plugins/renepay/Makefile @@ -22,6 +22,7 @@ PLUGIN_RENEPAY_HDRS := \ plugins/renepay/dijkstra.h \ plugins/renepay/disabledmap.h \ plugins/renepay/payment.h \ + plugins/renepay/payment_info.h \ plugins/renepay/chan_extra.h \ plugins/renepay/route.h \ plugins/renepay/routebuilder.h \ diff --git a/plugins/renepay/json.c b/plugins/renepay/json.c index 065c66723d54..58224c81b66e 100644 --- a/plugins/renepay/json.c +++ b/plugins/renepay/json.c @@ -173,20 +173,21 @@ void json_add_payment(struct json_stream *s, const struct payment *payment) { assert(s); assert(payment); + const struct payment_info *pinfo = &payment->payment_info; - if (payment->label != NULL) - json_add_string(s, "label", payment->label); - if (payment->invstr != NULL) - json_add_invstring(s, payment->invstr); + if (pinfo->label != NULL) + json_add_string(s, "label", pinfo->label); + if (pinfo->invstr != NULL) + json_add_invstring(s, pinfo->invstr); - json_add_amount_msat(s, "amount_msat", payment->amount); - json_add_sha256(s, "payment_hash", &payment->payment_hash); - json_add_node_id(s, "destination", &payment->destination); + json_add_amount_msat(s, "amount_msat", pinfo->amount); + json_add_sha256(s, "payment_hash", &pinfo->payment_hash); + json_add_node_id(s, "destination", &pinfo->destination); - if (payment->description) - json_add_string(s, "description", payment->description); + if (pinfo->description) + json_add_string(s, "description", pinfo->description); - json_add_timeabs(s, "created_at", payment->start_time); + json_add_timeabs(s, "created_at", pinfo->start_time); json_add_u64(s, "groupid", payment->groupid); json_add_u64(s, "parts", payment->next_partid); @@ -219,13 +220,14 @@ void json_add_payment(struct json_stream *s, const struct payment *payment) // - number of parts? } -void json_add_route(struct json_stream *js, const struct route *route) +void json_add_route(struct json_stream *js, const struct route *route, + const struct payment *payment) { assert(js); assert(route); - - struct payment *payment = route->payment; assert(payment); + + const struct payment_info *pinfo = &payment->payment_info; assert(route->hops); const size_t pathlen = tal_count(route->hops); @@ -245,10 +247,10 @@ void json_add_route(struct json_stream *js, const struct route *route) json_object_end(js); } json_array_end(js); - json_add_sha256(js, "payment_hash", &payment->payment_hash); + json_add_sha256(js, "payment_hash", &pinfo->payment_hash); - if (payment->payment_secret) - json_add_secret(js, "payment_secret", payment->payment_secret); + if (pinfo->payment_secret) + json_add_secret(js, "payment_secret", pinfo->payment_secret); /* FIXME: sendpay has a check that we don't total more than * the exact amount, if we're setting partid (i.e. MPP). @@ -259,24 +261,24 @@ void json_add_route(struct json_stream *js, const struct route *route) * The spec was loosened so you are actually allowed * to overpay, so this check is now overzealous. */ if (pathlen > 0 && - amount_msat_greater(route_delivers(route), payment->amount)) { + amount_msat_greater(route_delivers(route), pinfo->amount)) { json_add_amount_msat(js, "amount_msat", route_delivers(route)); } else { - json_add_amount_msat(js, "amount_msat", payment->amount); + json_add_amount_msat(js, "amount_msat", pinfo->amount); } json_add_u64(js, "partid", route->key.partid); json_add_u64(js, "groupid", route->key.groupid); /* FIXME: some of these fields might not be required for all * payment parts. */ - json_add_string(js, "bolt11", payment->invstr); + json_add_string(js, "bolt11", pinfo->invstr); - if (payment->payment_metadata) + if (pinfo->payment_metadata) json_add_hex_talarr(js, "payment_metadata", - payment->payment_metadata); - if (payment->label) - json_add_string(js, "label", payment->label); - if (payment->description) - json_add_string(js, "description", payment->description); + pinfo->payment_metadata); + if (pinfo->label) + json_add_string(js, "label", pinfo->label); + if (pinfo->description) + json_add_string(js, "description", pinfo->description); } diff --git a/plugins/renepay/json.h b/plugins/renepay/json.h index 3658a370878f..b7bdab7e4590 100644 --- a/plugins/renepay/json.h +++ b/plugins/renepay/json.h @@ -14,6 +14,7 @@ struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, void json_add_payment(struct json_stream *s, const struct payment *payment); -void json_add_route(struct json_stream *s, const struct route *route); +void json_add_route(struct json_stream *s, const struct route *route, + const struct payment *payment); #endif /* LIGHTNING_PLUGINS_RENEPAY_JSON_H */ diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c index 468a1ab16cb1..95f35f3ccf56 100644 --- a/plugins/renepay/mods.c +++ b/plugins/renepay/mods.c @@ -222,9 +222,10 @@ static struct command_result *previous_sendpays_done(struct command *cmd, * succeed the payment, and when they fail we need to * substract from the total. */ - struct route *r = new_route( - pending_routes, payment, groupid, partid, - payment->payment_hash, this_msat, this_sent); + struct route *r = + new_route(pending_routes, groupid, partid, + payment->payment_info.payment_hash, + this_msat, this_sent); assert(r); tal_arr_expand(&pending_routes, r); } else @@ -234,8 +235,8 @@ static struct command_result *previous_sendpays_done(struct command *cmd, if (complete_groupid != INVALID_ID) { /* There are completed sendpays, we don't need to do anything * but summarize the result. */ - payment->start_time.ts.tv_sec = complete_created_at; - payment->start_time.ts.tv_nsec = 0; + payment->payment_info.start_time.ts.tv_sec = complete_created_at; + payment->payment_info.start_time.ts.tv_nsec = 0; payment->total_delivering = complete_msat; payment->total_sent = complete_sent; payment->next_partid = complete_parts + 1; @@ -261,7 +262,7 @@ static struct command_result *previous_sendpays_done(struct command *cmd, max_pending_partid); if (amount_msat_greater_eq(payment->total_delivering, - payment->amount)) { + payment->payment_info.amount)) { /* Pending payment already pays the full amount, we * better stop. */ return payment_fail( @@ -271,7 +272,8 @@ static struct command_result *previous_sendpays_done(struct command *cmd, } for (size_t j = 0; j < tal_count(pending_routes); j++) { - route_pending_register(pending_routes[j]); + route_pending_register(payment->routetracker, + pending_routes[j]); } } else { @@ -295,7 +297,8 @@ static struct command_result *previous_sendpays_cb(struct payment *payment) cmd->plugin, cmd, "listsendpays", previous_sendpays_done, payment_rpc_failure, payment); - json_add_sha256(req->js, "payment_hash", &payment->payment_hash); + json_add_sha256(req->js, "payment_hash", + &payment->payment_info.payment_hash); return send_outreq(cmd->plugin, req); } @@ -330,8 +333,10 @@ static struct command_result *selfpay_success(struct command *cmd, const jsmntok_t *tok, struct route *route) { - struct payment *payment = route->payment; + struct payment *payment = + payment_map_get(pay_plugin->payment_map, route->key.payment_hash); assert(payment); + struct preimage preimage; const char *err; err = json_scan(tmpctx, buf, tok, "{payment_preimage:%}", @@ -350,7 +355,8 @@ static struct command_result *selfpay_failure(struct command *cmd, const jsmntok_t *tok, struct route *route) { - struct payment *payment = route->payment; + struct payment *payment = + payment_map_get(pay_plugin->payment_map, route->key.payment_hash); assert(payment); struct payment_result *result = tal_sendpay_result_from_json(tmpctx, buf, tok); if (result == NULL) @@ -363,7 +369,8 @@ static struct command_result *selfpay_failure(struct command *cmd, static struct command_result *selfpay_cb(struct payment *payment) { - if (!node_id_eq(&pay_plugin->my_id, &payment->destination)) { + if (!node_id_eq(&pay_plugin->my_id, + &payment->payment_info.destination)) { return payment_continue(payment); } @@ -372,14 +379,16 @@ static struct command_result *selfpay_cb(struct payment *payment) plugin_err(pay_plugin->plugin, "Selfpay: cannot get a valid cmd."); - struct route *route = new_route(payment, payment, payment->groupid, - /*partid=*/0, payment->payment_hash, - payment->amount, payment->amount); + struct payment_info *pinfo = &payment->payment_info; + struct route *route = + new_route(payment, payment->groupid, + /*partid=*/0, pinfo->payment_hash, + pinfo->amount, pinfo->amount); struct out_req *req; req = jsonrpc_request_start(cmd->plugin, cmd, "sendpay", selfpay_success, selfpay_failure, route); route->hops = tal_arr(route, struct route_hop, 0); - json_add_route(req->js, route); + json_add_route(req->js, route, payment); return send_outreq(cmd->plugin, req); } @@ -640,13 +649,16 @@ static struct command_result *routehints_done(struct command *cmd UNUSED, // FIXME are there route hints for B12? assert(payment); assert(payment->local_gossmods); - assert(payment->routehints); - const size_t nhints = tal_count(payment->routehints); + + const struct node_id *destination = &payment->payment_info.destination; + const struct route_info **routehints = payment->payment_info.routehints; + assert(routehints); + const size_t nhints = tal_count(routehints); /* Hints are added to the local_gossmods. */ for (size_t i = 0; i < nhints; i++) { /* Each one, presumably, leads to the destination */ - const struct route_info *r = payment->routehints[i]; - const struct node_id *end = &payment->destination; + const struct route_info *r = routehints[i]; + const struct node_id *end = destination; for (int j = tal_count(r) - 1; j >= 0; j--) { add_hintchan(payment, &r[j].pubkey, end, @@ -697,7 +709,8 @@ compute_routes_done(struct command *cmd UNUSED, const char *buf UNUSED, struct amount_msat feebudget, fees_spent, remaining; /* Total feebudget */ - if (!amount_msat_sub(&feebudget, payment->maxspend, payment->amount)) + if (!amount_msat_sub(&feebudget, payment->payment_info.maxspend, + payment->payment_info.amount)) plugin_err(pay_plugin->plugin, "%s: fee budget is negative?", __PRETTY_FUNCTION__); @@ -713,7 +726,7 @@ compute_routes_done(struct command *cmd UNUSED, const char *buf UNUSED, feebudget = AMOUNT_MSAT(0); /* How much are we still trying to send? */ - if (!amount_msat_sub(&remaining, payment->amount, + if (!amount_msat_sub(&remaining, payment->payment_info.amount, payment->total_delivering)) plugin_err(pay_plugin->plugin, "%s: total_delivering is greater than amount?", @@ -742,15 +755,18 @@ compute_routes_done(struct command *cmd UNUSED, const char *buf UNUSED, payment->routes_computed = get_routes( payment, - payment, + &payment->payment_info, &pay_plugin->my_id, - &payment->destination, + &payment->payment_info.destination, pay_plugin->gossmap, pay_plugin->uncertainty, + payment->disabledmap, remaining, - payment->final_cltv, feebudget, + &payment->next_partid, + payment->groupid, + &errcode, &err_msg); @@ -793,7 +809,7 @@ static struct command_result *send_routes_done(struct command *cmd, for (size_t i = 0; i < tal_count(payment->routes_computed); i++) { struct route *route = payment->routes_computed[i]; - route_sendpay_request(cmd, route); + route_sendpay_request(cmd, route, payment); payment_note(payment, LOG_INFORM, "Sent route request: partid=%" PRIu64 @@ -880,7 +896,7 @@ collect_results_done(struct command *cmd UNUSED, const char *buf UNUSED, /* If we have the preimate that means one succeed, we * inmediately finish the payment. */ if (!amount_msat_greater_eq(payment->total_delivering, - payment->amount)) { + payment->payment_info.amount)) { plugin_err( pay_plugin->plugin, "%s: received a success sendpay for this " @@ -888,7 +904,8 @@ collect_results_done(struct command *cmd UNUSED, const char *buf UNUSED, "is less than the payment amount %s.", __PRETTY_FUNCTION__, fmt_amount_msat(tmpctx, payment->total_delivering), - fmt_amount_msat(tmpctx, payment->amount)); + fmt_amount_msat(tmpctx, + payment->payment_info.amount)); } return payment_success(payment, take(payment_preimage)); } @@ -899,7 +916,7 @@ collect_results_done(struct command *cmd UNUSED, const char *buf UNUSED, } if (amount_msat_greater_eq(payment->total_delivering, - payment->amount)) { + payment->payment_info.amount)) { /* There are no succeeds but we are still pending delivering the * entire payment. We still need to collect more results. */ payment->have_results = false; @@ -973,7 +990,7 @@ static struct command_result *checktimeout_done(struct command *cmd UNUSED, const jsmntok_t *result UNUSED, struct payment *payment) { - if (time_after(time_now(), payment->stop_time)) { + if (time_after(time_now(), payment->payment_info.stop_time)) { return payment_fail(payment, PAY_STOPPED_RETRYING, "Timed out"); } return payment_continue(payment); diff --git a/plugins/renepay/payment.c b/plugins/renepay/payment.c index 7ade4e74127c..b06ce44de9ab 100644 --- a/plugins/renepay/payment.c +++ b/plugins/renepay/payment.c @@ -35,51 +35,52 @@ struct payment *payment_new( bool use_shadow) { struct payment *p = tal(ctx, struct payment); + struct payment_info *pinfo = &p->payment_info; /* === Unique properties === */ assert(payment_hash); - p->payment_hash = *payment_hash; + pinfo->payment_hash = *payment_hash; assert(invstr); - p->invstr = tal_strdup(p, invstr); + pinfo->invstr = tal_strdup(p, invstr); - p->label = tal_strdup_or_null(p, label); - p->description = tal_strdup_or_null(p, description); - p->payment_secret = tal_dup_or_null(p, struct secret, payment_secret); - p->payment_metadata = tal_dup_talarr(p, u8, payment_metadata); + pinfo->label = tal_strdup_or_null(p, label); + pinfo->description = tal_strdup_or_null(p, description); + pinfo->payment_secret = tal_dup_or_null(p, struct secret, payment_secret); + pinfo->payment_metadata = tal_dup_talarr(p, u8, payment_metadata); if (taken(routehints)) - p->routehints = tal_steal(p, routehints); + pinfo->routehints = tal_steal(p, routehints); else { /* Deep copy */ - p->routehints = + pinfo->routehints = tal_dup_talarr(p, const struct route_info *, routehints); - for (size_t i = 0; i < tal_count(p->routehints); i++) - p->routehints[i] = - tal_steal(p->routehints, p->routehints[i]); + for (size_t i = 0; i < tal_count(pinfo->routehints); i++) + pinfo->routehints[i] = + tal_steal(pinfo->routehints, pinfo->routehints[i]); } assert(destination); - p->destination = *destination; - p->amount = amount; + pinfo->destination = *destination; + pinfo->amount = amount; /* === Payment attempt parameters === */ - if (!amount_msat_add(&p->maxspend, amount, maxfee)) - p->maxspend = AMOUNT_MSAT(UINT64_MAX); - p->maxdelay = maxdelay; + if (!amount_msat_add(&pinfo->maxspend, amount, maxfee)) + pinfo->maxspend = AMOUNT_MSAT(UINT64_MAX); + pinfo->maxdelay = maxdelay; - p->start_time = time_now(); - p->stop_time = timeabs_add(p->start_time, time_from_sec(retryfor)); + pinfo->start_time = time_now(); + pinfo->stop_time = timeabs_add(pinfo->start_time, time_from_sec(retryfor)); - p->final_cltv = final_cltv; + pinfo->final_cltv = final_cltv; /* === Developer options === */ - p->base_fee_penalty = base_fee_penalty_millionths / 1e6; - p->prob_cost_factor = prob_cost_factor_millionths / 1e6; - p->delay_feefactor = riskfactor_millionths / 1e6; - p->min_prob_success = min_prob_success_millionths / 1e6; - p->use_shadow = use_shadow; + pinfo->base_fee_penalty = base_fee_penalty_millionths / 1e6; + pinfo->prob_cost_factor = prob_cost_factor_millionths / 1e6; + pinfo->delay_feefactor = riskfactor_millionths / 1e6; + pinfo->min_prob_success = min_prob_success_millionths / 1e6; + pinfo->use_shadow = use_shadow; /* === Public State === */ @@ -105,7 +106,7 @@ struct payment *payment_new( p->waitresult_timer = NULL; p->routes_computed = NULL; - p->routetracker = new_routetracker(p); + p->routetracker = new_routetracker(p, p); return p; } @@ -141,26 +142,27 @@ bool payment_update( bool use_shadow) { assert(p); + struct payment_info *pinfo = &p->payment_info; /* === Unique properties === */ // unchanged /* === Payment attempt parameters === */ - if (!amount_msat_add(&p->maxspend, p->amount, maxfee)) - p->maxspend = AMOUNT_MSAT(UINT64_MAX); - p->maxdelay = maxdelay; + if (!amount_msat_add(&pinfo->maxspend, pinfo->amount, maxfee)) + pinfo->maxspend = AMOUNT_MSAT(UINT64_MAX); + pinfo->maxdelay = maxdelay; - p->start_time = time_now(); - p->stop_time = timeabs_add(p->start_time, time_from_sec(retryfor)); + pinfo->start_time = time_now(); + pinfo->stop_time = timeabs_add(pinfo->start_time, time_from_sec(retryfor)); - p->final_cltv = final_cltv; + pinfo->final_cltv = final_cltv; /* === Developer options === */ - p->base_fee_penalty = base_fee_penalty_millionths / 1e6; - p->prob_cost_factor = prob_cost_factor_millionths / 1e6; - p->delay_feefactor = riskfactor_millionths / 1e6; - p->min_prob_success = min_prob_success_millionths / 1e6; - p->use_shadow = use_shadow; + pinfo->base_fee_penalty = base_fee_penalty_millionths / 1e6; + pinfo->prob_cost_factor = prob_cost_factor_millionths / 1e6; + pinfo->delay_feefactor = riskfactor_millionths / 1e6; + pinfo->min_prob_success = min_prob_success_millionths / 1e6; + pinfo->use_shadow = use_shadow; /* === Public State === */ @@ -218,7 +220,7 @@ struct amount_msat payment_delivered(const struct payment *p) struct amount_msat payment_amount(const struct payment *p) { assert(p); - return p->amount; + return p->payment_info.amount; } struct amount_msat payment_fees(const struct payment *p) diff --git a/plugins/renepay/payment.h b/plugins/renepay/payment.h index cda65d6a6c8f..ff22d20b0e7b 100644 --- a/plugins/renepay/payment.h +++ b/plugins/renepay/payment.h @@ -4,6 +4,7 @@ #include #include #include +#include enum payment_status { PAYMENT_PENDING, PAYMENT_SUCCESS, PAYMENT_FAIL }; @@ -14,80 +15,10 @@ struct payment { // TODO: probably not necessary after we store all payments in a // hashtable instead of a list struct list_node list; + + struct payment_info payment_info; - - /* === Unique properties === */ - - /* payment_hash is unique */ - struct sha256 payment_hash; - - /* invstring (bolt11 or bolt12) */ - const char *invstr; - - /* Description and labels, if any. */ - const char *description, *label; - - /* payment_secret, if specified by invoice. */ - struct secret *payment_secret; - - /* Payment metadata, if specified by invoice. */ - const u8 *payment_metadata; - - /* Extracted routehints */ - const struct route_info **routehints; - - /* How much, what, where */ - struct node_id destination; - struct amount_msat amount; - - /* === Payment attempt parameters === */ - - /* Limits on what routes we'll accept. */ - struct amount_msat maxspend; - - /* Max accepted HTLC delay.*/ - unsigned int maxdelay; - - /* TODO new feature: Maximum number of hops */ - // see common/gossip_constants.h:8:#define ROUTING_MAX_HOPS 20 - // int max_num_hops; - - /* We promised this in pay() output */ - struct timeabs start_time; - - /* We stop trying after this time is reached. */ - struct timeabs stop_time; - - u32 final_cltv; - - - /* === Developer options === */ - - /* Penalty for base fee */ - double base_fee_penalty; - - /* Conversion from prob. cost to millionths */ - double prob_cost_factor; - /* prob. cost = - prob_cost_factor * log prob. */ - - /* Penalty for CLTV delays */ - double delay_feefactor; - - /* With these the effective linear fee cost is computed as - * - * linear fee cost = - * millionths - * + base_fee* base_fee_penalty - * +delay*delay_feefactor; - * */ - - /* The minimum acceptable prob. of success */ - double min_prob_success; - - /* --developer allows disabling shadow route */ - bool use_shadow; - - + /* === Public State === */ /* TODO: these properties should be private and only changed through * payment_ methods. */ @@ -146,7 +77,7 @@ struct payment { static inline const struct sha256 payment_hash(const struct payment *p) { - return p->payment_hash; + return p->payment_info.payment_hash; } static inline size_t payment_hash64(const struct sha256 h) @@ -157,14 +88,14 @@ static inline size_t payment_hash64(const struct sha256 h) static inline bool payment_hash_eq(const struct payment *p, const struct sha256 h) { - return p->payment_hash.u.u32[0] == h.u.u32[0] && - p->payment_hash.u.u32[1] == h.u.u32[1] && - p->payment_hash.u.u32[2] == h.u.u32[2] && - p->payment_hash.u.u32[3] == h.u.u32[3] && - p->payment_hash.u.u32[4] == h.u.u32[4] && - p->payment_hash.u.u32[5] == h.u.u32[5] && - p->payment_hash.u.u32[6] == h.u.u32[6] && - p->payment_hash.u.u32[7] == h.u.u32[7]; + return p->payment_info.payment_hash.u.u32[0] == h.u.u32[0] && + p->payment_info.payment_hash.u.u32[1] == h.u.u32[1] && + p->payment_info.payment_hash.u.u32[2] == h.u.u32[2] && + p->payment_info.payment_hash.u.u32[3] == h.u.u32[3] && + p->payment_info.payment_hash.u.u32[4] == h.u.u32[4] && + p->payment_info.payment_hash.u.u32[5] == h.u.u32[5] && + p->payment_info.payment_hash.u.u32[6] == h.u.u32[6] && + p->payment_info.payment_hash.u.u32[7] == h.u.u32[7]; } HTABLE_DEFINE_TYPE(struct payment, payment_hash, payment_hash64, diff --git a/plugins/renepay/payment_info.h b/plugins/renepay/payment_info.h new file mode 100644 index 000000000000..1660acb01bb3 --- /dev/null +++ b/plugins/renepay/payment_info.h @@ -0,0 +1,82 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_PAYMENT_INFO_H +#define LIGHTNING_PLUGINS_RENEPAY_PAYMENT_INFO_H + +/* Plain data payment information. */ + +#include "config.h" +#include +#include +#include +#include + +struct payment_info { + /* payment_hash is unique */ + struct sha256 payment_hash; + + /* invstring (bolt11 or bolt12) */ + const char *invstr; + + /* Description and labels, if any. */ + const char *description, *label; + + /* payment_secret, if specified by invoice. */ + struct secret *payment_secret; + + /* Payment metadata, if specified by invoice. */ + const u8 *payment_metadata; + + /* Extracted routehints */ + const struct route_info **routehints; + + /* How much, what, where */ + struct node_id destination; + struct amount_msat amount; + + /* === Payment attempt parameters === */ + + /* Limits on what routes we'll accept. */ + struct amount_msat maxspend; + + /* Max accepted HTLC delay.*/ + unsigned int maxdelay; + + /* TODO new feature: Maximum number of hops */ + // see common/gossip_constants.h:8:#define ROUTING_MAX_HOPS 20 + // int max_num_hops; + + /* We promised this in pay() output */ + struct timeabs start_time; + + /* We stop trying after this time is reached. */ + struct timeabs stop_time; + + u32 final_cltv; + + /* === Developer options === */ + + /* Penalty for base fee */ + double base_fee_penalty; + + /* Conversion from prob. cost to millionths */ + double prob_cost_factor; + /* prob. cost = - prob_cost_factor * log prob. */ + + /* Penalty for CLTV delays */ + double delay_feefactor; + + /* With these the effective linear fee cost is computed as + * + * linear fee cost = + * millionths + * + base_fee* base_fee_penalty + * +delay*delay_feefactor; + * */ + + /* The minimum acceptable prob. of success */ + double min_prob_success; + + /* --developer allows disabling shadow route */ + bool use_shadow; +}; + +#endif /* LIGHTNING_PLUGINS_RENEPAY_PAYMENT_INFO_H */ diff --git a/plugins/renepay/route.c b/plugins/renepay/route.c index 986a5ef5d1f9..0e3c4589f280 100644 --- a/plugins/renepay/route.c +++ b/plugins/renepay/route.c @@ -1,13 +1,12 @@ #include "config.h" #include -struct route *new_route(const tal_t *ctx, struct payment *payment, u32 groupid, +struct route *new_route(const tal_t *ctx, u32 groupid, u32 partid, struct sha256 payment_hash, struct amount_msat amount, struct amount_msat amount_sent) { struct route *route = tal(ctx, struct route); - route->payment = payment; route->key.partid = partid; route->key.groupid = groupid; route->key.payment_hash = payment_hash; @@ -26,18 +25,17 @@ struct route *new_route(const tal_t *ctx, struct payment *payment, u32 groupid, /* Construct a route from a flow. * * @ctx: allocator - * @payment: NULL or the payment this route will point to * @groupid, @partid, @payment_hash: unique identification keys for this route * @final_cltv: final delay required by the payment * @gossmap: global gossmap * @flow: the flow to convert to route */ -struct route *flow_to_route(const tal_t *ctx, struct payment *payment, +struct route *flow_to_route(const tal_t *ctx, u32 groupid, u32 partid, struct sha256 payment_hash, u32 final_cltv, struct gossmap *gossmap, struct flow *flow) { struct route *route = - new_route(ctx, payment, groupid, partid, payment_hash, + new_route(ctx, groupid, partid, payment_hash, AMOUNT_MSAT(0), AMOUNT_MSAT(0)); size_t pathlen = tal_count(flow->path); @@ -75,7 +73,7 @@ struct route *flow_to_route(const tal_t *ctx, struct payment *payment, return tal_free(route); } -struct route **flows_to_routes(const tal_t *ctx, struct payment *payment, +struct route **flows_to_routes(const tal_t *ctx, u32 groupid, u32 partid, struct sha256 payment_hash, u32 final_cltv, struct gossmap *gossmap, struct flow **flows) @@ -86,7 +84,7 @@ struct route **flows_to_routes(const tal_t *ctx, struct payment *payment, struct route **routes = tal_arr(ctx, struct route *, N); for (size_t i = 0; i < N; i++) { routes[i] = - flow_to_route(routes, payment, groupid, partid++, + flow_to_route(routes, groupid, partid++, payment_hash, final_cltv, gossmap, flows[i]); if (!routes[i]) goto function_fail; diff --git a/plugins/renepay/route.h b/plugins/renepay/route.h index 36ae5b2de4dd..52a80d7c2165 100644 --- a/plugins/renepay/route.h +++ b/plugins/renepay/route.h @@ -49,9 +49,6 @@ struct route { enum jsonrpc_errcode final_error; const char *final_msg; - /* So we can be an independent object for callbacks. */ - struct payment *payment; - /* Information to link this flow to a unique sendpay. */ struct routekey { struct sha256 payment_hash; @@ -112,17 +109,17 @@ static inline bool routekey_equal(const struct route *route, HTABLE_DEFINE_TYPE(struct route, route_get_key, routekey_hash, routekey_equal, route_map); -struct route *new_route(const tal_t *ctx, struct payment *payment, u32 groupid, +struct route *new_route(const tal_t *ctx, u32 groupid, u32 partid, struct sha256 payment_hash, struct amount_msat amount, struct amount_msat amount_sent); -struct route *flow_to_route(const tal_t *ctx, struct payment *payment, +struct route *flow_to_route(const tal_t *ctx, u32 groupid, u32 partid, struct sha256 payment_hash, u32 final_cltv, struct gossmap *gossmap, struct flow *flow); -struct route **flows_to_routes(const tal_t *ctx, struct payment *payment, +struct route **flows_to_routes(const tal_t *ctx, u32 groupid, u32 partid, struct sha256 payment_hash, u32 final_cltv, struct gossmap *gossmap, struct flow **flows); diff --git a/plugins/renepay/routebuilder.c b/plugins/renepay/routebuilder.c index 60c2cf2cb419..1d550a67d89a 100644 --- a/plugins/renepay/routebuilder.c +++ b/plugins/renepay/routebuilder.c @@ -3,56 +3,7 @@ #include #include -static bitmap * -make_disabled_channels_bitmap(const tal_t *ctx, const struct gossmap *gossmap, - const struct chan_extra_map *chan_extra_map, - const struct short_channel_id *disabled_scids) -{ - bitmap *disabled = - tal_arrz(ctx, bitmap, BITMAP_NWORDS(gossmap_max_chan_idx(gossmap))); - if (!disabled) - return NULL; - - /* Disable every channel in the list of disabled scids. */ - for (size_t i = 0; i < tal_count(disabled_scids); i++) { - struct gossmap_chan *c = - gossmap_find_chan(gossmap, &disabled_scids[i]); - if (c) - bitmap_set_bit(disabled, gossmap_chan_idx(gossmap, c)); - } - /* Also disable every channel that we don't have in the chan_extra_map. - */ - for (struct gossmap_chan *chan = gossmap_first_chan(gossmap); chan; - chan = gossmap_next_chan(gossmap, chan)) { - const u32 chan_id = gossmap_chan_idx(gossmap, chan); - struct short_channel_id scid = gossmap_chan_scid(gossmap, chan); - struct chan_extra *ce = - chan_extra_map_get(chan_extra_map, scid); - if (!ce) - bitmap_set_bit(disabled, chan_id); - } - return disabled; -} - -/* Disable all channels that lead to a disabled node. */ -static bool make_disabled_nodes(const struct gossmap *gossmap, - const struct node_id *disabled_nodes, - bitmap *disabled) -{ - /* Disable every channel in the list of disabled scids. */ - for (size_t i = 0; i < tal_count(disabled_nodes); i++) { - const struct gossmap_node *node = - gossmap_find_node(gossmap, &disabled_nodes[i]); - - for (size_t j = 0; j < node->num_chans; j++) { - int half; - const struct gossmap_chan *c = - gossmap_nth_chan(gossmap, node, j, &half); - bitmap_set_bit(disabled, gossmap_chan_idx(gossmap, c)); - } - } - return true; -} +#include // static void uncertainty_commit_routes(struct uncertainty *uncertainty, // struct route **routes) @@ -69,23 +20,13 @@ static void uncertainty_remove_routes(struct uncertainty *uncertainty, uncertainty_remove_htlcs(uncertainty, routes[i]); } -static void mark_chan_disabled(const struct gossmap_chan *chan, - struct short_channel_id **disabled_scids, - bitmap *disabled_bitmap, - struct gossmap *gossmap) -{ - struct short_channel_id scid = gossmap_chan_scid(gossmap, chan); - tal_arr_expand(disabled_scids, scid); - bitmap_set_bit(disabled_bitmap, gossmap_chan_idx(gossmap, chan)); -} - // TODO: check /* Shave-off amounts that do not meet the liquidity constraints. Disable * channels that produce an htlc_max bottleneck. */ static struct flow **flows_adjust_htlcmax_constraints( const tal_t *ctx, struct flow **flows TAKES, struct gossmap *gossmap, struct chan_extra_map *chan_extra_map, - struct short_channel_id **disabled_scids, bitmap *disabled_bitmap) + bitmap *disabled_bitmap) { struct flow **new_flows = tal_arr(ctx, struct flow *, 0); enum renepay_errorcode errorcode; @@ -106,8 +47,9 @@ static struct flow **flows_adjust_htlcmax_constraints( tal_arr_expand(&new_flows, f); } else if (errorcode == RENEPAY_BAD_CHANNEL) { // this is a channel that we can disable - mark_chan_disabled(bad_channel, disabled_scids, - disabled_bitmap, gossmap); + // FIXME: log this error? + bitmap_set_bit(disabled_bitmap, + gossmap_chan_idx(gossmap, bad_channel)); continue; } else { // we had an unexpected error @@ -134,7 +76,7 @@ static struct flow **flows_adjust_htlcmax_constraints( static struct flow **flows_adjust_htlcmin_constraints( const tal_t *ctx, struct flow **flows TAKES, struct gossmap *gossmap, struct chan_extra_map *chan_extra_map, - struct short_channel_id **disabled_scids, bitmap *disabled_bitmap) + bitmap *disabled_bitmap) { struct flow **new_flows = tal_arr(ctx, struct flow *, 0); enum renepay_errorcode errorcode; @@ -155,8 +97,9 @@ static struct flow **flows_adjust_htlcmin_constraints( tal_arr_expand(&new_flows, f); } else if (errorcode == RENEPAY_BAD_CHANNEL) { // this is a channel that we can disable - mark_chan_disabled(bad_channel, disabled_scids, - disabled_bitmap, gossmap); + // FIXME: log this error? + bitmap_set_bit(disabled_bitmap, + gossmap_chan_idx(gossmap, bad_channel)); continue; } else { // we had an unexpected error @@ -179,16 +122,23 @@ static struct flow **flows_adjust_htlcmin_constraints( } /* Routes are computed and saved in the payment for later use. */ -struct route **get_routes(const tal_t *ctx, struct payment *payment, +struct route **get_routes(const tal_t *ctx, + struct payment_info *payment_info, const struct node_id *source, const struct node_id *destination, - struct gossmap *gossmap, struct uncertainty *uncertainty, + struct gossmap *gossmap, + struct uncertainty *uncertainty, + struct disabledmap *disabledmap, struct amount_msat amount_to_deliver, - const u32 final_cltv, struct amount_msat feebudget, + struct amount_msat feebudget, + + u64 *next_partid, + u64 groupid, - enum jsonrpc_errcode *ecode, const char **fail) + enum jsonrpc_errcode *ecode, + const char **fail) { assert(gossmap); assert(uncertainty); @@ -196,17 +146,14 @@ struct route **get_routes(const tal_t *ctx, struct payment *payment, const tal_t *this_ctx = tal(ctx, tal_t); struct route **routes = tal_arr(ctx, struct route *, 0); - double probability_budget = payment->min_prob_success; - double delay_feefactor = payment->delay_feefactor; - const double base_fee_penalty = payment->base_fee_penalty; - const double prob_cost_factor = payment->prob_cost_factor; - const unsigned int maxdelay = payment->maxdelay; + double probability_budget = payment_info->min_prob_success; + double delay_feefactor = payment_info->delay_feefactor; + const double base_fee_penalty = payment_info->base_fee_penalty; + const double prob_cost_factor = payment_info->prob_cost_factor; + const unsigned int maxdelay = payment_info->maxdelay; - bitmap *disabled_bitmap = make_disabled_channels_bitmap( - this_ctx, gossmap, uncertainty->chan_extra_map, - payment->disabled_scids); - - make_disabled_nodes(gossmap, payment->disabled_nodes, disabled_bitmap); + bitmap *disabled_bitmap = + tal_disabledmap_get_bitmap(this_ctx, disabledmap, gossmap); if (!disabled_bitmap) { if (ecode) @@ -217,6 +164,18 @@ struct route **get_routes(const tal_t *ctx, struct payment *payment, goto function_fail; } + /* Also disable every channel that we don't have in the chan_extra_map. + */ + for (struct gossmap_chan *chan = gossmap_first_chan(gossmap); chan; + chan = gossmap_next_chan(gossmap, chan)) { + const u32 chan_id = gossmap_chan_idx(gossmap, chan); + struct short_channel_id scid = gossmap_chan_scid(gossmap, chan); + struct chan_extra *ce = + chan_extra_map_get(uncertainty->chan_extra_map, scid); + if (!ce) + bitmap_set_bit(disabled_bitmap, chan_id); + } + const struct gossmap_node *src, *dst; src = gossmap_find_node(gossmap, source); if (!src) { @@ -241,13 +200,14 @@ struct route **get_routes(const tal_t *ctx, struct payment *payment, while (!amount_msat_zero(amount_to_deliver)) { + printf("amount to deliver: %s\n", fmt_amount_msat(this_ctx, amount_to_deliver)); + /* TODO: choose an algorithm, could be something like * payment->algorithm, that we set up based on command line * options and that can be changed according to some conditions * met during the payment process, eg. add "select_solver" pay * mod. */ /* TODO: use uncertainty instead of chan_extra */ - /* TODO: shall we add to possibility to blacklist nodes? */ /* Min. Cost Flow algorithm to find optimal flows. */ struct flow **flows = @@ -278,7 +238,7 @@ struct route **get_routes(const tal_t *ctx, struct payment *payment, flows = flows_adjust_htlcmax_constraints( this_ctx, take(flows), gossmap, uncertainty_get_chan_extra_map(uncertainty), - &payment->disabled_scids, disabled_bitmap); + disabled_bitmap); if (!flows) { if (ecode) *ecode = PAY_ROUTE_NOT_FOUND; @@ -294,7 +254,7 @@ struct route **get_routes(const tal_t *ctx, struct payment *payment, flows = flows_adjust_htlcmin_constraints( this_ctx, take(flows), gossmap, uncertainty_get_chan_extra_map(uncertainty), - &payment->disabled_scids, disabled_bitmap); + disabled_bitmap); if (!flows) { if (ecode) *ecode = PAY_ROUTE_NOT_FOUND; @@ -335,7 +295,7 @@ struct route **get_routes(const tal_t *ctx, struct payment *payment, /* Check the CLTV delay */ /* TODO: review this, only flows with non-zero amounts */ - const u64 delay = flows_worst_delay(flows) + final_cltv; + const u64 delay = flows_worst_delay(flows) + payment_info->final_cltv; if (delay > maxdelay) { /* FIXME: What is a sane limit? */ if (delay_feefactor > 1000) { @@ -386,14 +346,14 @@ struct route **get_routes(const tal_t *ctx, struct payment *payment, // TODO check ownership of these routes for (size_t i = 0; i < tal_count(flows); i++) { struct route *r = flow_to_route( - ctx, payment, payment->groupid, - payment->next_partid, payment->payment_hash, - final_cltv, gossmap, flows[i]); + ctx, groupid, + *next_partid, payment_info->payment_hash, + payment_info->final_cltv, gossmap, flows[i]); if (!r) { /* TODO: what could have gone wrong? */ continue; } - payment->next_partid++; + (*next_partid)++; uncertainty_commit_htlcs(uncertainty, r); tal_arr_expand(&routes, r); diff --git a/plugins/renepay/routebuilder.h b/plugins/renepay/routebuilder.h index e7d1e135010c..9dd953dc95fa 100644 --- a/plugins/renepay/routebuilder.h +++ b/plugins/renepay/routebuilder.h @@ -4,19 +4,27 @@ #include "config.h" #include #include -#include +#include +#include #include #include -struct route **get_routes(const tal_t *ctx, struct payment *payment, +struct route **get_routes(const tal_t *ctx, + struct payment_info *payment_info, const struct node_id *source, const struct node_id *destination, - struct gossmap *gossmap, struct uncertainty *uncertainty, + struct gossmap *gossmap, + struct uncertainty *uncertainty, + struct disabledmap *disabledmap, struct amount_msat amount_to_deliver, - const u32 final_cltv, struct amount_msat feebudget, + struct amount_msat feebudget, + + u64 *next_partid, + u64 groupid, - enum jsonrpc_errcode *ecode, const char **fail); + enum jsonrpc_errcode *ecode, + const char **fail); #endif /* LIGHTNING_PLUGINS_RENEPAY_ROUTEBUILDER_H */ diff --git a/plugins/renepay/routefail.c b/plugins/renepay/routefail.c index ca12d197eb66..76e33fe42e25 100644 --- a/plugins/renepay/routefail.c +++ b/plugins/renepay/routefail.c @@ -15,6 +15,7 @@ enum node_type { struct routefail { struct command *cmd; + struct payment *payment; struct route *route; }; @@ -24,7 +25,18 @@ static struct command_result *handle_failure(struct routefail *r); struct command_result *routefail_start(const tal_t *ctx, struct route *route, struct command *cmd) { + assert(route); struct routefail *r = tal(ctx, struct routefail); + struct payment *payment = + payment_map_get(pay_plugin->payment_map, route->key.payment_hash); + + if (payment == NULL) + plugin_err(pay_plugin->plugin, + "%s: payment with hash %s not found.", + __PRETTY_FUNCTION__, + fmt_sha256(tmpctx, &route->key.payment_hash)); + + r->payment = payment; r->route = route; r->cmd = cmd; assert(route->result); @@ -36,7 +48,7 @@ static struct command_result *routefail_end(struct routefail *r) /* Notify the tracker that route has failed and routefail have completed * handling all possible errors cases. */ struct command *cmd = r->cmd; - route_failure_register(r->route); + route_failure_register(r->payment->routetracker, r->route); tal_free(r); return notification_handled(cmd); } @@ -109,13 +121,16 @@ static struct command_result *update_gossip_failure(struct command *cmd UNUSED, const jsmntok_t *result, struct routefail *r) { + assert(r); + assert(r->payment); + /* FIXME it might be too strong assumption that erring_channel should * always be present here, but at least the documentation for * waitsendpay says it is present in the case of error. */ assert(r->route->result->erring_channel); payment_disable_chan( - r->route->payment, *r->route->result->erring_channel, LOG_INFORM, + r->payment, *r->route->result->erring_channel, LOG_INFORM, "addgossip failed (%.*s)", json_tok_full_len(result), json_tok_full(buf, result)); return update_gossip_done(cmd, buf, result, r); @@ -198,10 +213,10 @@ static struct command_result *handle_failure(struct routefail *r) assert(route); struct payment_result *result = route->result; assert(result); - struct payment *payment = route->payment; + struct payment *payment = r->payment; assert(payment); - u32 path_len = 0; + int path_len = 0; if (route->hops) path_len = tal_count(route->hops); @@ -248,7 +263,7 @@ static struct command_result *handle_failure(struct routefail *r) /* we disable the next node in the hop */ assert(*result->erring_index < path_len); payment_disable_node( - route->payment, + payment, route->hops[*result->erring_index].node_id, LOG_DBG, "received %s from previous hop", onion_wire_name(result->failcode)); @@ -286,7 +301,7 @@ static struct command_result *handle_failure(struct routefail *r) result->failcode, onion_wire_name(result->failcode)); } else { - payment_disable_node(route->payment, + payment_disable_node(payment, *result->erring_node, LOG_INFORM, "received error %s", onion_wire_name(result->failcode)); @@ -316,7 +331,7 @@ static struct command_result *handle_failure(struct routefail *r) } else { assert(result->erring_channel); payment_disable_chan( - route->payment, *result->erring_channel, LOG_INFORM, + payment, *result->erring_channel, LOG_INFORM, "%s", onion_wire_name(result->failcode)); } break; @@ -335,7 +350,7 @@ static struct command_result *handle_failure(struct routefail *r) result->failcode, onion_wire_name(result->failcode)); - payment_disable_node(route->payment, + payment_disable_node(payment, *result->erring_node, LOG_INFORM, "received error %s", onion_wire_name(result->failcode)); @@ -367,7 +382,7 @@ static struct command_result *handle_failure(struct routefail *r) * information and try again. To avoid hitting this * error again with the same channel we flag it. */ assert(result->erring_channel); - payment_warn_chan(route->payment, + payment_warn_chan(payment, *result->erring_channel, LOG_INFORM, "received error %s", onion_wire_name(result->failcode)); @@ -401,7 +416,13 @@ static struct command_result *handle_failure(struct routefail *r) /* Update the knowledge in the uncertaity network. */ if (route->hops) { - assert(last_good_channel < path_len); + if(last_good_channel >= path_len) + { + plugin_err(pay_plugin->plugin, + "last_good_channel (%d) >= path_len (%d)", + last_good_channel, + path_len); + } /* All channels before the erring node could forward the * payment. */ diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c index 177cfed19c88..565b82fba928 100644 --- a/plugins/renepay/routetracker.c +++ b/plugins/renepay/routetracker.c @@ -6,9 +6,23 @@ #include #include -struct routetracker *new_routetracker(const tal_t *ctx) +static struct payment *route_get_payment_verify(struct route *route) +{ + struct payment *payment = + payment_map_get(pay_plugin->payment_map, route->key.payment_hash); + if (!payment) + plugin_err(pay_plugin->plugin, + "%s: no payment associated with routekey %s", + __PRETTY_FUNCTION__, + fmt_routekey(tmpctx, &route->key)); + return payment; +} + +struct routetracker *new_routetracker(const tal_t *ctx, struct payment *payment) { struct routetracker *rt = tal(ctx, struct routetracker); + + rt->payment = payment; rt->sent_routes = tal(rt, struct route_map); route_map_init(rt->sent_routes); @@ -41,23 +55,25 @@ static void routetracker_add_to_final(struct routetracker *routetracker, tal_arr_expand(&routetracker->finalized_routes, route); tal_steal(routetracker, route); } -static void route_success_register(struct route *route) +static void route_success_register(struct routetracker *routetracker, + struct route *route) { - routetracker_add_to_final(route->payment->routetracker, route); + routetracker_add_to_final(routetracker, route); } -void route_failure_register(struct route *route) +void route_failure_register(struct routetracker *routetracker, + struct route *route) { - routetracker_add_to_final(route->payment->routetracker, route); + routetracker_add_to_final(routetracker, route); } -static void route_sent_register(struct route *route) +static void route_sent_register(struct routetracker *routetracker, + struct route *route) { - struct routetracker *routetracker = route->payment->routetracker; route_map_add(routetracker->sent_routes, route); tal_steal(routetracker, route); } -static void route_sendpay_fail(struct route *route TAKES) +static void route_sendpay_fail(struct routetracker *routetracker, + struct route *route TAKES) { - struct routetracker *routetracker = route->payment->routetracker; if (!route_map_del(routetracker->sent_routes, route)) plugin_log(pay_plugin->plugin, LOG_UNUSUAL, "%s: route (%s) is not marked as sent", @@ -72,14 +88,14 @@ static void route_sendpay_fail(struct route *route TAKES) * - after a sendpay is accepted, * - or after listsendpays reveals some pending route that we didn't * previously know about. */ -void route_pending_register(const struct route *route) +void route_pending_register(struct routetracker *routetracker, + const struct route *route) { assert(route); - struct payment *payment = route->payment; + assert(routetracker); + struct payment *payment = routetracker->payment; assert(payment); assert(payment->groupid == route->key.groupid); - struct routetracker *routetracker = payment->routetracker; - assert(routetracker); /* we already keep track of this route */ if (route_map_get(routetracker->pending_routes, &route->key)) @@ -110,14 +126,15 @@ void route_pending_register(const struct route *route) } } -static void route_result_collected(struct route *route TAKES) +static void route_result_collected(struct routetracker *routetracker, + struct route *route TAKES) { assert(route); + assert(routetracker); assert(route->result); - - assert(route->payment); - struct payment *payment = route->payment; - assert(payment->groupid == route->key.groupid); + + struct payment *payment = routetracker->payment; + assert(payment); if (route->result->status == SENDPAY_FAILED) { if (!amount_msat_sub(&payment->total_delivering, @@ -142,7 +159,8 @@ static struct command_result *sendpay_done(struct command *cmd, struct route *route) { assert(route); - route_pending_register(route); + struct payment *payment = route_get_payment_verify(route); + route_pending_register(payment->routetracker, route); return command_still_pending(cmd); } @@ -156,8 +174,7 @@ static struct command_result *sendpay_failed(struct command *cmd, struct route *route) { assert(route); - assert(route->payment); - struct payment *payment = route->payment; + struct payment *payment = route_get_payment_verify(route); enum jsonrpc_errcode errcode; const char *msg; @@ -187,7 +204,7 @@ static struct command_result *sendpay_failed(struct command *cmd, payment_disable_chan(payment, route->hops[0].scid, LOG_INFORM, "sendpay didn't like first hop: %s", msg); - route_sendpay_fail(take(route)); + route_sendpay_fail(payment->routetracker, take(route)); return command_still_pending(cmd); } @@ -235,21 +252,22 @@ void payment_collect_results(struct payment *payment, tal_strdup(tmpctx, r->final_msg); } } - route_result_collected(take(r)); + route_result_collected(routetracker, take(r)); } tal_resize(&routetracker->finalized_routes, 0); } struct command_result *route_sendpay_request(struct command *cmd, - struct route *route) + struct route *route, + struct payment *payment) { struct out_req *req = jsonrpc_request_start(pay_plugin->plugin, cmd, "sendpay", sendpay_done, sendpay_failed, route); - json_add_route(req->js, route); + json_add_route(req->js, route, payment); - route_sent_register(route); + route_sent_register(payment->routetracker, route); return send_outreq(pay_plugin->plugin, req); } @@ -362,6 +380,6 @@ struct command_result *notification_sendpay_success(struct command *cmd, // FIXME: what happens when several success notification arrive for the // same payment? Even after the payment has been resolved. - route_success_register(route); + route_success_register(payment->routetracker, route); return notification_handled(cmd); } diff --git a/plugins/renepay/routetracker.h b/plugins/renepay/routetracker.h index 840ce75bb745..cf6ca55864d4 100644 --- a/plugins/renepay/routetracker.h +++ b/plugins/renepay/routetracker.h @@ -7,12 +7,13 @@ #include struct routetracker{ + struct payment *payment; struct route_map *sent_routes; struct route_map *pending_routes; struct route **finalized_routes; }; -struct routetracker *new_routetracker(const tal_t *ctx); +struct routetracker *new_routetracker(const tal_t *ctx, struct payment *payment); // bool routetracker_is_ready(const struct routetracker *routetracker); void routetracker_cleanup(struct routetracker *routetracker); size_t routetracker_count_sent(struct routetracker *routetracker); @@ -29,11 +30,13 @@ void payment_collect_results(struct payment *payment, /* Announce that this route is pending and needs to be kept in the waiting list * for notifications. */ -void route_pending_register(const struct route *route); +void route_pending_register(struct routetracker *routetracker, + const struct route *route); /* Sends a sendpay request for this route. */ struct command_result *route_sendpay_request(struct command *cmd, - struct route *route); + struct route *route, + struct payment *payment); struct command_result *notification_sendpay_failure(struct command *cmd, const char *buf, @@ -44,7 +47,8 @@ struct command_result *notification_sendpay_success(struct command *cmd, const jsmntok_t *params); /* Notify the tracker that this route has failed. */ -void route_failure_register(struct route *route); +void route_failure_register(struct routetracker *routetracker, + struct route *route); // FIXME: double-check that we actually get one notification for each sendpay, // ie. that after some time we don't have yet pending sendpays for old failed or From 03f048f7b9e45215479a6188009bc84fe391103c Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 6 May 2024 08:32:09 +0100 Subject: [PATCH 25/31] renepay: bug fix on the routebuilder The route-builder checks the liquidity bounds of each route one at a time. Every route that satisfy the contraints is recorded in the uncertainty network and produces an HTLC burden on the channels it uses, so that the following routes cannot count on the same liquidity twice. --- plugins/renepay/mcf.c | 9 +- plugins/renepay/routebuilder.c | 510 ++++++++++++-------------- plugins/renepay/test/run-bottleneck.c | 361 ++++++++++++++++++ 3 files changed, 600 insertions(+), 280 deletions(-) create mode 100644 plugins/renepay/test/run-bottleneck.c diff --git a/plugins/renepay/mcf.c b/plugins/renepay/mcf.c index 883057d91adb..20c18c4f41b8 100644 --- a/plugins/renepay/mcf.c +++ b/plugins/renepay/mcf.c @@ -1435,15 +1435,8 @@ get_flow_paths(const tal_t *ctx, const struct gossmap *gossmap, goto function_fail; } excess = amount_msat(0); + fp->amount = delivered; - if (!flow_assign_delivery(fp, gossmap, chan_extra_map, - delivered)) { - if (fail) - *fail = - tal_fmt(ctx, "failed to add final " - "amount to flow"); - goto function_fail; - } fp->success_prob = flow_probability(fp, gossmap, chan_extra_map); if (fp->success_prob < 0) { diff --git a/plugins/renepay/routebuilder.c b/plugins/renepay/routebuilder.c index 1d550a67d89a..188ebcde5c6a 100644 --- a/plugins/renepay/routebuilder.c +++ b/plugins/renepay/routebuilder.c @@ -20,105 +20,113 @@ static void uncertainty_remove_routes(struct uncertainty *uncertainty, uncertainty_remove_htlcs(uncertainty, routes[i]); } -// TODO: check /* Shave-off amounts that do not meet the liquidity constraints. Disable * channels that produce an htlc_max bottleneck. */ -static struct flow **flows_adjust_htlcmax_constraints( - const tal_t *ctx, struct flow **flows TAKES, struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - bitmap *disabled_bitmap) +static enum renepay_errorcode +flow_adjust_htlcmax_constraints(struct flow *flow, struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + bitmap *disabled_bitmap) { - struct flow **new_flows = tal_arr(ctx, struct flow *, 0); + assert(flow); + assert(gossmap); + assert(chan_extra_map); + assert(disabled_bitmap); + assert(!amount_msat_zero(flow_delivers(flow))); + enum renepay_errorcode errorcode; - for (size_t i = 0; i < tal_count(flows); i++) { - struct flow *f = flows[i]; - struct amount_msat max_deliverable; - const struct gossmap_chan *bad_channel; + struct amount_msat max_deliverable; + const struct gossmap_chan *bad_channel; - errorcode = flow_maximum_deliverable( - &max_deliverable, f, gossmap, chan_extra_map, &bad_channel); + errorcode = flow_maximum_deliverable(&max_deliverable, flow, gossmap, + chan_extra_map, &bad_channel); - if (!errorcode) { - // no issues - f->amount = - amount_msat_min(flow_delivers(f), max_deliverable); + if (!errorcode) { + assert(!amount_msat_zero(max_deliverable)); - tal_arr_expand(&new_flows, f); - } else if (errorcode == RENEPAY_BAD_CHANNEL) { - // this is a channel that we can disable - // FIXME: log this error? - bitmap_set_bit(disabled_bitmap, - gossmap_chan_idx(gossmap, bad_channel)); - continue; - } else { - // we had an unexpected error - goto function_fail; - } - } + // no issues + flow->amount = + amount_msat_min(flow_delivers(flow), max_deliverable); - for (size_t i = 0; i < tal_count(new_flows); i++) { - tal_steal(new_flows, new_flows[i]); + return errorcode; } - if (taken(flows)) - tal_free(flows); - return new_flows; + if (errorcode == RENEPAY_BAD_CHANNEL) { + // this is a channel that we can disable + // FIXME: log this error? + bitmap_set_bit(disabled_bitmap, + gossmap_chan_idx(gossmap, bad_channel)); + } -function_fail: - if (taken(flows)) - tal_free(flows); - return tal_free(new_flows); + // we had an unexpected error + return errorcode; } -// TODO: check -/* Disable channels that produce an htlc_min bottleneck. */ -static struct flow **flows_adjust_htlcmin_constraints( - const tal_t *ctx, struct flow **flows TAKES, struct gossmap *gossmap, - struct chan_extra_map *chan_extra_map, - bitmap *disabled_bitmap) +static enum renepay_errorcode +route_check_constraints(struct route *route, struct gossmap *gossmap, + struct uncertainty *uncertainty, + bitmap *disabled_bitmap) { - struct flow **new_flows = tal_arr(ctx, struct flow *, 0); - enum renepay_errorcode errorcode; - struct amount_msat max_deliverable; - - for (size_t i = 0; i < tal_count(flows); i++) { - struct flow *f = flows[i]; - const struct gossmap_chan *bad_channel; - - errorcode = flow_maximum_deliverable( - &max_deliverable, f, gossmap, chan_extra_map, &bad_channel); - - if (!errorcode) { - // no issues - f->amount = - amount_msat_min(flow_delivers(f), max_deliverable); + assert(route); + assert(route->hops); + const size_t pathlen = tal_count(route->hops); + if (!amount_msat_eq(route->amount, route->hops[pathlen - 1].amount)) + return RENEPAY_PRECONDITION_ERROR; + if (!amount_msat_eq(route->amount_sent, route->hops[0].amount)) + return RENEPAY_PRECONDITION_ERROR; + + for (size_t i = 0; i < pathlen; i++) { + struct route_hop *hop = &route->hops[i]; + int dir = hop->direction; + struct gossmap_chan *chan = + gossmap_find_chan(gossmap, &hop->scid); + assert(chan); + struct chan_extra *ce = + uncertainty_find_channel(uncertainty, hop->scid); - tal_arr_expand(&new_flows, f); - } else if (errorcode == RENEPAY_BAD_CHANNEL) { - // this is a channel that we can disable - // FIXME: log this error? + // check that we stay within the htlc max and min limits + if (amount_msat_greater(hop->amount, + channel_htlc_max(chan, dir)) || + amount_msat_less(hop->amount, + channel_htlc_min(chan, dir))) { bitmap_set_bit(disabled_bitmap, - gossmap_chan_idx(gossmap, bad_channel)); - continue; - } else { - // we had an unexpected error - goto function_fail; + gossmap_chan_idx(gossmap, chan)); + return RENEPAY_BAD_CHANNEL; } - } - for (size_t i = 0; i < tal_count(new_flows); i++) { - tal_steal(new_flows, new_flows[i]); + // check that the sum of all htlcs and this amount does not + // exceed the maximum known by our knowledge + struct amount_msat total_htlcs = ce->half[dir].htlc_total; + if (!amount_msat_add(&total_htlcs, total_htlcs, hop->amount)) + return RENEPAY_AMOUNT_OVERFLOW; + + if (amount_msat_greater(total_htlcs, ce->half[dir].known_max)) + return RENEPAY_UNEXPECTED; } + return RENEPAY_NOERROR; +} - if (taken(flows)) - tal_free(flows); - return new_flows; +static void tal_report_error(const tal_t *ctx, enum jsonrpc_errcode *ecode, + const char **fail, + enum jsonrpc_errcode error_value, const char *fmt, + ...) +{ + tal_t *this_ctx = tal(ctx, tal_t); -function_fail: - if (taken(flows)) - tal_free(flows); - return tal_free(new_flows); + va_list ap; + const char *str; + + va_start(ap, fmt); + str = tal_vfmt(this_ctx, fmt, ap); + va_end(ap); + + if (ecode) + *ecode = error_value; + + if (fail) + *fail = tal_fmt(ctx, "%s", str); + + this_ctx = tal_free(this_ctx); } /* Routes are computed and saved in the payment for later use. */ @@ -151,16 +159,14 @@ struct route **get_routes(const tal_t *ctx, const double base_fee_penalty = payment_info->base_fee_penalty; const double prob_cost_factor = payment_info->prob_cost_factor; const unsigned int maxdelay = payment_info->maxdelay; + bool delay_feefactor_updated = true; bitmap *disabled_bitmap = tal_disabledmap_get_bitmap(this_ctx, disabledmap, gossmap); if (!disabled_bitmap) { - if (ecode) - *ecode = PLUGIN_ERROR; - if (fail) - *fail = - tal_fmt(ctx, "Failed to build disabled_bitmap."); + tal_report_error(ctx, ecode, fail, PLUGIN_ERROR, + "Failed to build disabled_bitmap."); goto function_fail; } @@ -179,20 +185,15 @@ struct route **get_routes(const tal_t *ctx, const struct gossmap_node *src, *dst; src = gossmap_find_node(gossmap, source); if (!src) { - if (ecode) - *ecode = PAY_ROUTE_NOT_FOUND; - if (fail) - *fail = tal_fmt(ctx, "We don't have any channels."); + tal_report_error(ctx, ecode, fail, PAY_ROUTE_NOT_FOUND, + "We don't have any channels."); goto function_fail; } dst = gossmap_find_node(gossmap, destination); if (!dst) { - if (ecode) - *ecode = PAY_ROUTE_NOT_FOUND; - if (fail) - *fail = tal_fmt( - ctx, - "Destination is unknown in the network gossip."); + tal_report_error( + ctx, ecode, fail, PAY_ROUTE_NOT_FOUND, + "Destination is unknown in the network gossip."); goto function_fail; } @@ -200,8 +201,6 @@ struct route **get_routes(const tal_t *ctx, while (!amount_msat_zero(amount_to_deliver)) { - printf("amount to deliver: %s\n", fmt_amount_msat(this_ctx, amount_to_deliver)); - /* TODO: choose an algorithm, could be something like * payment->algorithm, that we set up based on command line * options and that can be changed according to some conditions @@ -216,218 +215,185 @@ struct route **get_routes(const tal_t *ctx, disabled_bitmap, amount_to_deliver, feebudget, probability_budget, delay_feefactor, base_fee_penalty, prob_cost_factor, &errmsg); + delay_feefactor_updated = false; if (!flows) { - if (ecode) - *ecode = PAY_ROUTE_NOT_FOUND; - - if (fail) - *fail = tal_fmt( - ctx, - "minflow couldn't find a feasible flow: %s", - errmsg); + tal_report_error( + ctx, ecode, fail, PAY_ROUTE_NOT_FOUND, + "minflow couldn't find a feasible flow: %s", + errmsg); goto function_fail; } - /* In previous implementations we would search for - * htlcmax/htlcmin violations and disable those channels and - * then redo the MCF computation. Now we instead remove only - * those flows for which there is a constraint violation and - * mark the involved channels as disabled for the next MCF - * iteration. */ - flows = flows_adjust_htlcmax_constraints( - this_ctx, take(flows), gossmap, - uncertainty_get_chan_extra_map(uncertainty), - disabled_bitmap); - if (!flows) { - if (ecode) - *ecode = PAY_ROUTE_NOT_FOUND; + enum renepay_errorcode errorcode; + for (size_t i = 0; i < tal_count(flows); i++) { - if (fail) - *fail = tal_fmt( - ctx, - "failed to adjust htlcmax constraints."); + // do we overpay? + if (amount_msat_greater(flows[i]->amount, + amount_to_deliver)) { + // should not happen + tal_report_error( + ctx, ecode, fail, PLUGIN_ERROR, + "%s: flow is delivering to destination " + "(%s) more than requested (%s)", + __PRETTY_FUNCTION__, + fmt_amount_msat(this_ctx, flows[i]->amount), + fmt_amount_msat(this_ctx, + amount_to_deliver)); + goto function_fail; + } - goto function_fail; - } + // fees considered, remove the least amount as to fit in + // with the htlcmax constraints + errorcode = flow_adjust_htlcmax_constraints( + flows[i], gossmap, + uncertainty_get_chan_extra_map(uncertainty), + disabled_bitmap); + if (errorcode == RENEPAY_BAD_CHANNEL) + // we handle a bad channel error by disabling + // it, infinite loops are avoided since we have + // everytime less and less channels + continue; + if (errorcode) { + // any other error is bad + tal_report_error( + ctx, ecode, fail, PLUGIN_ERROR, + "flow_adjust_htlcmax_constraints returned " + "errorcode: %s", + renepay_errorcode_name(errorcode)); + goto function_fail; + } - flows = flows_adjust_htlcmin_constraints( - this_ctx, take(flows), gossmap, - uncertainty_get_chan_extra_map(uncertainty), - disabled_bitmap); - if (!flows) { - if (ecode) - *ecode = PAY_ROUTE_NOT_FOUND; + // a bound check, we shouldn't deliver a zero amount, it + // would mean a bug somewhere + if (amount_msat_zero(flows[i]->amount)) { + tal_report_error(ctx, ecode, fail, PLUGIN_ERROR, + "flow conveys a zero amount"); + goto function_fail; + } - if (fail) - *fail = tal_fmt( - ctx, - "failed to adjust htlcmin constraints."); + const double prob = flow_probability( + flows[i], gossmap, + uncertainty_get_chan_extra_map(uncertainty)); + if (prob < 0) { + // should not happen + tal_report_error(ctx, ecode, fail, PLUGIN_ERROR, + "flow_probability failed"); + goto function_fail; + } - goto function_fail; - } - // TODO: check issue #7136 + // this flow seems good, build me a route + struct route *r = flow_to_route( + this_ctx, groupid, *next_partid, + payment_info->payment_hash, + payment_info->final_cltv, gossmap, flows[i]); - /* Check the fee limits. */ - /* TODO: review this, only flows with non-zero amount */ - struct amount_msat fee; - if (!flowset_fee(&fee, flows)) { - if (ecode) - *ecode = PLUGIN_ERROR; + if (!r) { + tal_report_error( + ctx, ecode, fail, PLUGIN_ERROR, + "%s failed to build route from flow.", + __PRETTY_FUNCTION__); + goto function_fail; + } - if (fail) - *fail = tal_fmt(ctx, "flowset_fee failed"); - goto function_fail; - } - if (amount_msat_greater(fee, feebudget)) { - if (ecode) - *ecode = PAY_ROUTE_TOO_EXPENSIVE; + const struct amount_msat fee = route_fees(r); + const struct amount_msat delivering = route_delivers(r); - if (fail) - *fail = tal_fmt( - ctx, + // are we still within the fee budget? + if (amount_msat_greater(fee, feebudget)) { + tal_report_error( + ctx, ecode, fail, PAY_ROUTE_TOO_EXPENSIVE, "Fee exceeds our fee budget, fee=%s " "(feebudget=%s)", fmt_amount_msat(this_ctx, fee), fmt_amount_msat(this_ctx, feebudget)); - goto function_fail; - } + goto function_fail; + } - /* Check the CLTV delay */ - /* TODO: review this, only flows with non-zero amounts */ - const u64 delay = flows_worst_delay(flows) + payment_info->final_cltv; - if (delay > maxdelay) { - /* FIXME: What is a sane limit? */ - if (delay_feefactor > 1000) { - if (ecode) - *ecode = PAY_ROUTE_TOO_EXPENSIVE; - if (fail) - *fail = tal_fmt( - ctx, + // check the CLTV delay does not exceed our settings + const unsigned int delay = route_delay(r); + if (delay > maxdelay) { + if (!delay_feefactor_updated) { + delay_feefactor *= 2; + delay_feefactor_updated = true; + } + + /* FIXME: What is a sane limit? */ + if (delay_feefactor > 1000) { + tal_report_error( + ctx, ecode, fail, + PAY_ROUTE_TOO_EXPENSIVE, "CLTV delay exceeds our CLTV " - "budget, delay=%" PRIu64 - "(maxdelay=%u)", + "budget, delay=%u (maxdelay=%u)", delay, maxdelay); - goto function_fail; + goto function_fail; + } + continue; } - delay_feefactor *= 2; - continue; // retry - } - - /* Compute the flows probability */ - /* TODO: review this, only flows with non-zero amounts */ - double prob = flowset_probability( - this_ctx, flows, gossmap, - uncertainty_get_chan_extra_map(uncertainty), NULL); - if (prob < 0) { - if (ecode) - *ecode = PLUGIN_ERROR; - if (fail) - *fail = - tal_fmt(ctx, "flowset_probability failed"); - goto function_fail; - } - - struct amount_msat delivering; - if (!flowset_delivers(&delivering, flows)) { - if (ecode) - *ecode = PLUGIN_ERROR; + // check that the route satisfy all constraints + errorcode = route_check_constraints( + r, gossmap, uncertainty, disabled_bitmap); - if (fail) - *fail = tal_fmt(ctx, "flowset_delivers failed"); - goto function_fail; - } - - /* OK, we are happy with these flows: convert to - * routes in the current payment. */ - delivering = AMOUNT_MSAT(0); - fee = AMOUNT_MSAT(0); - // TODO check ownership of these routes - for (size_t i = 0; i < tal_count(flows); i++) { - struct route *r = flow_to_route( - ctx, groupid, - *next_partid, payment_info->payment_hash, - payment_info->final_cltv, gossmap, flows[i]); - if (!r) { - /* TODO: what could have gone wrong? */ + if (errorcode == RENEPAY_BAD_CHANNEL) continue; + if (errorcode) { + // any other error is bad + tal_report_error( + ctx, ecode, fail, PLUGIN_ERROR, + "route_check_constraints returned " + "errorcode: %s", + renepay_errorcode_name(errorcode)); + goto function_fail; } - (*next_partid)++; - uncertainty_commit_htlcs(uncertainty, r); - tal_arr_expand(&routes, r); - struct amount_msat route_fee = route_fees(r), - route_deliver = route_delivers(r); - - if (!amount_msat_add(&fee, fee, route_fee) || - !amount_msat_add(&delivering, delivering, - route_deliver)) { - if (ecode) - *ecode = PLUGIN_ERROR; - - if (fail) - *fail = tal_fmt( - ctx, - "%s (line %d) amount_msat " - "arithmetic overflow.", - __PRETTY_FUNCTION__, __LINE__); + // update the fee budget + if (!amount_msat_sub(&feebudget, feebudget, fee)) { + // should never happen + tal_report_error( + ctx, ecode, fail, PLUGIN_ERROR, + "%s routing fees (%s) exceed fee " + "budget (%s).", + __PRETTY_FUNCTION__, + fmt_amount_msat(this_ctx, fee), + fmt_amount_msat(this_ctx, feebudget)); goto function_fail; } - } - /* For the next iteration get me the amount_to_deliver */ - if (!amount_msat_sub(&amount_to_deliver, amount_to_deliver, - delivering)) { - /* In the next iteration we search routes that allocate - *amount_to_deliver - delivering If we have delivering > - *amount_to_deliver it means we have made a mistake - *somewhere. - */ - if (ecode) - *ecode = PLUGIN_ERROR; - - if (fail) - *fail = tal_fmt( - ctx, - "%s (line %d) delivering to destination " - "(%s) is more than requested (%s)", - __PRETTY_FUNCTION__, __LINE__, + // update the amount that we deliver + if (!amount_msat_sub(&amount_to_deliver, + amount_to_deliver, delivering)) { + // should never happen + tal_report_error( + ctx, ecode, fail, PLUGIN_ERROR, + "%s: route delivering to destination (%s) " + "is more than requested (%s)", + __PRETTY_FUNCTION__, fmt_amount_msat(this_ctx, delivering), fmt_amount_msat(this_ctx, amount_to_deliver)); - goto function_fail; - } - - /* For the next iteration get me the feebudget */ - if (!amount_msat_sub(&feebudget, feebudget, fee)) { - if (ecode) - *ecode = PLUGIN_ERROR; + goto function_fail; + } - if (fail) - *fail = tal_fmt( - ctx, - "%s (line %d) routing fees (%s) exceed fee " - "budget (%s).", - __PRETTY_FUNCTION__, __LINE__, - fmt_amount_msat(this_ctx, fee), - fmt_amount_msat(this_ctx, feebudget)); - goto function_fail; - } + // update the probability target + if (prob < 1e-10) { + // probability is too small for division + probability_budget = 1.0; + } else { + /* prob here is a conditional probability, the + * next flow will have a conditional + * probability prob2 and we would like that + * prob*prob2 >= probability_budget hence + * probability_budget/prob becomes the next + * iteration's target. */ + probability_budget = + MIN(1.0, probability_budget / prob); + } - /* For the next iteration get me the probability_budget */ - if (prob < 1e-10) { - /* this last flow probability is too small for division - */ - probability_budget = 1.0; - } else { - /* prob here is a conditional probability, the next - * round of flows will have a conditional probability - * prob2 and we would like that prob*prob2 >= - * probability_budget hence probability_budget/prob - * becomes the next iteration's target. */ - probability_budget = - MIN(1.0, probability_budget / prob); + // route added + (*next_partid)++; + uncertainty_commit_htlcs(uncertainty, r); + tal_arr_expand(&routes, r); } } diff --git a/plugins/renepay/test/run-bottleneck.c b/plugins/renepay/test/run-bottleneck.c new file mode 100644 index 000000000000..500d55804e1c --- /dev/null +++ b/plugins/renepay/test/run-bottleneck.c @@ -0,0 +1,361 @@ +#include "config.h" + +#include "../errorcodes.c" +#include "../flow.c" +#include "../mcf.c" +#include "../uncertainty.c" +#include "../disabledmap.c" +#include "../route.c" +#include "../routebuilder.c" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static u8 empty_map[] = {10}; + +static const char *print_flows(const tal_t *ctx, const char *desc, + const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct flow **flows) +{ + tal_t *this_ctx = tal(ctx, tal_t); + double tot_prob = + flowset_probability(tmpctx, flows, gossmap, chan_extra_map, NULL); + assert(tot_prob >= 0); + char *buff = tal_fmt(ctx, "%s: %zu subflows, prob %2lf\n", desc, + tal_count(flows), tot_prob); + for (size_t i = 0; i < tal_count(flows); i++) { + struct amount_msat fee, delivered; + tal_append_fmt(&buff, " "); + for (size_t j = 0; j < tal_count(flows[i]->path); j++) { + struct short_channel_id scid = + gossmap_chan_scid(gossmap, flows[i]->path[j]); + tal_append_fmt(&buff, "%s%s", j ? "->" : "", + fmt_short_channel_id(this_ctx, scid)); + } + delivered = flows[i]->amount; + if (!flow_fee(&fee, flows[i])) { + abort(); + } + tal_append_fmt(&buff, " prob %.2f, %s delivered with fee %s\n", + flows[i]->success_prob, + fmt_amount_msat(this_ctx, delivered), + fmt_amount_msat(this_ctx, fee)); + } + + tal_free(this_ctx); + return buff; +} + +static const char *print_routes(const tal_t *ctx, + struct route **routes) +{ + tal_t *this_ctx = tal(ctx, tal_t); + char *buff = tal_fmt(ctx, "%zu routes\n", tal_count(routes)); + for (size_t i = 0; i < tal_count(routes); i++) { + struct amount_msat fee, delivered; + + delivered = route_delivers(routes[i]); + fee = route_fees(routes[i]); + tal_append_fmt(&buff, " %s", fmt_route_path(this_ctx, routes[i])); + tal_append_fmt(&buff, " %s delivered with fee %s\n", + fmt_amount_msat(this_ctx, delivered), + fmt_amount_msat(this_ctx, fee)); + } + + tal_free(this_ctx); + return buff; +} + +static void write_to_store(int store_fd, const u8 *msg) +{ + struct gossip_hdr hdr; + + hdr.flags = cpu_to_be16(0); + hdr.len = cpu_to_be16(tal_count(msg)); + /* We don't actually check these! */ + hdr.crc = 0; + hdr.timestamp = 0; + assert(write(store_fd, &hdr, sizeof(hdr)) == sizeof(hdr)); + assert(write(store_fd, msg, tal_count(msg)) == tal_count(msg)); +} + +static void add_connection(int store_fd, + const struct node_id *from, + const struct node_id *to, + struct short_channel_id scid, + struct amount_msat min, + struct amount_msat max, + u32 base_fee, s32 proportional_fee, + u32 delay, + struct amount_sat capacity) +{ + secp256k1_ecdsa_signature dummy_sig; + struct secret not_a_secret; + struct pubkey dummy_key; + u8 *msg; + const struct node_id *ids[2]; + + /* So valgrind doesn't complain */ + memset(&dummy_sig, 0, sizeof(dummy_sig)); + memset(¬_a_secret, 1, sizeof(not_a_secret)); + pubkey_from_secret(¬_a_secret, &dummy_key); + + if (node_id_cmp(from, to) > 0) { + ids[0] = to; + ids[1] = from; + } else { + ids[0] = from; + ids[1] = to; + } + msg = towire_channel_announcement(tmpctx, &dummy_sig, &dummy_sig, + &dummy_sig, &dummy_sig, + /* features */ NULL, + &chainparams->genesis_blockhash, + scid, + ids[0], ids[1], + &dummy_key, &dummy_key); + write_to_store(store_fd, msg); + + msg = towire_gossip_store_channel_amount(tmpctx, capacity); + write_to_store(store_fd, msg); + + u8 flags = node_id_idx(from, to); + + msg = towire_channel_update(tmpctx, + &dummy_sig, + &chainparams->genesis_blockhash, + scid, 0, + ROUTING_OPT_HTLC_MAX_MSAT, + flags, + delay, + min, + base_fee, + proportional_fee, + max); + write_to_store(store_fd, msg); +} + +static void node_id_from_privkey(const struct privkey *p, struct node_id *id) +{ + struct pubkey k; + pubkey_from_privkey(p, &k); + node_id_from_pubkey(id, &k); +} + +#define NUM_NODES 8 + +int main(int argc, char *argv[]) +{ + int fd; + char *gossfile; + struct gossmap *gossmap; + struct node_id nodes[NUM_NODES]; + + common_setup(argv[0]); + chainparams = chainparams_for_network("regtest"); + + fd = tmpdir_mkstemp(tmpctx, "run-bottleneck.XXXXXX", &gossfile); + assert(write(fd, empty_map, sizeof(empty_map)) == sizeof(empty_map)); + + gossmap = gossmap_load(tmpctx, gossfile, NULL); + assert(gossmap); + + for (size_t i = 0; i < NUM_NODES; i++) { + struct privkey tmp; + memset(&tmp, i+1, sizeof(tmp)); + node_id_from_privkey(&tmp, &nodes[i]); + } + + /* We will try a payment from 1 to 8, forcing a payment split between + * two routes 1->2->4->5->6->8 and 1->3->4->5->7->8. + * To force the split the total payment amount will be greater than the + * channel 1-2 and 1-3 capacities. Then channel 4--5 will be a common + * edge in the payment routes. + * + * MCF does not handle fees hence if the capacity of 4--5 is enough to + * let the entire payment pass, we expect that minflow computes two + * routes that are scaled down by get_route algorithm + * to fit for the fee constraints. + * + * +--2--+ +--6--+ + * | | | | + * 1 4---5 8 + * | | | | + * +--3--+ +--7--+ + * + * */ + struct short_channel_id scid; + + assert(mk_short_channel_id(&scid, 1, 2, 0)); + add_connection(fd, &nodes[0], &nodes[1], scid, + AMOUNT_MSAT(0), + AMOUNT_MSAT(60 * 1000 * 1000), + 0, 0, 5, + AMOUNT_SAT(60 * 1000)); + + assert(mk_short_channel_id(&scid, 1, 3, 0)); + add_connection(fd, &nodes[0], &nodes[2], scid, + AMOUNT_MSAT(0), + AMOUNT_MSAT(60 * 1000 * 1000), + 0, 0, 5, + AMOUNT_SAT(60 * 1000)); + + assert(mk_short_channel_id(&scid, 2, 4, 0)); + add_connection(fd, &nodes[1], &nodes[3], scid, + AMOUNT_MSAT(0), + AMOUNT_MSAT(1000 * 1000 * 1000), + 0, 0, 5, + AMOUNT_SAT(1000 * 1000)); + + assert(mk_short_channel_id(&scid, 3, 4, 0)); + add_connection(fd, &nodes[2], &nodes[3], scid, + AMOUNT_MSAT(0), + AMOUNT_MSAT(1000 * 1000 * 1000), + 0, 0, 5, + AMOUNT_SAT(1000 * 1000)); + + assert(mk_short_channel_id(&scid, 4, 5, 0)); + add_connection(fd, &nodes[3], &nodes[4], scid, + AMOUNT_MSAT(0), + /* MCF cuts off at 95% of the conditional capacity, for + * cap = 106k that means only 100.7k sats can be sent + * through this channel. */ + AMOUNT_MSAT(106 * 1000 * 1000), + 0, 0, 5, + AMOUNT_SAT(110 * 1000)); + + assert(mk_short_channel_id(&scid, 5, 6, 0)); + add_connection(fd, &nodes[4], &nodes[5], scid, + AMOUNT_MSAT(0), + AMOUNT_MSAT(1000 * 1000 * 1000), + 0, 100 * 1000 /* 10% */, 5, + AMOUNT_SAT(1000 * 1000)); + + assert(mk_short_channel_id(&scid, 5, 7, 0)); + add_connection(fd, &nodes[4], &nodes[6], scid, + AMOUNT_MSAT(0), + AMOUNT_MSAT(1000 * 1000 * 1000), + 0, 100 * 1000 /* 10% */, 5, + AMOUNT_SAT(1000 * 1000)); + + assert(mk_short_channel_id(&scid, 6, 8, 0)); + add_connection(fd, &nodes[5], &nodes[7], scid, + AMOUNT_MSAT(0), + AMOUNT_MSAT(1000 * 1000 * 1000), + 0, 0, 5, + AMOUNT_SAT(1000 * 1000)); + + assert(mk_short_channel_id(&scid, 7, 8, 0)); + add_connection(fd, &nodes[6], &nodes[7], scid, + AMOUNT_MSAT(0), + AMOUNT_MSAT(1000 * 1000 * 1000), + 0, 0, 5, + AMOUNT_SAT(1000 * 1000)); + + assert(gossmap_refresh(gossmap, NULL)); + struct uncertainty *uncertainty = uncertainty_new(tmpctx); + int skipped_count = + uncertainty_update(uncertainty, gossmap); + assert(skipped_count==0); + + bitmap *disabled = tal_arrz( + tmpctx, bitmap, BITMAP_NWORDS(gossmap_max_chan_idx(gossmap))); + + char *errmsg; + struct flow **flows; + flows = + minflow(tmpctx, gossmap, gossmap_find_node(gossmap, &nodes[0]), + gossmap_find_node(gossmap, &nodes[7]), + uncertainty->chan_extra_map, disabled, + /* Half the capacity */ + AMOUNT_MSAT(100 * 1000 * 1000), + /* max_fee = */ AMOUNT_MSAT(20 * 1000 * 1000), + /* min probability = */ 0.9, + /* delay fee factor = */ 1e-6, + /* base fee penalty */ 10, + /* prob cost factor = */ 10, &errmsg); + + if (!flows) { + printf("Minflow has failed with: %s", errmsg); + // assert(0 && "minflow failed"); + } + + if(flows) + printf("%s\n", print_flows(tmpctx, "Simple minflow", gossmap, + uncertainty->chan_extra_map, flows)); + + struct preimage preimage; + + struct amount_msat maxfee = AMOUNT_MSAT(20*1000*1000); + struct payment_info pinfo; + pinfo.invstr = NULL; + pinfo.label = NULL; + pinfo.description = NULL; + pinfo.payment_secret = NULL; + pinfo.payment_metadata = NULL; + pinfo.routehints = NULL; + pinfo.destination = nodes[7]; + pinfo.amount = AMOUNT_MSAT(100 * 1000 * 1000); + + assert(amount_msat_add(&pinfo.maxspend, maxfee, pinfo.amount)); + pinfo.maxdelay = 100; + pinfo.final_cltv = 5; + + pinfo.start_time = time_now(); + pinfo.stop_time = timeabs_add(pinfo.start_time, time_from_sec(10000)); + + pinfo.base_fee_penalty = 1e-5; + pinfo.prob_cost_factor = 1e-5; + pinfo.delay_feefactor = 1e-6; + pinfo.min_prob_success = 0.9; + pinfo.use_shadow = false; + + randombytes_buf(&preimage, sizeof(preimage)); + sha256(&pinfo.payment_hash, &preimage, sizeof(preimage)); + + // char hex_preimage[600], hex_sha256[600]; + // assert(hex_encode(preimage.r, sizeof(preimage.r), hex_preimage, sizeof(hex_preimage))); + // assert(hex_encode(pinfo.payment_hash.u.u8, sizeof(pinfo.payment_hash), hex_sha256, sizeof(hex_sha256))); + // printf("preimage: %s\npayment_hash: %s\n", hex_preimage, hex_sha256); + + struct disabledmap *disabledmap = disabledmap_new(tmpctx); + + enum jsonrpc_errcode errcode; + const char *err_msg; + + u64 groupid = 1; + u64 next_partid=1; + + struct route **routes = get_routes( + /* ctx */tmpctx, + /* payment */&pinfo, + /* source */&nodes[0], + /* destination */&nodes[7], + /* gossmap */gossmap, + /* uncertainty */uncertainty, + disabledmap, + /* amount */ pinfo.amount, + /* feebudget */maxfee, + &next_partid, + groupid, + &errcode, + &err_msg); + + if (!routes) { + printf("get_route failed with error %d: %s", errcode, err_msg); + } + if(routes) + printf("get_routes: %s\n", print_routes(tmpctx, routes)); + + common_shutdown(); +} From 54be91252588cd5c8444fb0bedc4ebc265ced3d6 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 6 May 2024 09:45:12 +0100 Subject: [PATCH 26/31] renepay: bug fix, local channel information Listpeerchannels would update the local channel information setting the liquidity in the outgoing channel to known_min=known_max=capacity, when in fact it should be known_min=known_max=spendable. --- plugins/renepay/mods.c | 10 +++++++--- tests/plugins/no_fail.py | 28 ++++++++++++++++++++++++++++ tests/test_renepay.py | 12 +++++++++--- 3 files changed, 44 insertions(+), 6 deletions(-) create mode 100755 tests/plugins/no_fail.py diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c index 95f35f3ccf56..a6bbd52badb4 100644 --- a/plugins/renepay/mods.c +++ b/plugins/renepay/mods.c @@ -484,9 +484,13 @@ static void gossmod_cb(struct gossmap_localmods *mods, payment_disable_chan(payment, scidd->scid, LOG_DBG, "listpeerchannels says not enabled"); - /* Also update the uncertainty network */ - uncertainty_update_from_listpeerchannels(pay_plugin->uncertainty, scidd, max, - enabled, buf, chantok); + /* Also update the uncertainty network by fixing the liquidity of the + * outgoing channel. If we try to set the liquidity of the incoming + * channel as well we would have conflicting information because our + * knowledge model does not take into account channel reserves. */ + if (scidd->dir == node_id_idx(self, peer)) + uncertainty_update_from_listpeerchannels( + pay_plugin->uncertainty, scidd, max, enabled, buf, chantok); } static struct command_result *getmychannels_done(struct command *cmd, diff --git a/tests/plugins/no_fail.py b/tests/plugins/no_fail.py new file mode 100755 index 000000000000..d0a1c47b4bd7 --- /dev/null +++ b/tests/plugins/no_fail.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +"""Plugin that breaks the node if a fail notification is received. +""" + +from pyln.client import Plugin, RpcError +import os + +plugin = Plugin() + + +@plugin.init() +def init(plugin, options, configuration): + plugin.log("no_fail initialized") + + +@plugin.subscribe("sendpay_failure") +def channel_opened(plugin, sendpay_failure, **kwargs): + os._exit(1) + + +@plugin.method("nofail") +def nofail(plugin): + """Checks that the plugin is still running. + """ + return {"status": "active"} + + +plugin.run() diff --git a/tests/test_renepay.py b/tests/test_renepay.py index 644451a21c03..031e06ed110d 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -435,11 +435,14 @@ def test_fee_allocation(node_factory): | | 3----4 This a payment that fails if fee is not allocated as part of the flow - constraints. + constraints. The payment should be straightforward, no failures are + expected. """ - # High fees at 3% + # We set high fees at 3% and load a plugin that breaks if a sendpay_failure + # notification is received. opts = [ - {"disable-mpp": None, "fee-base": 1000, "fee-per-satoshi": 30000}, + {"disable-mpp": None, "fee-base": 1000, "fee-per-satoshi": 30000, + 'plugin': os.path.join(os.getcwd(), 'tests/plugins/no_fail.py')}, ] l1, l2, l3, l4 = node_factory.get_nodes(4, opts=opts * 4) start_channels( @@ -452,6 +455,9 @@ def test_fee_allocation(node_factory): invoice = only_one(l4.rpc.listinvoices("inv")["invoices"]) assert invoice["amount_received_msat"] >= Millisatoshi("1500000sat") + # is the no_fail.py plugin still running? + l1.rpc.call("nofail") + def test_htlc_max(node_factory): """ From 76a13324243b9b4e7f49019f17e2ac9cafb5af8a Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 6 May 2024 10:00:03 +0100 Subject: [PATCH 27/31] renepay: update test files --- plugins/renepay/test/Makefile | 2 +- plugins/renepay/test/run-arc.c | 6 ----- plugins/renepay/test/run-bottleneck.c | 2 ++ plugins/renepay/test/run-dijkstra.c | 6 ----- plugins/renepay/test/run-mcf-diamond.c | 29 +++++++++++++++----- plugins/renepay/test/run-mcf.c | 31 +++++++++++++++------ plugins/renepay/test/run-route_map.c | 4 +-- plugins/renepay/test/run-testflow.c | 37 ++++++++++++++++++-------- 8 files changed, 76 insertions(+), 41 deletions(-) diff --git a/plugins/renepay/test/Makefile b/plugins/renepay/test/Makefile index f0f56d03f184..bc7c875b47af 100644 --- a/plugins/renepay/test/Makefile +++ b/plugins/renepay/test/Makefile @@ -12,7 +12,7 @@ PLUGIN_RENEPAY_TEST_COMMON_OBJS := \ plugins/renepay/dijkstra.o \ plugins/renepay/chan_extra.o -$(PLUGIN_RENEPAY_TEST_PROGRAMS): $(PLUGIN_RENEPAY_TEST_COMMON_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) bitcoin/chainparams.o common/gossmap.o common/fp16.o common/dijkstra.o +$(PLUGIN_RENEPAY_TEST_PROGRAMS): $(PLUGIN_RENEPAY_TEST_COMMON_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) bitcoin/chainparams.o common/gossmap.o common/fp16.o common/dijkstra.o gossipd/gossip_store_wiregen.o check-renepay: $(PLUGIN_RENEPAY_TEST_PROGRAMS:%=unittest/%) diff --git a/plugins/renepay/test/run-arc.c b/plugins/renepay/test/run-arc.c index cd5901aa2c39..8806508d97bb 100644 --- a/plugins/renepay/test/run-arc.c +++ b/plugins/renepay/test/run-arc.c @@ -14,12 +14,6 @@ #include "../mcf.c" /* AUTOGENERATED MOCKS START */ -/* Generated stub for fromwire_blinded_path */ -struct blinded_path *fromwire_blinded_path(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *plen UNNEEDED) -{ fprintf(stderr, "fromwire_blinded_path called!\n"); abort(); } -/* Generated stub for towire_blinded_path */ -void towire_blinded_path(u8 **p UNNEEDED, const struct blinded_path *blinded_path UNNEEDED) -{ fprintf(stderr, "towire_blinded_path called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ int main(int argc, char *argv[]) diff --git a/plugins/renepay/test/run-bottleneck.c b/plugins/renepay/test/run-bottleneck.c index 500d55804e1c..585d87d1695e 100644 --- a/plugins/renepay/test/run-bottleneck.c +++ b/plugins/renepay/test/run-bottleneck.c @@ -351,6 +351,8 @@ int main(int argc, char *argv[]) &errcode, &err_msg); + assert(routes); + if (!routes) { printf("get_route failed with error %d: %s", errcode, err_msg); } diff --git a/plugins/renepay/test/run-dijkstra.c b/plugins/renepay/test/run-dijkstra.c index f4d7f2ee2beb..3c43b1188940 100644 --- a/plugins/renepay/test/run-dijkstra.c +++ b/plugins/renepay/test/run-dijkstra.c @@ -11,12 +11,6 @@ #include /* AUTOGENERATED MOCKS START */ -/* Generated stub for fromwire_blinded_path */ -struct blinded_path *fromwire_blinded_path(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *plen UNNEEDED) -{ fprintf(stderr, "fromwire_blinded_path called!\n"); abort(); } -/* Generated stub for towire_blinded_path */ -void towire_blinded_path(u8 **p UNNEEDED, const struct blinded_path *blinded_path UNNEEDED) -{ fprintf(stderr, "towire_blinded_path called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ static void insertion_in_increasing_distance(const tal_t *ctx) diff --git a/plugins/renepay/test/run-mcf-diamond.c b/plugins/renepay/test/run-mcf-diamond.c index d02b0790e165..e8b5c5d1549b 100644 --- a/plugins/renepay/test/run-mcf-diamond.c +++ b/plugins/renepay/test/run-mcf-diamond.c @@ -18,23 +18,38 @@ #include /* AUTOGENERATED MOCKS START */ -/* Generated stub for fromwire_blinded_path */ -struct blinded_path *fromwire_blinded_path(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *plen UNNEEDED) -{ fprintf(stderr, "fromwire_blinded_path called!\n"); abort(); } +/* Generated stub for disabledmap_add_channel */ +void disabledmap_add_channel(struct disabledmap *p UNNEEDED, + struct short_channel_id scid UNNEEDED) +{ fprintf(stderr, "disabledmap_add_channel called!\n"); abort(); } +/* Generated stub for disabledmap_add_node */ +void disabledmap_add_node(struct disabledmap *p UNNEEDED, struct node_id node UNNEEDED) +{ fprintf(stderr, "disabledmap_add_node called!\n"); abort(); } +/* Generated stub for disabledmap_channel_is_warned */ +bool disabledmap_channel_is_warned(struct disabledmap *p UNNEEDED, + struct short_channel_id scid UNNEEDED) +{ fprintf(stderr, "disabledmap_channel_is_warned called!\n"); abort(); } +/* Generated stub for disabledmap_new */ +struct disabledmap *disabledmap_new(const tal_t *ctx UNNEEDED) +{ fprintf(stderr, "disabledmap_new called!\n"); abort(); } +/* Generated stub for disabledmap_reset */ +void disabledmap_reset(struct disabledmap *p UNNEEDED) +{ fprintf(stderr, "disabledmap_reset called!\n"); abort(); } +/* Generated stub for disabledmap_warn_channel */ +void disabledmap_warn_channel(struct disabledmap *p UNNEEDED, + struct short_channel_id scid UNNEEDED) +{ fprintf(stderr, "disabledmap_warn_channel called!\n"); abort(); } /* Generated stub for json_add_payment */ void json_add_payment(struct json_stream *s UNNEEDED, const struct payment *payment UNNEEDED) { fprintf(stderr, "json_add_payment called!\n"); abort(); } /* Generated stub for new_routetracker */ -struct routetracker *new_routetracker(const tal_t *ctx UNNEEDED) +struct routetracker *new_routetracker(const tal_t *ctx UNNEEDED, struct payment *payment UNNEEDED) { fprintf(stderr, "new_routetracker called!\n"); abort(); } /* Generated stub for pay_plugin */ struct pay_plugin *pay_plugin; /* Generated stub for routetracker_cleanup */ void routetracker_cleanup(struct routetracker *routetracker UNNEEDED) { fprintf(stderr, "routetracker_cleanup called!\n"); abort(); } -/* Generated stub for towire_blinded_path */ -void towire_blinded_path(u8 **p UNNEEDED, const struct blinded_path *blinded_path UNNEEDED) -{ fprintf(stderr, "towire_blinded_path called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ static u8 empty_map[] = { diff --git a/plugins/renepay/test/run-mcf.c b/plugins/renepay/test/run-mcf.c index c5ae8c3d105f..ab0774d5e897 100644 --- a/plugins/renepay/test/run-mcf.c +++ b/plugins/renepay/test/run-mcf.c @@ -20,23 +20,38 @@ #include /* AUTOGENERATED MOCKS START */ -/* Generated stub for fromwire_blinded_path */ -struct blinded_path *fromwire_blinded_path(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *plen UNNEEDED) -{ fprintf(stderr, "fromwire_blinded_path called!\n"); abort(); } +/* Generated stub for disabledmap_add_channel */ +void disabledmap_add_channel(struct disabledmap *p UNNEEDED, + struct short_channel_id scid UNNEEDED) +{ fprintf(stderr, "disabledmap_add_channel called!\n"); abort(); } +/* Generated stub for disabledmap_add_node */ +void disabledmap_add_node(struct disabledmap *p UNNEEDED, struct node_id node UNNEEDED) +{ fprintf(stderr, "disabledmap_add_node called!\n"); abort(); } +/* Generated stub for disabledmap_channel_is_warned */ +bool disabledmap_channel_is_warned(struct disabledmap *p UNNEEDED, + struct short_channel_id scid UNNEEDED) +{ fprintf(stderr, "disabledmap_channel_is_warned called!\n"); abort(); } +/* Generated stub for disabledmap_new */ +struct disabledmap *disabledmap_new(const tal_t *ctx UNNEEDED) +{ fprintf(stderr, "disabledmap_new called!\n"); abort(); } +/* Generated stub for disabledmap_reset */ +void disabledmap_reset(struct disabledmap *p UNNEEDED) +{ fprintf(stderr, "disabledmap_reset called!\n"); abort(); } +/* Generated stub for disabledmap_warn_channel */ +void disabledmap_warn_channel(struct disabledmap *p UNNEEDED, + struct short_channel_id scid UNNEEDED) +{ fprintf(stderr, "disabledmap_warn_channel called!\n"); abort(); } /* Generated stub for json_add_payment */ void json_add_payment(struct json_stream *s UNNEEDED, const struct payment *payment UNNEEDED) { fprintf(stderr, "json_add_payment called!\n"); abort(); } /* Generated stub for new_routetracker */ -struct routetracker *new_routetracker(const tal_t *ctx UNNEEDED) +struct routetracker *new_routetracker(const tal_t *ctx UNNEEDED, struct payment *payment UNNEEDED) { fprintf(stderr, "new_routetracker called!\n"); abort(); } /* Generated stub for pay_plugin */ struct pay_plugin *pay_plugin; /* Generated stub for routetracker_cleanup */ void routetracker_cleanup(struct routetracker *routetracker UNNEEDED) { fprintf(stderr, "routetracker_cleanup called!\n"); abort(); } -/* Generated stub for towire_blinded_path */ -void towire_blinded_path(u8 **p UNNEEDED, const struct blinded_path *blinded_path UNNEEDED) -{ fprintf(stderr, "towire_blinded_path called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ static void swap(int *a, int *b) @@ -389,7 +404,7 @@ int main(int argc, char *argv[]) assert(0 && "minflow failed"); } - routes = flows_to_routes(tmpctx, NULL, 1, 1, payment_hash, 10, gossmap, flows); + routes = flows_to_routes(tmpctx, 1, 1, payment_hash, 10, gossmap, flows); assert(routes); for (size_t i = 0; i < tal_count(routes); i++) { uncertainty_commit_htlcs(uncertainty, routes[i]); diff --git a/plugins/renepay/test/run-route_map.c b/plugins/renepay/test/run-route_map.c index 2c53f0601ed2..b17a2aa4e577 100644 --- a/plugins/renepay/test/run-route_map.c +++ b/plugins/renepay/test/run-route_map.c @@ -48,9 +48,9 @@ static void valgrind_ok1(void) tal_t *local_ctx = tal(this_ctx,tal_t); struct routekey key; - struct route *r1 = new_route(local_ctx, NULL, 1, 1, hash, + struct route *r1 = new_route(local_ctx, 1, 1, hash, AMOUNT_MSAT(0), AMOUNT_MSAT(0)); - struct route *r2 = new_route(local_ctx, NULL, 2, 3, hash, + struct route *r2 = new_route(local_ctx, 2, 3, hash, AMOUNT_MSAT(0), AMOUNT_MSAT(0)); printf("key1 = %s\n", fmt_routekey(local_ctx,&r1->key)); diff --git a/plugins/renepay/test/run-testflow.c b/plugins/renepay/test/run-testflow.c index de075d66cb02..c974eeca471b 100644 --- a/plugins/renepay/test/run-testflow.c +++ b/plugins/renepay/test/run-testflow.c @@ -20,23 +20,38 @@ #include "../mcf.c" /* AUTOGENERATED MOCKS START */ -/* Generated stub for fromwire_blinded_path */ -struct blinded_path *fromwire_blinded_path(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *plen UNNEEDED) -{ fprintf(stderr, "fromwire_blinded_path called!\n"); abort(); } +/* Generated stub for disabledmap_add_channel */ +void disabledmap_add_channel(struct disabledmap *p UNNEEDED, + struct short_channel_id scid UNNEEDED) +{ fprintf(stderr, "disabledmap_add_channel called!\n"); abort(); } +/* Generated stub for disabledmap_add_node */ +void disabledmap_add_node(struct disabledmap *p UNNEEDED, struct node_id node UNNEEDED) +{ fprintf(stderr, "disabledmap_add_node called!\n"); abort(); } +/* Generated stub for disabledmap_channel_is_warned */ +bool disabledmap_channel_is_warned(struct disabledmap *p UNNEEDED, + struct short_channel_id scid UNNEEDED) +{ fprintf(stderr, "disabledmap_channel_is_warned called!\n"); abort(); } +/* Generated stub for disabledmap_new */ +struct disabledmap *disabledmap_new(const tal_t *ctx UNNEEDED) +{ fprintf(stderr, "disabledmap_new called!\n"); abort(); } +/* Generated stub for disabledmap_reset */ +void disabledmap_reset(struct disabledmap *p UNNEEDED) +{ fprintf(stderr, "disabledmap_reset called!\n"); abort(); } +/* Generated stub for disabledmap_warn_channel */ +void disabledmap_warn_channel(struct disabledmap *p UNNEEDED, + struct short_channel_id scid UNNEEDED) +{ fprintf(stderr, "disabledmap_warn_channel called!\n"); abort(); } /* Generated stub for json_add_payment */ void json_add_payment(struct json_stream *s UNNEEDED, const struct payment *payment UNNEEDED) { fprintf(stderr, "json_add_payment called!\n"); abort(); } /* Generated stub for new_routetracker */ -struct routetracker *new_routetracker(const tal_t *ctx UNNEEDED) +struct routetracker *new_routetracker(const tal_t *ctx UNNEEDED, struct payment *payment UNNEEDED) { fprintf(stderr, "new_routetracker called!\n"); abort(); } /* Generated stub for pay_plugin */ struct pay_plugin *pay_plugin; /* Generated stub for routetracker_cleanup */ void routetracker_cleanup(struct routetracker *routetracker UNNEEDED) { fprintf(stderr, "routetracker_cleanup called!\n"); abort(); } -/* Generated stub for towire_blinded_path */ -void towire_blinded_path(u8 **p UNNEEDED, const struct blinded_path *blinded_path UNNEEDED) -{ fprintf(stderr, "towire_blinded_path called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ static const u8 canned_map[] = { @@ -675,7 +690,7 @@ static void test_flow_to_route(void) F->dirs[0]=0; deliver = AMOUNT_MSAT(250000000); F->amount = deliver; - route = flow_to_route(this_ctx, NULL, 1, 1, payment_hash, 0, gossmap, F); + route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); assert(route); assert(amount_msat_eq(route->hops[0].amount, deliver)); @@ -691,7 +706,7 @@ static void test_flow_to_route(void) F->dirs[1]=0; deliver = AMOUNT_MSAT(250000000); F->amount=deliver; - route = flow_to_route(this_ctx, NULL, 1, 1, payment_hash, 0, gossmap, F); + route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); assert(route); assert(amount_msat_eq(route->hops[0].amount, amount_msat(250050016))); @@ -709,7 +724,7 @@ static void test_flow_to_route(void) F->dirs[2]=0; deliver = AMOUNT_MSAT(250000000); F->amount=deliver; - route = flow_to_route(this_ctx, NULL, 1, 1, payment_hash, 0, gossmap, F); + route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); assert(route); assert(amount_msat_eq(route->hops[0].amount, amount_msat(250087534))); @@ -729,7 +744,7 @@ static void test_flow_to_route(void) F->dirs[3]=0; deliver = AMOUNT_MSAT(250000000); F->amount=deliver; - route = flow_to_route(this_ctx, NULL, 1, 1, payment_hash, 0, gossmap, F); + route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); assert(route); assert(amount_msat_eq(route->hops[0].amount, amount_msat(250112544))); From 2216dcebfd52d593932f57310aa2e0457a2638b8 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 6 May 2024 10:01:24 +0100 Subject: [PATCH 28/31] renepay: a bit more verbose information in logs --- plugins/renepay/chan_extra.c | 5 +++-- plugins/renepay/flow.c | 33 +++++++++++++++++++++++++++++++++ plugins/renepay/flow.h | 4 ++++ plugins/renepay/route.c | 6 ++++-- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/plugins/renepay/chan_extra.c b/plugins/renepay/chan_extra.c index 1caa17a249ef..46e3ff499b37 100644 --- a/plugins/renepay/chan_extra.c +++ b/plugins/renepay/chan_extra.c @@ -24,9 +24,10 @@ const char *fmt_chan_extra_map(const tal_t *ctx, const char *scid_str = fmt_short_channel_id(this_ctx, ch->scid); for (int dir = 0; dir < 2; ++dir) { tal_append_fmt( - &buff, "%s[%d]:(%s,%s)\n", scid_str, dir, + &buff, "%s[%d]:(%s,%s) htlc: %s\n", scid_str, dir, fmt_amount_msat(this_ctx, ch->half[dir].known_min), - fmt_amount_msat(this_ctx, ch->half[dir].known_max)); + fmt_amount_msat(this_ctx, ch->half[dir].known_max), + fmt_amount_msat(this_ctx, ch->half[dir].htlc_total)); } } tal_free(this_ctx); diff --git a/plugins/renepay/flow.c b/plugins/renepay/flow.c index 757c07af2446..39b9864b10f8 100644 --- a/plugins/renepay/flow.c +++ b/plugins/renepay/flow.c @@ -34,6 +34,39 @@ struct amount_msat *tal_flow_amounts(const tal_t *ctx, const struct flow *flow) return tal_free(amounts); } +const char *fmt_flows(const tal_t *ctx, const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct flow **flows) +{ + tal_t *this_ctx = tal(ctx, tal_t); + double tot_prob = + flowset_probability(tmpctx, flows, gossmap, chan_extra_map, NULL); + assert(tot_prob >= 0); + char *buff = tal_fmt(ctx, "%zu subflows, prob %2lf\n", tal_count(flows), + tot_prob); + for (size_t i = 0; i < tal_count(flows); i++) { + struct amount_msat fee, delivered; + tal_append_fmt(&buff, " "); + for (size_t j = 0; j < tal_count(flows[i]->path); j++) { + struct short_channel_id scid = + gossmap_chan_scid(gossmap, flows[i]->path[j]); + tal_append_fmt(&buff, "%s%s", j ? "->" : "", + fmt_short_channel_id(this_ctx, scid)); + } + delivered = flows[i]->amount; + if (!flow_fee(&fee, flows[i])) { + abort(); + } + tal_append_fmt(&buff, " prob %.2f, %s delivered with fee %s\n", + flows[i]->success_prob, + fmt_amount_msat(this_ctx, delivered), + fmt_amount_msat(this_ctx, fee)); + } + + tal_free(this_ctx); + return buff; +} + /* Returns the greatest amount we can deliver to the destination using this * route. It takes into account the current knowledge, pending HTLC, * htlc_max and fees. diff --git a/plugins/renepay/flow.h b/plugins/renepay/flow.h index f097e631ba91..d0ad983d51f4 100644 --- a/plugins/renepay/flow.h +++ b/plugins/renepay/flow.h @@ -18,6 +18,10 @@ struct flow { struct amount_msat amount; }; +const char *fmt_flows(const tal_t *ctx, const struct gossmap *gossmap, + struct chan_extra_map *chan_extra_map, + struct flow **flows); + /* Helper to access the half chan at flow index idx */ const struct half_chan *flow_edge(const struct flow *flow, size_t idx); diff --git a/plugins/renepay/route.c b/plugins/renepay/route.c index 0e3c4589f280..06ac2526fa03 100644 --- a/plugins/renepay/route.c +++ b/plugins/renepay/route.c @@ -97,13 +97,15 @@ struct route **flows_to_routes(const tal_t *ctx, const char *fmt_route_path(const tal_t *ctx, const struct route *route) { + tal_t *this_ctx = tal(ctx, tal_t); char *s = tal_strdup(ctx, ""); const size_t pathlen = tal_count(route->hops); for (size_t i = 0; i < pathlen; i++) { const struct short_channel_id_dir scidd = hop_to_scidd(&route->hops[i]); - tal_append_fmt(&s, "-%s->", - fmt_short_channel_id(tmpctx, scidd.scid)); + tal_append_fmt(&s, "%s%s", i ? "->" : "", + fmt_short_channel_id(this_ctx, scidd.scid)); } + tal_free(this_ctx); return s; } From 4b5afb9d39f86adc8abb7f9cb5cd35359d948b05 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 6 May 2024 10:03:42 +0100 Subject: [PATCH 29/31] renepay: wake-up result collector every 50msec --- plugins/renepay/mods.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c index a6bbd52badb4..5f5423559bc0 100644 --- a/plugins/renepay/mods.c +++ b/plugins/renepay/mods.c @@ -19,6 +19,8 @@ #define OP_CALL (void *)1 #define OP_IF (void *)2 +#define COLLECTOR_TIME_WINDOW_MSEC 50 + void *payment_virtual_program[]; /* Advance the payment virtual machine */ @@ -863,11 +865,9 @@ static void sleep_done(struct payment *payment) static struct command_result *sleep_cb(struct payment *payment) { - // FIXME time duration is hardcoded, we could have this as a - // plugin wide option with default value at 10 millisecons. assert(payment->waitresult_timer == NULL); payment->waitresult_timer = plugin_timer( - pay_plugin->plugin, time_from_msec(10), sleep_done, payment); + pay_plugin->plugin, time_from_msec(COLLECTOR_TIME_WINDOW_MSEC), sleep_done, payment); struct command *cmd = payment_command(payment); assert(cmd); return command_still_pending(cmd); From f3647351e5aa1bde35c176f372782620afd65cd0 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 6 May 2024 10:13:45 +0100 Subject: [PATCH 30/31] renepay: fix sources --- plugins/renepay/json.c | 2 +- plugins/renepay/payment.h | 4 +-- plugins/renepay/routebuilder.h | 4 +-- plugins/renepay/routefail.c | 59 +++++++++++++++++++++------------- plugins/renepay/routetracker.c | 4 +-- tests/plugins/no_fail.py | 5 ++- tests/test_renepay.py | 8 +++-- 7 files changed, 52 insertions(+), 34 deletions(-) diff --git a/plugins/renepay/json.c b/plugins/renepay/json.c index 58224c81b66e..03b2f0dd8ef5 100644 --- a/plugins/renepay/json.c +++ b/plugins/renepay/json.c @@ -226,7 +226,7 @@ void json_add_route(struct json_stream *js, const struct route *route, assert(js); assert(route); assert(payment); - + const struct payment_info *pinfo = &payment->payment_info; assert(route->hops); diff --git a/plugins/renepay/payment.h b/plugins/renepay/payment.h index ff22d20b0e7b..3d76aa93d2b8 100644 --- a/plugins/renepay/payment.h +++ b/plugins/renepay/payment.h @@ -15,10 +15,10 @@ struct payment { // TODO: probably not necessary after we store all payments in a // hashtable instead of a list struct list_node list; - + struct payment_info payment_info; - + /* === Public State === */ /* TODO: these properties should be private and only changed through * payment_ methods. */ diff --git a/plugins/renepay/routebuilder.h b/plugins/renepay/routebuilder.h index 9dd953dc95fa..2692d2a82adc 100644 --- a/plugins/renepay/routebuilder.h +++ b/plugins/renepay/routebuilder.h @@ -20,9 +20,9 @@ struct route **get_routes(const tal_t *ctx, struct amount_msat amount_to_deliver, struct amount_msat feebudget, - + u64 *next_partid, - u64 groupid, + u64 groupid, enum jsonrpc_errcode *ecode, const char **fail); diff --git a/plugins/renepay/routefail.c b/plugins/renepay/routefail.c index 76e33fe42e25..2116abb07eaa 100644 --- a/plugins/renepay/routefail.c +++ b/plugins/renepay/routefail.c @@ -27,7 +27,7 @@ struct command_result *routefail_start(const tal_t *ctx, struct route *route, { assert(route); struct routefail *r = tal(ctx, struct routefail); - struct payment *payment = + struct payment *payment = payment_map_get(pay_plugin->payment_map, route->key.payment_hash); if (payment == NULL) @@ -123,7 +123,7 @@ static struct command_result *update_gossip_failure(struct command *cmd UNUSED, { assert(r); assert(r->payment); - + /* FIXME it might be too strong assumption that erring_channel should * always be present here, but at least the documentation for * waitsendpay says it is present in the case of error. */ @@ -188,26 +188,43 @@ static struct command_result *handle_failure(struct routefail *r) /* BOLT #4: * * A _forwarding node_ MAY, but a _final node_ MUST NOT: - * - return an `invalid_onion_version` error. - * - return an `invalid_onion_hmac` error. - * - return an `invalid_onion_key` error. - * - return a `temporary_channel_failure` error. - * - return a `permanent_channel_failure` error. - * - return a `required_channel_feature_missing` error. - * - return an `unknown_next_peer` error. - * - return an `amount_below_minimum` error. - * - return a `fee_insufficient` error. - * - return an `incorrect_cltv_expiry` error. - * - return an `expiry_too_soon` error. - * - return an `expiry_too_far` error. - * - return a `channel_disabled` error. + *... + * - return an `invalid_onion_version` error. + *... + * - return an `invalid_onion_hmac` error. + *... + * - return an `invalid_onion_key` error. + *... + * - return a `temporary_channel_failure` error. + *... + * - return a `permanent_channel_failure` error. + *... + * - return a `required_channel_feature_missing` error. + *... + * - return an `unknown_next_peer` error. + *... + * - return an `amount_below_minimum` error. + *... + * - return a `fee_insufficient` error. + *... + * - return an `incorrect_cltv_expiry` error. + *... + * - return an `expiry_too_soon` error. + *... + * - return an `expiry_too_far` error. + *... + * - return a `channel_disabled` error. + */ + /* BOLT #4: * * An _intermediate hop_ MUST NOT, but the _final node_: + *... * - MUST return an `incorrect_or_unknown_payment_details` error. + *... * - MUST return `final_incorrect_cltv_expiry` error. + *... * - MUST return a `final_incorrect_htlc_amount` error. */ - assert(r); struct route *route = r->route; assert(route); @@ -416,12 +433,10 @@ static struct command_result *handle_failure(struct routefail *r) /* Update the knowledge in the uncertaity network. */ if (route->hops) { - if(last_good_channel >= path_len) - { - plugin_err(pay_plugin->plugin, - "last_good_channel (%d) >= path_len (%d)", - last_good_channel, - path_len); + if (last_good_channel >= path_len) { + plugin_err(pay_plugin->plugin, + "last_good_channel (%d) >= path_len (%d)", + last_good_channel, path_len); } /* All channels before the erring node could forward the diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c index 565b82fba928..165654a3c9f4 100644 --- a/plugins/renepay/routetracker.c +++ b/plugins/renepay/routetracker.c @@ -21,7 +21,7 @@ static struct payment *route_get_payment_verify(struct route *route) struct routetracker *new_routetracker(const tal_t *ctx, struct payment *payment) { struct routetracker *rt = tal(ctx, struct routetracker); - + rt->payment = payment; rt->sent_routes = tal(rt, struct route_map); @@ -132,7 +132,7 @@ static void route_result_collected(struct routetracker *routetracker, assert(route); assert(routetracker); assert(route->result); - + struct payment *payment = routetracker->payment; assert(payment); diff --git a/tests/plugins/no_fail.py b/tests/plugins/no_fail.py index d0a1c47b4bd7..60318554323d 100755 --- a/tests/plugins/no_fail.py +++ b/tests/plugins/no_fail.py @@ -2,7 +2,7 @@ """Plugin that breaks the node if a fail notification is received. """ -from pyln.client import Plugin, RpcError +from pyln.client import Plugin import os plugin = Plugin() @@ -20,8 +20,7 @@ def channel_opened(plugin, sendpay_failure, **kwargs): @plugin.method("nofail") def nofail(plugin): - """Checks that the plugin is still running. - """ + """Checks that the plugin is still running.""" return {"status": "active"} diff --git a/tests/test_renepay.py b/tests/test_renepay.py index 031e06ed110d..fb6732afded4 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -441,8 +441,12 @@ def test_fee_allocation(node_factory): # We set high fees at 3% and load a plugin that breaks if a sendpay_failure # notification is received. opts = [ - {"disable-mpp": None, "fee-base": 1000, "fee-per-satoshi": 30000, - 'plugin': os.path.join(os.getcwd(), 'tests/plugins/no_fail.py')}, + { + "disable-mpp": None, + "fee-base": 1000, + "fee-per-satoshi": 30000, + "plugin": os.path.join(os.getcwd(), "tests/plugins/no_fail.py"), + }, ] l1, l2, l3, l4 = node_factory.get_nodes(4, opts=opts * 4) start_channels( From 43cae6bc213c09d90a69d4ba983dc8f686a1d812 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 6 May 2024 16:14:10 +0100 Subject: [PATCH 31/31] renepay: fix some memory leaks --- plugins/renepay/main.c | 8 ++++---- plugins/renepay/mods.c | 10 +++++++++- plugins/renepay/payment.c | 5 ++++- plugins/renepay/payment.h | 2 +- plugins/renepay/routetracker.c | 2 +- plugins/renepay/uncertainty.c | 1 + 6 files changed, 20 insertions(+), 8 deletions(-) diff --git a/plugins/renepay/main.c b/plugins/renepay/main.c index bb18dc29f26e..b7e83cdf3c3d 100644 --- a/plugins/renepay/main.c +++ b/plugins/renepay/main.c @@ -29,10 +29,10 @@ struct pay_plugin *pay_plugin; static void memleak_mark(struct plugin *p, struct htable *memtable) { memleak_scan_obj(memtable, pay_plugin); - - // TODO is this necessary? - // memleak_scan_htable(memtable, &pay_plugin->chan_extra_map->raw); - // memleak_scan_htable(memtable, &pay_plugin->payment_map->raw); + memleak_scan_htable(memtable, + &pay_plugin->uncertainty->chan_extra_map->raw); + memleak_scan_htable(memtable, &pay_plugin->payment_map->raw); + memleak_scan_htable(memtable, &pay_plugin->route_map->raw); } static const char *init(struct plugin *p, diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c index 5f5423559bc0..1520c05979d1 100644 --- a/plugins/renepay/mods.c +++ b/plugins/renepay/mods.c @@ -335,6 +335,7 @@ static struct command_result *selfpay_success(struct command *cmd, const jsmntok_t *tok, struct route *route) { + tal_steal(tmpctx, route); // discard this route when tmpctx clears struct payment *payment = payment_map_get(pay_plugin->payment_map, route->key.payment_hash); assert(payment); @@ -357,6 +358,7 @@ static struct command_result *selfpay_failure(struct command *cmd, const jsmntok_t *tok, struct route *route) { + tal_steal(tmpctx, route); // discard this route when tmpctx clears struct payment *payment = payment_map_get(pay_plugin->payment_map, route->key.payment_hash); assert(payment); @@ -382,6 +384,8 @@ static struct command_result *selfpay_cb(struct payment *payment) "Selfpay: cannot get a valid cmd."); struct payment_info *pinfo = &payment->payment_info; + /* Self-payment routes are not part of the routetracker, we build them + * on-the-fly here and release them on success or failure. */ struct route *route = new_route(payment, payment->groupid, /*partid=*/0, pinfo->payment_hash, @@ -748,7 +752,7 @@ compute_routes_done(struct command *cmd UNUSED, const char *buf UNUSED, // in the local gossmap. enum jsonrpc_errcode errcode; - const char *err_msg; + const char *err_msg = NULL; gossmap_apply_localmods(pay_plugin->gossmap, payment->local_gossmods); // TODO: add an algorithm selector here @@ -775,13 +779,17 @@ compute_routes_done(struct command *cmd UNUSED, const char *buf UNUSED, &errcode, &err_msg); + err_msg = tal_steal(tmpctx, err_msg); gossmap_remove_localmods(pay_plugin->gossmap, payment->local_gossmods); /* Couldn't feasible route, we stop. */ if (!payment->routes_computed) { + if(err_msg==NULL) + err_msg = tal_fmt(tmpctx, "get_routes returned NULL error message"); return payment_fail(payment, errcode, "%s", err_msg); } + return payment_continue(payment); } diff --git a/plugins/renepay/payment.c b/plugins/renepay/payment.c index b06ce44de9ab..6be264f54a00 100644 --- a/plugins/renepay/payment.c +++ b/plugins/renepay/payment.c @@ -280,7 +280,10 @@ struct command_result *payment_success(struct payment *payment, assert(preimage); payment->status = PAYMENT_SUCCESS; payment->preimage = tal_free(payment->preimage); - payment->preimage = tal_dup(payment, struct preimage, preimage); + if(taken(preimage)) + payment->preimage = tal_steal(payment, preimage); + else + payment->preimage = tal_dup(payment, struct preimage, preimage); return payment_finish(payment); } diff --git a/plugins/renepay/payment.h b/plugins/renepay/payment.h index 3d76aa93d2b8..8c6ac6ba047d 100644 --- a/plugins/renepay/payment.h +++ b/plugins/renepay/payment.h @@ -27,7 +27,7 @@ struct payment { enum payment_status status; /* Payment preimage, in case of success. */ - struct preimage *preimage; + const struct preimage *preimage; /* Final error code and message, in case of failure. */ enum jsonrpc_errcode error_code; diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c index 165654a3c9f4..4dc5fded84f9 100644 --- a/plugins/renepay/routetracker.c +++ b/plugins/renepay/routetracker.c @@ -238,7 +238,7 @@ void payment_collect_results(struct payment *payment, if (r->result->status == SENDPAY_COMPLETE && payment_preimage) { assert(r->result->payment_preimage); *payment_preimage = - tal_dup(payment, struct preimage, + tal_dup(tmpctx, struct preimage, r->result->payment_preimage); } diff --git a/plugins/renepay/uncertainty.c b/plugins/renepay/uncertainty.c index 37529431a549..c2c437970ab1 100644 --- a/plugins/renepay/uncertainty.c +++ b/plugins/renepay/uncertainty.c @@ -89,6 +89,7 @@ int uncertainty_update(struct uncertainty *uncertainty, struct gossmap *gossmap) } for(size_t i=0;i