renepay: accomodate fees in the payment flow

Min. Cost Flow does not take into account fees when computing a flow
    with liquidity constraints.
    This is a work-around solution that reduces the amount on every route to
    respect the liquidity bound. The deficity in the delivered amount is
    solved by running MCF once again.

    Changes:

    1. the function `flow_complete` allocates amounts to send over the set of routes
       computed by the MCF algorithm, but it does not allocate more than liquidity
       bound of the route. For this reason `minflow` returns a set of routes that
       satisfy the liquidity bounds but it is not guaranteed that the total payment
       reaches the destination therefore there could a deficit in the delivery:
       `deficit = amount_to_deliver - delivering`.

    2. in the function `add_payflows` after `minflow` returns a set of routes we
       call `flows_fit_amount` that tries to a allocate the `deficit` in the routes
       that the MCF have computed.

    3. if the resulting flows pass all payment constraints then we update
        `amount_to_deliver = amount_to_deliver - delivering`, and the loop
        repeats as long as `amount_to_deliver` is not zero.

    In other words, the excess amount, beyond the liquidity bound,
    in the routes is removed and then we try to allocate it
    into known routes, otherwise we do a whole MCF again just for the
    remaining amount.

    Fixes issue #6599
This commit is contained in:
Lagrang3 2024-01-25 07:13:43 +01:00 committed by Rusty Russell
parent a4f92eac81
commit b0054aad46
9 changed files with 681 additions and 82 deletions

View file

@ -2,6 +2,8 @@
#include <assert.h>
#include <ccan/tal/str/str.h>
#include <ccan/tal/tal.h>
#include <common/fp16.h>
#include <common/overflows.h>
#include <common/type_to_string.h>
#include <math.h>
#include <plugins/renepay/flow.h>
@ -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_deliver<amount_to_deliver
if (fail)
*fail = tal_fmt(
ctx,
"(%s, line %d) unexpected amount_msat_sub failure",
__PRETTY_FUNCTION__, __LINE__);
goto function_fail;
}
/* FIXME Current algorithm assigns as much of the deficit as possible to
* the list of routes, we can improve this lets say in order to maximize
* the probability. If the deficit is very small with respect to the
* amount each flow carries then optimization here will not make much
* difference. */
for (size_t i = 0; i < tal_count(flows) && !amount_msat_zero(deficit);
++i) {
struct amount_msat max_deliverable;
if (!flow_maximum_deliverable(&max_deliverable, flows[i],
gossmap, chan_extra_map)) {
if (fail)
*fail =
tal_fmt(ctx,
"(%s, line %d) "
"flow_maximum_deliverable failed",
__PRETTY_FUNCTION__, __LINE__);
goto function_fail;
}
struct amount_msat delivers = flow_delivers(flows[i]);
struct amount_msat diff;
if (!amount_msat_sub(&diff, max_deliverable, delivers)) {
// this should never happen, a precondition of this
// function is that the flows already respect the
// liquidity bounds.
if (fail)
*fail = tal_fmt(ctx,
"(%s, line %d) unexpected "
"amount_msat_sub failure",
__PRETTY_FUNCTION__, __LINE__);
goto function_fail;
}
if (amount_msat_zero(diff))
continue;
diff = amount_msat_min(diff, deficit);
if (!amount_msat_sub(&deficit, deficit, diff)) {
// this should never happen
if (fail)
*fail = tal_fmt(ctx,
"(%s, line %d) unexpected "
"amount_msat_sub failure",
__PRETTY_FUNCTION__, __LINE__);
goto function_fail;
}
if (!amount_msat_add(&delivers, delivers, diff)) {
if (fail)
*fail = tal_fmt(
ctx,
"(%s, line %d) amount_msat_add overflow",
__PRETTY_FUNCTION__, __LINE__);
goto function_fail;
}
if (!flow_complete(this_ctx, flows[i], gossmap, chan_extra_map,
delivers, &errmsg)) {
if (fail)
*fail = tal_fmt(
ctx,
"(%s, line %d) flow_complete failed: %s",
__PRETTY_FUNCTION__, __LINE__, errmsg);
goto function_fail;
}
}
if (!check_liquidity_bounds(flows, gossmap, chan_extra_map)) {
// this should not happen if our algorithm is correct
if (fail)
*fail = tal_fmt(ctx,
"(%s, line %d) liquidity bounds not "
"satisfied or failed check",
__PRETTY_FUNCTION__, __LINE__);
goto function_fail;
}
if (!flow_set_delivers(amount_allocated, flows)) {
if (fail)
*fail = tal_fmt(
ctx, "(%s, line %d) flow_set_delivers failed",
__PRETTY_FUNCTION__, __LINE__);
goto function_fail;
}
function_success:
tal_free(this_ctx);
return true;
function_fail:
tal_free(this_ctx);
return false;
}
/* This helper function preserves the uncertainty network invariant after the
* knowledge is updated. It assumes that the (channel,!dir) knowledge is
* correct. */
@ -811,6 +1176,14 @@ size_t commit_flowset(const tal_t *ctx, const struct gossmap *gossmap,
}
/* 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.
@ -833,14 +1206,25 @@ bool flow_complete(const tal_t *ctx, struct flow *flow,
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");
*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;
}

View file

@ -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 */

View file

@ -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;i<tal_count(flows);++i)
/* Establish ownership. */
for(size_t i=0;i<tal_count(flows);++i)
{
flows[i] = tal_steal(flows,flows[i]);
assert(flows[i]);
}
tal_free(this_ctx);
return flows;
@ -1644,6 +1653,7 @@ struct flow **minflow(const tal_t *ctx, struct gossmap *gossmap,
combine_cost_function(linear_network,residual_network,mu);
/* We solve a linear MCF problem. */
if(!optimize_mcf(this_ctx, dijkstra,linear_network,residual_network,
source_idx,target_idx,pay_amount_sats, &errmsg))
{
@ -1655,6 +1665,9 @@ struct flow **minflow(const tal_t *ctx, struct gossmap *gossmap,
}
struct flow **flow_paths;
/* 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);

View file

@ -10,6 +10,11 @@
#include <plugins/renepay/pay.h>
#include <plugins/renepay/pay_flow.h>
// 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)

View file

@ -5,7 +5,6 @@
#include <common/json_stream.h>
#include <common/memleak.h>
#include <plugins/renepay/pay.h>
#include <plugins/renepay/pay_flow.h>
#include <plugins/renepay/payment.h>
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);
}
}

View file

@ -3,6 +3,10 @@
#include "config.h"
#include <common/gossmap.h>
#include <plugins/libplugin.h>
#include <plugins/renepay/pay_flow.h>
/* 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,

View file

@ -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();
}

View file

@ -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. */

View file

@ -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')