diff --git a/plugins/renepay/flow.c b/plugins/renepay/flow.c index 94f794355..f138f4dc6 100644 --- a/plugins/renepay/flow.c +++ b/plugins/renepay/flow.c @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include #include #include @@ -136,6 +138,369 @@ struct chan_extra *new_chan_extra(struct chan_extra_map *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_deliveramounts = 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"); + *fail = tal_fmt( + ctx, "channel not found in chan_extra_map"); goto function_fail; } @@ -848,10 +1232,10 @@ bool flow_complete(const tal_t *ctx, struct flow *flow, double prob = edge_probability(this_ctx, h->known_min, h->known_max, h->htlc_total, delivered, &errmsg); - if(prob<0){ + if (prob < 0) { if (fail) - *fail = tal_fmt(ctx,"edge_probability failed: %s", - errmsg); + *fail = tal_fmt( + ctx, "edge_probability failed: %s", errmsg); goto function_fail; } flow->success_prob *= prob; @@ -860,14 +1244,14 @@ bool flow_complete(const tal_t *ctx, struct flow *flow, &delivered, flow_edge(flow, i)->base_fee, flow_edge(flow, i)->proportional_fee)) { if (fail) - *fail = tal_fmt(ctx, "fee overflow"); + *fail = tal_fmt(ctx, "fee overflow"); goto function_fail; } } tal_free(this_ctx); return true; - function_fail: +function_fail: tal_free(this_ctx); return false; } diff --git a/plugins/renepay/flow.h b/plugins/renepay/flow.h index 3934b043c..6295afa8d 100644 --- a/plugins/renepay/flow.h +++ b/plugins/renepay/flow.h @@ -262,4 +262,14 @@ 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); + #endif /* LIGHTNING_PLUGINS_RENEPAY_FLOW_H */ diff --git a/plugins/renepay/mcf.c b/plugins/renepay/mcf.c index baf1332df..e5e12a67f 100644 --- a/plugins/renepay/mcf.c +++ b/plugins/renepay/mcf.c @@ -457,6 +457,11 @@ static bool linearize_channel(const struct pay_parameters *params, return false; } + assert( + amount_msat_less_eq(extra_half->htlc_total, extra_half->known_max)); + assert( + amount_msat_less_eq(extra_half->known_min, extra_half->known_max)); + s64 h = extra_half->htlc_total.millisatoshis/1000; /* Raw: linearize_channel */ s64 a = extra_half->known_min.millisatoshis/1000, /* Raw: linearize_channel */ b = 1 + extra_half->known_max.millisatoshis/1000; /* Raw: linearize_channel */ @@ -1234,7 +1239,10 @@ get_flow_paths(const tal_t *ctx, const struct gossmap *gossmap, // how many msats in excess we paid for not having msat accuracy // in the MCF solver - struct amount_msat excess, char **fail) + struct amount_msat excess, + + // error message + char **fail) { tal_t *this_ctx = tal(ctx,tal_t); char *errmsg; @@ -1386,10 +1394,11 @@ get_flow_paths(const tal_t *ctx, const struct gossmap *gossmap, } } - /* Stablish ownership. */ - for(int i=0;igossmap, params->chan_extra_map, linear_network, residual_network, excess, &errmsg); diff --git a/plugins/renepay/pay_flow.c b/plugins/renepay/pay_flow.c index 978f2570f..ecff96c38 100644 --- a/plugins/renepay/pay_flow.c +++ b/plugins/renepay/pay_flow.c @@ -10,6 +10,11 @@ #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 @@ -445,18 +450,17 @@ static bool disable_htlc_violations(struct payment *payment, return disabled_some; } -const char *add_payflows(const tal_t *ctx, - struct payment *p, - struct amount_msat amount, - struct amount_msat feebudget, - bool is_entire_payment, +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; + char *errmsg, *fail = NULL; - disabled = make_disabled_bitmap(tmpctx, pay_plugin->gossmap, p->disabled_scids); + 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; @@ -465,83 +469,102 @@ const char *add_payflows(const tal_t *ctx, 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."); + return tal_fmt(ctx, + "Destination is unknown in the network gossip."); } - for (;;) { - struct flow **flows; - double prob; - struct amount_msat fee; - u64 delay; - bool too_expensive, too_delayed; - const u32 *final_cltvs; + /* probability "bugdet". We will prefer solutions whose probability of + * success is above this value. */ + double min_prob_success = p->min_prob_success; - flows = minflow(tmpctx, pay_plugin->gossmap, src, dst, - pay_plugin->chan_extra_map, disabled, - amount, - feebudget, - p->min_prob_success , - p->delay_feefactor, - p->base_fee_penalty, - p->prob_cost_factor, - &errmsg); + 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; - return tal_fmt(ctx, - "minflow couldn't find a feasible flow for %s, %s", - type_to_string(tmpctx,struct amount_msat,&amount), - errmsg); + + /* 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", + type_to_string(tmpctx, struct amount_msat, + &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? */ - char *fail; - prob = flowset_probability(tmpctx, flows, pay_plugin->gossmap, - pay_plugin->chan_extra_map, &fail); - if(prob<0) - { + 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", fail); + "flow_set_probability failed: %s", errmsg); } - if(!flowset_fee(&fee,flows)) - { - plugin_err(pay_plugin->plugin, - "flowset_fee failed"); + struct amount_msat fee; + if (!flowset_fee(&fee, flows)) { + plugin_err(pay_plugin->plugin, "flowset_fee failed"); } - delay = flows_worst_delay(flows) + p->final_cltv; + 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, - type_to_string(tmpctx,struct amount_msat,&fee), - delay); + "we have computed a set of %ld flows with " + "probability %.3lf, fees %s and delay %ld", + tal_count(flows), prob, + type_to_string(tmpctx, struct amount_msat, &fee), + delay); - too_expensive = amount_msat_greater(fee, feebudget); - if (too_expensive) - { + if (amount_msat_greater(fee, feebudget)) { *ecode = PAY_ROUTE_TOO_EXPENSIVE; - return tal_fmt(ctx, - "Fee exceeds our fee budget, " - "fee = %s (maxfee = %s)", - type_to_string(tmpctx, struct amount_msat, &fee), - type_to_string(tmpctx, struct amount_msat, &feebudget)); + fail = tal_fmt( + ctx, + "Fee exceeds our fee budget, " + "fee = %s (maxfee = %s)", + type_to_string(tmpctx, struct amount_msat, &fee), + type_to_string(tmpctx, struct amount_msat, + &feebudget)); + goto function_fail; } - too_delayed = (delay > p->maxdelay); - if (too_delayed) { + if (delay > p->maxdelay) { /* FIXME: What is a sane limit? */ if (p->delay_feefactor > 1000) { *ecode = PAY_ROUTE_TOO_EXPENSIVE; - return tal_fmt(ctx, - "CLTV delay exceeds our CLTV budget, " - "delay = %"PRIu64" (maxdelay = %u)", - delay, p->maxdelay); + 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); + "delay %" PRIu64 + " exceeds our max %u, so doubling " + "delay_feefactor to %f", + delay, p->maxdelay, p->delay_feefactor); continue; // retry } @@ -552,24 +575,57 @@ const char *add_payflows(const tal_t *ctx, * are far better, since we can report min/max which * *actually* made us reconsider. */ if (disable_htlc_violations(p, flows, pay_plugin->gossmap, - disabled)) - { + disabled)) { continue; // retry } /* This can adjust amounts and final cltv for each flow, * to make it look like it's going elsewhere */ - final_cltvs = shadow_additions(tmpctx, pay_plugin->gossmap, - p, flows, is_entire_payment); + 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); - return NULL; + 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) diff --git a/plugins/renepay/payment.c b/plugins/renepay/payment.c index 042c7527b..0fcb558a9 100644 --- a/plugins/renepay/payment.c +++ b/plugins/renepay/payment.c @@ -5,7 +5,6 @@ #include #include #include -#include #include struct payment *payment_new(const tal_t *ctx, @@ -458,3 +457,13 @@ void payment_reconsider(struct payment *payment) if (errmsg) payment_fail(payment, ecode, "%s", errmsg); } + +/* Remove all flows with the given state. */ +void payment_remove_flows(struct payment *p, enum pay_flow_state state) +{ + struct pay_flow *pf, *next; + list_for_each_safe(&p->flows, pf, next, list) { + if(pf->state == state) + list_del(&pf->list); + } +} diff --git a/plugins/renepay/payment.h b/plugins/renepay/payment.h index cd15652f0..f73c32597 100644 --- a/plugins/renepay/payment.h +++ b/plugins/renepay/payment.h @@ -3,6 +3,10 @@ #include "config.h" #include #include +#include + +/* FIXME: this shouldn't be here, the dependency tree is a little messed-up. */ +enum pay_flow_state; struct pay_flow; @@ -173,6 +177,9 @@ void payment_disable_chan(struct payment *p, enum log_level lvl, const char *fmt, ...); +/* Remove all flows with the given state. */ +void payment_remove_flows(struct payment *p, enum pay_flow_state state); + struct command_result *payment_fail( struct payment *payment, enum jsonrpc_errcode code, diff --git a/plugins/renepay/test/run-testflow.c b/plugins/renepay/test/run-testflow.c index 9a172cfd8..25764ce65 100644 --- a/plugins/renepay/test/run-testflow.c +++ b/plugins/renepay/test/run-testflow.c @@ -710,12 +710,87 @@ static void test_flow_complete(void) tal_free(this_ctx); } +/* Test channel_maximum_forward function */ +static void test_channel_maximum_forward(void) +{ + const tal_t *this_ctx = tal(tmpctx, tal_t); + + char *gossfile; + int fd = tmpdir_mkstemp(this_ctx, "run-testflow.XXXXXX", &gossfile); + tal_add_destructor(gossfile, remove_file); + + assert(write_all(fd, canned_map, sizeof(canned_map))); + struct gossmap *gossmap = gossmap_load(this_ctx, gossfile, NULL); + assert(gossmap); + + struct short_channel_id scid12; + assert(short_channel_id_from_str("113x1x1", 7, &scid12)); + + struct gossmap_chan *c; + + // check the bounds channel 1--2 + c = gossmap_find_chan(gossmap, &scid12); + + const u64 test_in[] = { + 0, 1, 2, 3, + 10, 11, 12, 20, + 30, 100, 110, 111, + 200, 300, 1000, 2000, + 3000, 10000, 10025, 20000, + 1000000, 1100032, 2000000, 3000000, + 1000000000, 2000000000, 3000000000, 1000000000000 /*10 BTC*/}; + const u64 test_basefee[] = { + 0, 1, 2, 3, 4, 10, 20, 30, + 100, 200, 1000, 2000, 10000, 10001, 10002, 10010, + 10011, 10012, 20000, 1000000, 2000000, 1 << 23, (1 << 24) - 1}; + const u64 test_ppm[] = {0, 1, 2, 3, 4, + 10, 20, 30, 100, 200, + 300, 1000, 2000, 3000, 10000, + 20000, 30000, 11111, 100000, 100001, + 200000, 500000, 900000, 999999, 1000000}; + + const size_t N_in = sizeof(test_in) / sizeof(test_in[0]); + for (int i = 0; i < N_in; ++i) { + const struct amount_msat in = amount_msat(test_in[i]); + + const size_t N_base = + sizeof(test_basefee) / sizeof(test_basefee[0]); + for (int j = 0; j < N_base; ++j) { + const u64 basefee = test_basefee[j]; + + const size_t N_ppm = + sizeof(test_ppm) / sizeof(test_ppm[0]); + for (int k = 0; k < N_ppm; ++k) { + const u64 ppm = test_ppm[k]; + + c->half[0].base_fee = basefee; + c->half[0].proportional_fee = ppm; + struct amount_msat out; + + assert(channel_maximum_forward(&out, c, 0, in)); + + // do we satisfy the fee constraint? + assert(check_fee_inequality(in, out, basefee, + ppm)); + + // is this the best we can do? + assert( + amount_msat_add(&out, out, amount_msat(1))); + assert(!check_fee_inequality(in, out, basefee, + ppm)); + } + } + } + tal_free(this_ctx); +} + int main(int argc, char *argv[]) { common_setup(argv[0]); test_edge_probability(); test_flow_complete(); + test_channel_maximum_forward(); common_shutdown(); } diff --git a/plugins/renepay/uncertainty_network.c b/plugins/renepay/uncertainty_network.c index 5c4ca2939..7dba292da 100644 --- a/plugins/renepay/uncertainty_network.c +++ b/plugins/renepay/uncertainty_network.c @@ -258,7 +258,24 @@ void uncertainty_network_update_from_listpeerchannels(struct payment *p, /* this channel is not public, but it belongs to us */ totaltok = json_get_member(buf, chantok, "total_msat"); - json_to_msat(buf, totaltok, &capacity); + if (!totaltok) { + errmsg = tal_fmt( + tmpctx, + "Failed to update channel from listpeerchannels " + "scid=%s, missing total_msat", + type_to_string(tmpctx, struct short_channel_id, + &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", + type_to_string(tmpctx, struct short_channel_id, + &scidd->scid)); + goto error; + } ce = new_chan_extra(chan_extra_map, scidd->scid, capacity); } @@ -269,7 +286,7 @@ void uncertainty_network_update_from_listpeerchannels(struct payment *p, if (!amount_msat_sub(&max, max, amount_msat_div(p->amount, 100))) max = AMOUNT_MSAT(0); - // TODO(eduardo): this includes pending HTLC of previous + // 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, @@ -277,6 +294,10 @@ void uncertainty_network_update_from_listpeerchannels(struct payment *p, plugin_err(pay_plugin->plugin, "chan_extra_set_liquidity failed: %s", errmsg); } + return; + + error: + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, "%s", errmsg); } /* Forget ALL channels information by a fraction of the capacity. */ diff --git a/tests/test_renepay.py b/tests/test_renepay.py index e0d79be29..b5bf3f4a3 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -373,3 +373,27 @@ def test_self_pay(node_factory): with pytest.raises(RpcError, match=r'Unknown invoice') as excinfo: l1.rpc.call('renepay', {'invstring': inv2}) assert excinfo.value.error['code'] == 203 + + +def test_fee_allocation(node_factory): + ''' + Topology: + 1----2 + | | + 3----4 + This a payment that fails if fee is not allocated as part of the flow + constraints. + ''' + # High fees at 3% + opts = [ + {'disable-mpp': None, 'fee-base': 1000, 'fee-per-satoshi': 30000}, + ] + l1, l2, l3, l4 = node_factory.get_nodes(4, opts=opts * 4) + start_channels([(l1, l2, 1000000), (l2, l4, 2000000), + (l1, l3, 1000000), (l3, l4, 2000000)]) + + inv = l4.rpc.invoice("1500000sat", "inv", 'description') + l1.rpc.call('renepay', {'invstring': inv['bolt11'], 'maxfee': '75000sat'}) + l1.wait_for_htlcs() + invoice = only_one(l4.rpc.listinvoices('inv')['invoices']) + assert invoice['amount_received_msat'] >= Millisatoshi('1500000sat')