mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-22 22:45:27 +01:00
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:
parent
a4f92eac81
commit
b0054aad46
9 changed files with 681 additions and 82 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -850,8 +1234,8 @@ bool flow_complete(const tal_t *ctx, struct flow *flow,
|
|||
h->htlc_total, delivered, &errmsg);
|
||||
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;
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
||||
/* 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),
|
||||
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,
|
||||
"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,
|
||||
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));
|
||||
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,
|
||||
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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Add table
Reference in a new issue