mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 18:11:28 +01:00
renepay: bug fix on the routebuilder
The route-builder checks the liquidity bounds of each route one at a time. Every route that satisfy the contraints is recorded in the uncertainty network and produces an HTLC burden on the channels it uses, so that the following routes cannot count on the same liquidity twice.
This commit is contained in:
parent
da00cae30b
commit
4adf9b4080
@ -1435,15 +1435,8 @@ get_flow_paths(const tal_t *ctx, const struct gossmap *gossmap,
|
||||
goto function_fail;
|
||||
}
|
||||
excess = amount_msat(0);
|
||||
fp->amount = delivered;
|
||||
|
||||
if (!flow_assign_delivery(fp, gossmap, chan_extra_map,
|
||||
delivered)) {
|
||||
if (fail)
|
||||
*fail =
|
||||
tal_fmt(ctx, "failed to add final "
|
||||
"amount to flow");
|
||||
goto function_fail;
|
||||
}
|
||||
fp->success_prob =
|
||||
flow_probability(fp, gossmap, chan_extra_map);
|
||||
if (fp->success_prob < 0) {
|
||||
|
@ -20,105 +20,113 @@ static void uncertainty_remove_routes(struct uncertainty *uncertainty,
|
||||
uncertainty_remove_htlcs(uncertainty, routes[i]);
|
||||
}
|
||||
|
||||
// TODO: check
|
||||
/* Shave-off amounts that do not meet the liquidity constraints. Disable
|
||||
* channels that produce an htlc_max bottleneck. */
|
||||
static struct flow **flows_adjust_htlcmax_constraints(
|
||||
const tal_t *ctx, struct flow **flows TAKES, struct gossmap *gossmap,
|
||||
struct chan_extra_map *chan_extra_map,
|
||||
bitmap *disabled_bitmap)
|
||||
static enum renepay_errorcode
|
||||
flow_adjust_htlcmax_constraints(struct flow *flow, struct gossmap *gossmap,
|
||||
struct chan_extra_map *chan_extra_map,
|
||||
bitmap *disabled_bitmap)
|
||||
{
|
||||
struct flow **new_flows = tal_arr(ctx, struct flow *, 0);
|
||||
assert(flow);
|
||||
assert(gossmap);
|
||||
assert(chan_extra_map);
|
||||
assert(disabled_bitmap);
|
||||
assert(!amount_msat_zero(flow_delivers(flow)));
|
||||
|
||||
enum renepay_errorcode errorcode;
|
||||
|
||||
for (size_t i = 0; i < tal_count(flows); i++) {
|
||||
struct flow *f = flows[i];
|
||||
struct amount_msat max_deliverable;
|
||||
const struct gossmap_chan *bad_channel;
|
||||
struct amount_msat max_deliverable;
|
||||
const struct gossmap_chan *bad_channel;
|
||||
|
||||
errorcode = flow_maximum_deliverable(
|
||||
&max_deliverable, f, gossmap, chan_extra_map, &bad_channel);
|
||||
errorcode = flow_maximum_deliverable(&max_deliverable, flow, gossmap,
|
||||
chan_extra_map, &bad_channel);
|
||||
|
||||
if (!errorcode) {
|
||||
// no issues
|
||||
f->amount =
|
||||
amount_msat_min(flow_delivers(f), max_deliverable);
|
||||
if (!errorcode) {
|
||||
assert(!amount_msat_zero(max_deliverable));
|
||||
|
||||
tal_arr_expand(&new_flows, f);
|
||||
} else if (errorcode == RENEPAY_BAD_CHANNEL) {
|
||||
// this is a channel that we can disable
|
||||
// FIXME: log this error?
|
||||
bitmap_set_bit(disabled_bitmap,
|
||||
gossmap_chan_idx(gossmap, bad_channel));
|
||||
continue;
|
||||
} else {
|
||||
// we had an unexpected error
|
||||
goto function_fail;
|
||||
}
|
||||
// no issues
|
||||
flow->amount =
|
||||
amount_msat_min(flow_delivers(flow), max_deliverable);
|
||||
|
||||
return errorcode;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < tal_count(new_flows); i++) {
|
||||
tal_steal(new_flows, new_flows[i]);
|
||||
if (errorcode == RENEPAY_BAD_CHANNEL) {
|
||||
// this is a channel that we can disable
|
||||
// FIXME: log this error?
|
||||
bitmap_set_bit(disabled_bitmap,
|
||||
gossmap_chan_idx(gossmap, bad_channel));
|
||||
}
|
||||
|
||||
if (taken(flows))
|
||||
tal_free(flows);
|
||||
return new_flows;
|
||||
|
||||
function_fail:
|
||||
if (taken(flows))
|
||||
tal_free(flows);
|
||||
return tal_free(new_flows);
|
||||
// we had an unexpected error
|
||||
return errorcode;
|
||||
}
|
||||
|
||||
// TODO: check
|
||||
/* Disable channels that produce an htlc_min bottleneck. */
|
||||
static struct flow **flows_adjust_htlcmin_constraints(
|
||||
const tal_t *ctx, struct flow **flows TAKES, struct gossmap *gossmap,
|
||||
struct chan_extra_map *chan_extra_map,
|
||||
bitmap *disabled_bitmap)
|
||||
static enum renepay_errorcode
|
||||
route_check_constraints(struct route *route, struct gossmap *gossmap,
|
||||
struct uncertainty *uncertainty,
|
||||
bitmap *disabled_bitmap)
|
||||
{
|
||||
struct flow **new_flows = tal_arr(ctx, struct flow *, 0);
|
||||
enum renepay_errorcode errorcode;
|
||||
struct amount_msat max_deliverable;
|
||||
assert(route);
|
||||
assert(route->hops);
|
||||
const size_t pathlen = tal_count(route->hops);
|
||||
if (!amount_msat_eq(route->amount, route->hops[pathlen - 1].amount))
|
||||
return RENEPAY_PRECONDITION_ERROR;
|
||||
if (!amount_msat_eq(route->amount_sent, route->hops[0].amount))
|
||||
return RENEPAY_PRECONDITION_ERROR;
|
||||
|
||||
for (size_t i = 0; i < tal_count(flows); i++) {
|
||||
struct flow *f = flows[i];
|
||||
const struct gossmap_chan *bad_channel;
|
||||
for (size_t i = 0; i < pathlen; i++) {
|
||||
struct route_hop *hop = &route->hops[i];
|
||||
int dir = hop->direction;
|
||||
struct gossmap_chan *chan =
|
||||
gossmap_find_chan(gossmap, &hop->scid);
|
||||
assert(chan);
|
||||
struct chan_extra *ce =
|
||||
uncertainty_find_channel(uncertainty, hop->scid);
|
||||
|
||||
errorcode = flow_maximum_deliverable(
|
||||
&max_deliverable, f, gossmap, chan_extra_map, &bad_channel);
|
||||
|
||||
if (!errorcode) {
|
||||
// no issues
|
||||
f->amount =
|
||||
amount_msat_min(flow_delivers(f), max_deliverable);
|
||||
|
||||
tal_arr_expand(&new_flows, f);
|
||||
} else if (errorcode == RENEPAY_BAD_CHANNEL) {
|
||||
// this is a channel that we can disable
|
||||
// FIXME: log this error?
|
||||
// check that we stay within the htlc max and min limits
|
||||
if (amount_msat_greater(hop->amount,
|
||||
channel_htlc_max(chan, dir)) ||
|
||||
amount_msat_less(hop->amount,
|
||||
channel_htlc_min(chan, dir))) {
|
||||
bitmap_set_bit(disabled_bitmap,
|
||||
gossmap_chan_idx(gossmap, bad_channel));
|
||||
continue;
|
||||
} else {
|
||||
// we had an unexpected error
|
||||
goto function_fail;
|
||||
gossmap_chan_idx(gossmap, chan));
|
||||
return RENEPAY_BAD_CHANNEL;
|
||||
}
|
||||
|
||||
// check that the sum of all htlcs and this amount does not
|
||||
// exceed the maximum known by our knowledge
|
||||
struct amount_msat total_htlcs = ce->half[dir].htlc_total;
|
||||
if (!amount_msat_add(&total_htlcs, total_htlcs, hop->amount))
|
||||
return RENEPAY_AMOUNT_OVERFLOW;
|
||||
|
||||
if (amount_msat_greater(total_htlcs, ce->half[dir].known_max))
|
||||
return RENEPAY_UNEXPECTED;
|
||||
}
|
||||
return RENEPAY_NOERROR;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < tal_count(new_flows); i++) {
|
||||
tal_steal(new_flows, new_flows[i]);
|
||||
}
|
||||
static void tal_report_error(const tal_t *ctx, enum jsonrpc_errcode *ecode,
|
||||
const char **fail,
|
||||
enum jsonrpc_errcode error_value, const char *fmt,
|
||||
...)
|
||||
{
|
||||
tal_t *this_ctx = tal(ctx, tal_t);
|
||||
|
||||
if (taken(flows))
|
||||
tal_free(flows);
|
||||
return new_flows;
|
||||
va_list ap;
|
||||
const char *str;
|
||||
|
||||
function_fail:
|
||||
if (taken(flows))
|
||||
tal_free(flows);
|
||||
return tal_free(new_flows);
|
||||
va_start(ap, fmt);
|
||||
str = tal_vfmt(this_ctx, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (ecode)
|
||||
*ecode = error_value;
|
||||
|
||||
if (fail)
|
||||
*fail = tal_fmt(ctx, "%s", str);
|
||||
|
||||
this_ctx = tal_free(this_ctx);
|
||||
}
|
||||
|
||||
/* Routes are computed and saved in the payment for later use. */
|
||||
@ -151,16 +159,14 @@ struct route **get_routes(const tal_t *ctx,
|
||||
const double base_fee_penalty = payment_info->base_fee_penalty;
|
||||
const double prob_cost_factor = payment_info->prob_cost_factor;
|
||||
const unsigned int maxdelay = payment_info->maxdelay;
|
||||
bool delay_feefactor_updated = true;
|
||||
|
||||
bitmap *disabled_bitmap =
|
||||
tal_disabledmap_get_bitmap(this_ctx, disabledmap, gossmap);
|
||||
|
||||
if (!disabled_bitmap) {
|
||||
if (ecode)
|
||||
*ecode = PLUGIN_ERROR;
|
||||
if (fail)
|
||||
*fail =
|
||||
tal_fmt(ctx, "Failed to build disabled_bitmap.");
|
||||
tal_report_error(ctx, ecode, fail, PLUGIN_ERROR,
|
||||
"Failed to build disabled_bitmap.");
|
||||
goto function_fail;
|
||||
}
|
||||
|
||||
@ -179,20 +185,15 @@ struct route **get_routes(const tal_t *ctx,
|
||||
const struct gossmap_node *src, *dst;
|
||||
src = gossmap_find_node(gossmap, source);
|
||||
if (!src) {
|
||||
if (ecode)
|
||||
*ecode = PAY_ROUTE_NOT_FOUND;
|
||||
if (fail)
|
||||
*fail = tal_fmt(ctx, "We don't have any channels.");
|
||||
tal_report_error(ctx, ecode, fail, PAY_ROUTE_NOT_FOUND,
|
||||
"We don't have any channels.");
|
||||
goto function_fail;
|
||||
}
|
||||
dst = gossmap_find_node(gossmap, destination);
|
||||
if (!dst) {
|
||||
if (ecode)
|
||||
*ecode = PAY_ROUTE_NOT_FOUND;
|
||||
if (fail)
|
||||
*fail = tal_fmt(
|
||||
ctx,
|
||||
"Destination is unknown in the network gossip.");
|
||||
tal_report_error(
|
||||
ctx, ecode, fail, PAY_ROUTE_NOT_FOUND,
|
||||
"Destination is unknown in the network gossip.");
|
||||
goto function_fail;
|
||||
}
|
||||
|
||||
@ -200,8 +201,6 @@ struct route **get_routes(const tal_t *ctx,
|
||||
|
||||
while (!amount_msat_zero(amount_to_deliver)) {
|
||||
|
||||
printf("amount to deliver: %s\n", fmt_amount_msat(this_ctx, amount_to_deliver));
|
||||
|
||||
/* TODO: choose an algorithm, could be something like
|
||||
* payment->algorithm, that we set up based on command line
|
||||
* options and that can be changed according to some conditions
|
||||
@ -216,218 +215,185 @@ struct route **get_routes(const tal_t *ctx,
|
||||
disabled_bitmap, amount_to_deliver, feebudget,
|
||||
probability_budget, delay_feefactor,
|
||||
base_fee_penalty, prob_cost_factor, &errmsg);
|
||||
delay_feefactor_updated = false;
|
||||
|
||||
if (!flows) {
|
||||
if (ecode)
|
||||
*ecode = PAY_ROUTE_NOT_FOUND;
|
||||
|
||||
if (fail)
|
||||
*fail = tal_fmt(
|
||||
ctx,
|
||||
"minflow couldn't find a feasible flow: %s",
|
||||
errmsg);
|
||||
tal_report_error(
|
||||
ctx, ecode, fail, PAY_ROUTE_NOT_FOUND,
|
||||
"minflow couldn't find a feasible flow: %s",
|
||||
errmsg);
|
||||
goto function_fail;
|
||||
}
|
||||
|
||||
/* In previous implementations we would search for
|
||||
* htlcmax/htlcmin violations and disable those channels and
|
||||
* then redo the MCF computation. Now we instead remove only
|
||||
* those flows for which there is a constraint violation and
|
||||
* mark the involved channels as disabled for the next MCF
|
||||
* iteration. */
|
||||
flows = flows_adjust_htlcmax_constraints(
|
||||
this_ctx, take(flows), gossmap,
|
||||
uncertainty_get_chan_extra_map(uncertainty),
|
||||
disabled_bitmap);
|
||||
if (!flows) {
|
||||
if (ecode)
|
||||
*ecode = PAY_ROUTE_NOT_FOUND;
|
||||
enum renepay_errorcode errorcode;
|
||||
for (size_t i = 0; i < tal_count(flows); i++) {
|
||||
|
||||
if (fail)
|
||||
*fail = tal_fmt(
|
||||
ctx,
|
||||
"failed to adjust htlcmax constraints.");
|
||||
// do we overpay?
|
||||
if (amount_msat_greater(flows[i]->amount,
|
||||
amount_to_deliver)) {
|
||||
// should not happen
|
||||
tal_report_error(
|
||||
ctx, ecode, fail, PLUGIN_ERROR,
|
||||
"%s: flow is delivering to destination "
|
||||
"(%s) more than requested (%s)",
|
||||
__PRETTY_FUNCTION__,
|
||||
fmt_amount_msat(this_ctx, flows[i]->amount),
|
||||
fmt_amount_msat(this_ctx,
|
||||
amount_to_deliver));
|
||||
goto function_fail;
|
||||
}
|
||||
|
||||
goto function_fail;
|
||||
}
|
||||
// fees considered, remove the least amount as to fit in
|
||||
// with the htlcmax constraints
|
||||
errorcode = flow_adjust_htlcmax_constraints(
|
||||
flows[i], gossmap,
|
||||
uncertainty_get_chan_extra_map(uncertainty),
|
||||
disabled_bitmap);
|
||||
if (errorcode == RENEPAY_BAD_CHANNEL)
|
||||
// we handle a bad channel error by disabling
|
||||
// it, infinite loops are avoided since we have
|
||||
// everytime less and less channels
|
||||
continue;
|
||||
if (errorcode) {
|
||||
// any other error is bad
|
||||
tal_report_error(
|
||||
ctx, ecode, fail, PLUGIN_ERROR,
|
||||
"flow_adjust_htlcmax_constraints returned "
|
||||
"errorcode: %s",
|
||||
renepay_errorcode_name(errorcode));
|
||||
goto function_fail;
|
||||
}
|
||||
|
||||
flows = flows_adjust_htlcmin_constraints(
|
||||
this_ctx, take(flows), gossmap,
|
||||
uncertainty_get_chan_extra_map(uncertainty),
|
||||
disabled_bitmap);
|
||||
if (!flows) {
|
||||
if (ecode)
|
||||
*ecode = PAY_ROUTE_NOT_FOUND;
|
||||
// a bound check, we shouldn't deliver a zero amount, it
|
||||
// would mean a bug somewhere
|
||||
if (amount_msat_zero(flows[i]->amount)) {
|
||||
tal_report_error(ctx, ecode, fail, PLUGIN_ERROR,
|
||||
"flow conveys a zero amount");
|
||||
goto function_fail;
|
||||
}
|
||||
|
||||
if (fail)
|
||||
*fail = tal_fmt(
|
||||
ctx,
|
||||
"failed to adjust htlcmin constraints.");
|
||||
const double prob = flow_probability(
|
||||
flows[i], gossmap,
|
||||
uncertainty_get_chan_extra_map(uncertainty));
|
||||
if (prob < 0) {
|
||||
// should not happen
|
||||
tal_report_error(ctx, ecode, fail, PLUGIN_ERROR,
|
||||
"flow_probability failed");
|
||||
goto function_fail;
|
||||
}
|
||||
|
||||
goto function_fail;
|
||||
}
|
||||
// TODO: check issue #7136
|
||||
// this flow seems good, build me a route
|
||||
struct route *r = flow_to_route(
|
||||
this_ctx, groupid, *next_partid,
|
||||
payment_info->payment_hash,
|
||||
payment_info->final_cltv, gossmap, flows[i]);
|
||||
|
||||
/* Check the fee limits. */
|
||||
/* TODO: review this, only flows with non-zero amount */
|
||||
struct amount_msat fee;
|
||||
if (!flowset_fee(&fee, flows)) {
|
||||
if (ecode)
|
||||
*ecode = PLUGIN_ERROR;
|
||||
if (!r) {
|
||||
tal_report_error(
|
||||
ctx, ecode, fail, PLUGIN_ERROR,
|
||||
"%s failed to build route from flow.",
|
||||
__PRETTY_FUNCTION__);
|
||||
goto function_fail;
|
||||
}
|
||||
|
||||
if (fail)
|
||||
*fail = tal_fmt(ctx, "flowset_fee failed");
|
||||
goto function_fail;
|
||||
}
|
||||
if (amount_msat_greater(fee, feebudget)) {
|
||||
if (ecode)
|
||||
*ecode = PAY_ROUTE_TOO_EXPENSIVE;
|
||||
const struct amount_msat fee = route_fees(r);
|
||||
const struct amount_msat delivering = route_delivers(r);
|
||||
|
||||
if (fail)
|
||||
*fail = tal_fmt(
|
||||
ctx,
|
||||
// are we still within the fee budget?
|
||||
if (amount_msat_greater(fee, feebudget)) {
|
||||
tal_report_error(
|
||||
ctx, ecode, fail, PAY_ROUTE_TOO_EXPENSIVE,
|
||||
"Fee exceeds our fee budget, fee=%s "
|
||||
"(feebudget=%s)",
|
||||
fmt_amount_msat(this_ctx, fee),
|
||||
fmt_amount_msat(this_ctx, feebudget));
|
||||
goto function_fail;
|
||||
}
|
||||
|
||||
/* Check the CLTV delay */
|
||||
/* TODO: review this, only flows with non-zero amounts */
|
||||
const u64 delay = flows_worst_delay(flows) + payment_info->final_cltv;
|
||||
if (delay > maxdelay) {
|
||||
/* FIXME: What is a sane limit? */
|
||||
if (delay_feefactor > 1000) {
|
||||
if (ecode)
|
||||
*ecode = PAY_ROUTE_TOO_EXPENSIVE;
|
||||
if (fail)
|
||||
*fail = tal_fmt(
|
||||
ctx,
|
||||
"CLTV delay exceeds our CLTV "
|
||||
"budget, delay=%" PRIu64
|
||||
"(maxdelay=%u)",
|
||||
delay, maxdelay);
|
||||
goto function_fail;
|
||||
}
|
||||
|
||||
delay_feefactor *= 2;
|
||||
continue; // retry
|
||||
}
|
||||
// check the CLTV delay does not exceed our settings
|
||||
const unsigned int delay = route_delay(r);
|
||||
if (delay > maxdelay) {
|
||||
if (!delay_feefactor_updated) {
|
||||
delay_feefactor *= 2;
|
||||
delay_feefactor_updated = true;
|
||||
}
|
||||
|
||||
/* Compute the flows probability */
|
||||
/* TODO: review this, only flows with non-zero amounts */
|
||||
double prob = flowset_probability(
|
||||
this_ctx, flows, gossmap,
|
||||
uncertainty_get_chan_extra_map(uncertainty), NULL);
|
||||
if (prob < 0) {
|
||||
if (ecode)
|
||||
*ecode = PLUGIN_ERROR;
|
||||
if (fail)
|
||||
*fail =
|
||||
tal_fmt(ctx, "flowset_probability failed");
|
||||
goto function_fail;
|
||||
}
|
||||
|
||||
struct amount_msat delivering;
|
||||
if (!flowset_delivers(&delivering, flows)) {
|
||||
if (ecode)
|
||||
*ecode = PLUGIN_ERROR;
|
||||
|
||||
if (fail)
|
||||
*fail = tal_fmt(ctx, "flowset_delivers failed");
|
||||
goto function_fail;
|
||||
}
|
||||
|
||||
/* OK, we are happy with these flows: convert to
|
||||
* routes in the current payment. */
|
||||
delivering = AMOUNT_MSAT(0);
|
||||
fee = AMOUNT_MSAT(0);
|
||||
// TODO check ownership of these routes
|
||||
for (size_t i = 0; i < tal_count(flows); i++) {
|
||||
struct route *r = flow_to_route(
|
||||
ctx, groupid,
|
||||
*next_partid, payment_info->payment_hash,
|
||||
payment_info->final_cltv, gossmap, flows[i]);
|
||||
if (!r) {
|
||||
/* TODO: what could have gone wrong? */
|
||||
/* FIXME: What is a sane limit? */
|
||||
if (delay_feefactor > 1000) {
|
||||
tal_report_error(
|
||||
ctx, ecode, fail,
|
||||
PAY_ROUTE_TOO_EXPENSIVE,
|
||||
"CLTV delay exceeds our CLTV "
|
||||
"budget, delay=%u (maxdelay=%u)",
|
||||
delay, maxdelay);
|
||||
goto function_fail;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
(*next_partid)++;
|
||||
uncertainty_commit_htlcs(uncertainty, r);
|
||||
tal_arr_expand(&routes, r);
|
||||
|
||||
struct amount_msat route_fee = route_fees(r),
|
||||
route_deliver = route_delivers(r);
|
||||
// check that the route satisfy all constraints
|
||||
errorcode = route_check_constraints(
|
||||
r, gossmap, uncertainty, disabled_bitmap);
|
||||
|
||||
if (!amount_msat_add(&fee, fee, route_fee) ||
|
||||
!amount_msat_add(&delivering, delivering,
|
||||
route_deliver)) {
|
||||
if (ecode)
|
||||
*ecode = PLUGIN_ERROR;
|
||||
|
||||
if (fail)
|
||||
*fail = tal_fmt(
|
||||
ctx,
|
||||
"%s (line %d) amount_msat "
|
||||
"arithmetic overflow.",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
if (errorcode == RENEPAY_BAD_CHANNEL)
|
||||
continue;
|
||||
if (errorcode) {
|
||||
// any other error is bad
|
||||
tal_report_error(
|
||||
ctx, ecode, fail, PLUGIN_ERROR,
|
||||
"route_check_constraints returned "
|
||||
"errorcode: %s",
|
||||
renepay_errorcode_name(errorcode));
|
||||
goto function_fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* For the next iteration get me the amount_to_deliver */
|
||||
if (!amount_msat_sub(&amount_to_deliver, amount_to_deliver,
|
||||
delivering)) {
|
||||
/* In the next iteration we search routes that allocate
|
||||
*amount_to_deliver - delivering If we have delivering >
|
||||
*amount_to_deliver it means we have made a mistake
|
||||
*somewhere.
|
||||
*/
|
||||
if (ecode)
|
||||
*ecode = PLUGIN_ERROR;
|
||||
// update the fee budget
|
||||
if (!amount_msat_sub(&feebudget, feebudget, fee)) {
|
||||
// should never happen
|
||||
tal_report_error(
|
||||
ctx, ecode, fail, PLUGIN_ERROR,
|
||||
"%s routing fees (%s) exceed fee "
|
||||
"budget (%s).",
|
||||
__PRETTY_FUNCTION__,
|
||||
fmt_amount_msat(this_ctx, fee),
|
||||
fmt_amount_msat(this_ctx, feebudget));
|
||||
goto function_fail;
|
||||
}
|
||||
|
||||
if (fail)
|
||||
*fail = tal_fmt(
|
||||
ctx,
|
||||
"%s (line %d) delivering to destination "
|
||||
"(%s) is more than requested (%s)",
|
||||
__PRETTY_FUNCTION__, __LINE__,
|
||||
// update the amount that we deliver
|
||||
if (!amount_msat_sub(&amount_to_deliver,
|
||||
amount_to_deliver, delivering)) {
|
||||
// should never happen
|
||||
tal_report_error(
|
||||
ctx, ecode, fail, PLUGIN_ERROR,
|
||||
"%s: route delivering to destination (%s) "
|
||||
"is more than requested (%s)",
|
||||
__PRETTY_FUNCTION__,
|
||||
fmt_amount_msat(this_ctx, delivering),
|
||||
fmt_amount_msat(this_ctx,
|
||||
amount_to_deliver));
|
||||
goto function_fail;
|
||||
}
|
||||
goto function_fail;
|
||||
}
|
||||
|
||||
/* For the next iteration get me the feebudget */
|
||||
if (!amount_msat_sub(&feebudget, feebudget, fee)) {
|
||||
if (ecode)
|
||||
*ecode = PLUGIN_ERROR;
|
||||
// update the probability target
|
||||
if (prob < 1e-10) {
|
||||
// probability is too small for division
|
||||
probability_budget = 1.0;
|
||||
} else {
|
||||
/* prob here is a conditional probability, the
|
||||
* next flow will have a conditional
|
||||
* probability prob2 and we would like that
|
||||
* prob*prob2 >= probability_budget hence
|
||||
* probability_budget/prob becomes the next
|
||||
* iteration's target. */
|
||||
probability_budget =
|
||||
MIN(1.0, probability_budget / prob);
|
||||
}
|
||||
|
||||
if (fail)
|
||||
*fail = tal_fmt(
|
||||
ctx,
|
||||
"%s (line %d) routing fees (%s) exceed fee "
|
||||
"budget (%s).",
|
||||
__PRETTY_FUNCTION__, __LINE__,
|
||||
fmt_amount_msat(this_ctx, fee),
|
||||
fmt_amount_msat(this_ctx, feebudget));
|
||||
goto function_fail;
|
||||
}
|
||||
|
||||
/* For the next iteration get me the probability_budget */
|
||||
if (prob < 1e-10) {
|
||||
/* this last flow probability is too small for division
|
||||
*/
|
||||
probability_budget = 1.0;
|
||||
} else {
|
||||
/* prob here is a conditional probability, the next
|
||||
* round of flows will have a conditional probability
|
||||
* prob2 and we would like that prob*prob2 >=
|
||||
* probability_budget hence probability_budget/prob
|
||||
* becomes the next iteration's target. */
|
||||
probability_budget =
|
||||
MIN(1.0, probability_budget / prob);
|
||||
// route added
|
||||
(*next_partid)++;
|
||||
uncertainty_commit_htlcs(uncertainty, r);
|
||||
tal_arr_expand(&routes, r);
|
||||
}
|
||||
}
|
||||
|
||||
|
361
plugins/renepay/test/run-bottleneck.c
Normal file
361
plugins/renepay/test/run-bottleneck.c
Normal file
@ -0,0 +1,361 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "../errorcodes.c"
|
||||
#include "../flow.c"
|
||||
#include "../mcf.c"
|
||||
#include "../uncertainty.c"
|
||||
#include "../disabledmap.c"
|
||||
#include "../route.c"
|
||||
#include "../routebuilder.c"
|
||||
|
||||
#include <bitcoin/chainparams.h>
|
||||
#include <bitcoin/preimage.h>
|
||||
#include <ccan/str/hex/hex.h>
|
||||
#include <common/gossip_store.h>
|
||||
#include <common/setup.h>
|
||||
#include <common/utils.h>
|
||||
#include <gossipd/gossip_store_wiregen.h>
|
||||
#include <sodium/randombytes.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <wire/peer_wiregen.h>
|
||||
|
||||
static u8 empty_map[] = {10};
|
||||
|
||||
static const char *print_flows(const tal_t *ctx, const char *desc,
|
||||
const struct gossmap *gossmap,
|
||||
struct chan_extra_map *chan_extra_map,
|
||||
struct flow **flows)
|
||||
{
|
||||
tal_t *this_ctx = tal(ctx, tal_t);
|
||||
double tot_prob =
|
||||
flowset_probability(tmpctx, flows, gossmap, chan_extra_map, NULL);
|
||||
assert(tot_prob >= 0);
|
||||
char *buff = tal_fmt(ctx, "%s: %zu subflows, prob %2lf\n", desc,
|
||||
tal_count(flows), tot_prob);
|
||||
for (size_t i = 0; i < tal_count(flows); i++) {
|
||||
struct amount_msat fee, delivered;
|
||||
tal_append_fmt(&buff, " ");
|
||||
for (size_t j = 0; j < tal_count(flows[i]->path); j++) {
|
||||
struct short_channel_id scid =
|
||||
gossmap_chan_scid(gossmap, flows[i]->path[j]);
|
||||
tal_append_fmt(&buff, "%s%s", j ? "->" : "",
|
||||
fmt_short_channel_id(this_ctx, scid));
|
||||
}
|
||||
delivered = flows[i]->amount;
|
||||
if (!flow_fee(&fee, flows[i])) {
|
||||
abort();
|
||||
}
|
||||
tal_append_fmt(&buff, " prob %.2f, %s delivered with fee %s\n",
|
||||
flows[i]->success_prob,
|
||||
fmt_amount_msat(this_ctx, delivered),
|
||||
fmt_amount_msat(this_ctx, fee));
|
||||
}
|
||||
|
||||
tal_free(this_ctx);
|
||||
return buff;
|
||||
}
|
||||
|
||||
static const char *print_routes(const tal_t *ctx,
|
||||
struct route **routes)
|
||||
{
|
||||
tal_t *this_ctx = tal(ctx, tal_t);
|
||||
char *buff = tal_fmt(ctx, "%zu routes\n", tal_count(routes));
|
||||
for (size_t i = 0; i < tal_count(routes); i++) {
|
||||
struct amount_msat fee, delivered;
|
||||
|
||||
delivered = route_delivers(routes[i]);
|
||||
fee = route_fees(routes[i]);
|
||||
tal_append_fmt(&buff, " %s", fmt_route_path(this_ctx, routes[i]));
|
||||
tal_append_fmt(&buff, " %s delivered with fee %s\n",
|
||||
fmt_amount_msat(this_ctx, delivered),
|
||||
fmt_amount_msat(this_ctx, fee));
|
||||
}
|
||||
|
||||
tal_free(this_ctx);
|
||||
return buff;
|
||||
}
|
||||
|
||||
static void write_to_store(int store_fd, const u8 *msg)
|
||||
{
|
||||
struct gossip_hdr hdr;
|
||||
|
||||
hdr.flags = cpu_to_be16(0);
|
||||
hdr.len = cpu_to_be16(tal_count(msg));
|
||||
/* We don't actually check these! */
|
||||
hdr.crc = 0;
|
||||
hdr.timestamp = 0;
|
||||
assert(write(store_fd, &hdr, sizeof(hdr)) == sizeof(hdr));
|
||||
assert(write(store_fd, msg, tal_count(msg)) == tal_count(msg));
|
||||
}
|
||||
|
||||
static void add_connection(int store_fd,
|
||||
const struct node_id *from,
|
||||
const struct node_id *to,
|
||||
struct short_channel_id scid,
|
||||
struct amount_msat min,
|
||||
struct amount_msat max,
|
||||
u32 base_fee, s32 proportional_fee,
|
||||
u32 delay,
|
||||
struct amount_sat capacity)
|
||||
{
|
||||
secp256k1_ecdsa_signature dummy_sig;
|
||||
struct secret not_a_secret;
|
||||
struct pubkey dummy_key;
|
||||
u8 *msg;
|
||||
const struct node_id *ids[2];
|
||||
|
||||
/* So valgrind doesn't complain */
|
||||
memset(&dummy_sig, 0, sizeof(dummy_sig));
|
||||
memset(¬_a_secret, 1, sizeof(not_a_secret));
|
||||
pubkey_from_secret(¬_a_secret, &dummy_key);
|
||||
|
||||
if (node_id_cmp(from, to) > 0) {
|
||||
ids[0] = to;
|
||||
ids[1] = from;
|
||||
} else {
|
||||
ids[0] = from;
|
||||
ids[1] = to;
|
||||
}
|
||||
msg = towire_channel_announcement(tmpctx, &dummy_sig, &dummy_sig,
|
||||
&dummy_sig, &dummy_sig,
|
||||
/* features */ NULL,
|
||||
&chainparams->genesis_blockhash,
|
||||
scid,
|
||||
ids[0], ids[1],
|
||||
&dummy_key, &dummy_key);
|
||||
write_to_store(store_fd, msg);
|
||||
|
||||
msg = towire_gossip_store_channel_amount(tmpctx, capacity);
|
||||
write_to_store(store_fd, msg);
|
||||
|
||||
u8 flags = node_id_idx(from, to);
|
||||
|
||||
msg = towire_channel_update(tmpctx,
|
||||
&dummy_sig,
|
||||
&chainparams->genesis_blockhash,
|
||||
scid, 0,
|
||||
ROUTING_OPT_HTLC_MAX_MSAT,
|
||||
flags,
|
||||
delay,
|
||||
min,
|
||||
base_fee,
|
||||
proportional_fee,
|
||||
max);
|
||||
write_to_store(store_fd, msg);
|
||||
}
|
||||
|
||||
static void node_id_from_privkey(const struct privkey *p, struct node_id *id)
|
||||
{
|
||||
struct pubkey k;
|
||||
pubkey_from_privkey(p, &k);
|
||||
node_id_from_pubkey(id, &k);
|
||||
}
|
||||
|
||||
#define NUM_NODES 8
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int fd;
|
||||
char *gossfile;
|
||||
struct gossmap *gossmap;
|
||||
struct node_id nodes[NUM_NODES];
|
||||
|
||||
common_setup(argv[0]);
|
||||
chainparams = chainparams_for_network("regtest");
|
||||
|
||||
fd = tmpdir_mkstemp(tmpctx, "run-bottleneck.XXXXXX", &gossfile);
|
||||
assert(write(fd, empty_map, sizeof(empty_map)) == sizeof(empty_map));
|
||||
|
||||
gossmap = gossmap_load(tmpctx, gossfile, NULL);
|
||||
assert(gossmap);
|
||||
|
||||
for (size_t i = 0; i < NUM_NODES; i++) {
|
||||
struct privkey tmp;
|
||||
memset(&tmp, i+1, sizeof(tmp));
|
||||
node_id_from_privkey(&tmp, &nodes[i]);
|
||||
}
|
||||
|
||||
/* We will try a payment from 1 to 8, forcing a payment split between
|
||||
* two routes 1->2->4->5->6->8 and 1->3->4->5->7->8.
|
||||
* To force the split the total payment amount will be greater than the
|
||||
* channel 1-2 and 1-3 capacities. Then channel 4--5 will be a common
|
||||
* edge in the payment routes.
|
||||
*
|
||||
* MCF does not handle fees hence if the capacity of 4--5 is enough to
|
||||
* let the entire payment pass, we expect that minflow computes two
|
||||
* routes that are scaled down by get_route algorithm
|
||||
* to fit for the fee constraints.
|
||||
*
|
||||
* +--2--+ +--6--+
|
||||
* | | | |
|
||||
* 1 4---5 8
|
||||
* | | | |
|
||||
* +--3--+ +--7--+
|
||||
*
|
||||
* */
|
||||
struct short_channel_id scid;
|
||||
|
||||
assert(mk_short_channel_id(&scid, 1, 2, 0));
|
||||
add_connection(fd, &nodes[0], &nodes[1], scid,
|
||||
AMOUNT_MSAT(0),
|
||||
AMOUNT_MSAT(60 * 1000 * 1000),
|
||||
0, 0, 5,
|
||||
AMOUNT_SAT(60 * 1000));
|
||||
|
||||
assert(mk_short_channel_id(&scid, 1, 3, 0));
|
||||
add_connection(fd, &nodes[0], &nodes[2], scid,
|
||||
AMOUNT_MSAT(0),
|
||||
AMOUNT_MSAT(60 * 1000 * 1000),
|
||||
0, 0, 5,
|
||||
AMOUNT_SAT(60 * 1000));
|
||||
|
||||
assert(mk_short_channel_id(&scid, 2, 4, 0));
|
||||
add_connection(fd, &nodes[1], &nodes[3], scid,
|
||||
AMOUNT_MSAT(0),
|
||||
AMOUNT_MSAT(1000 * 1000 * 1000),
|
||||
0, 0, 5,
|
||||
AMOUNT_SAT(1000 * 1000));
|
||||
|
||||
assert(mk_short_channel_id(&scid, 3, 4, 0));
|
||||
add_connection(fd, &nodes[2], &nodes[3], scid,
|
||||
AMOUNT_MSAT(0),
|
||||
AMOUNT_MSAT(1000 * 1000 * 1000),
|
||||
0, 0, 5,
|
||||
AMOUNT_SAT(1000 * 1000));
|
||||
|
||||
assert(mk_short_channel_id(&scid, 4, 5, 0));
|
||||
add_connection(fd, &nodes[3], &nodes[4], scid,
|
||||
AMOUNT_MSAT(0),
|
||||
/* MCF cuts off at 95% of the conditional capacity, for
|
||||
* cap = 106k that means only 100.7k sats can be sent
|
||||
* through this channel. */
|
||||
AMOUNT_MSAT(106 * 1000 * 1000),
|
||||
0, 0, 5,
|
||||
AMOUNT_SAT(110 * 1000));
|
||||
|
||||
assert(mk_short_channel_id(&scid, 5, 6, 0));
|
||||
add_connection(fd, &nodes[4], &nodes[5], scid,
|
||||
AMOUNT_MSAT(0),
|
||||
AMOUNT_MSAT(1000 * 1000 * 1000),
|
||||
0, 100 * 1000 /* 10% */, 5,
|
||||
AMOUNT_SAT(1000 * 1000));
|
||||
|
||||
assert(mk_short_channel_id(&scid, 5, 7, 0));
|
||||
add_connection(fd, &nodes[4], &nodes[6], scid,
|
||||
AMOUNT_MSAT(0),
|
||||
AMOUNT_MSAT(1000 * 1000 * 1000),
|
||||
0, 100 * 1000 /* 10% */, 5,
|
||||
AMOUNT_SAT(1000 * 1000));
|
||||
|
||||
assert(mk_short_channel_id(&scid, 6, 8, 0));
|
||||
add_connection(fd, &nodes[5], &nodes[7], scid,
|
||||
AMOUNT_MSAT(0),
|
||||
AMOUNT_MSAT(1000 * 1000 * 1000),
|
||||
0, 0, 5,
|
||||
AMOUNT_SAT(1000 * 1000));
|
||||
|
||||
assert(mk_short_channel_id(&scid, 7, 8, 0));
|
||||
add_connection(fd, &nodes[6], &nodes[7], scid,
|
||||
AMOUNT_MSAT(0),
|
||||
AMOUNT_MSAT(1000 * 1000 * 1000),
|
||||
0, 0, 5,
|
||||
AMOUNT_SAT(1000 * 1000));
|
||||
|
||||
assert(gossmap_refresh(gossmap, NULL));
|
||||
struct uncertainty *uncertainty = uncertainty_new(tmpctx);
|
||||
int skipped_count =
|
||||
uncertainty_update(uncertainty, gossmap);
|
||||
assert(skipped_count==0);
|
||||
|
||||
bitmap *disabled = tal_arrz(
|
||||
tmpctx, bitmap, BITMAP_NWORDS(gossmap_max_chan_idx(gossmap)));
|
||||
|
||||
char *errmsg;
|
||||
struct flow **flows;
|
||||
flows =
|
||||
minflow(tmpctx, gossmap, gossmap_find_node(gossmap, &nodes[0]),
|
||||
gossmap_find_node(gossmap, &nodes[7]),
|
||||
uncertainty->chan_extra_map, disabled,
|
||||
/* Half the capacity */
|
||||
AMOUNT_MSAT(100 * 1000 * 1000),
|
||||
/* max_fee = */ AMOUNT_MSAT(20 * 1000 * 1000),
|
||||
/* min probability = */ 0.9,
|
||||
/* delay fee factor = */ 1e-6,
|
||||
/* base fee penalty */ 10,
|
||||
/* prob cost factor = */ 10, &errmsg);
|
||||
|
||||
if (!flows) {
|
||||
printf("Minflow has failed with: %s", errmsg);
|
||||
// assert(0 && "minflow failed");
|
||||
}
|
||||
|
||||
if(flows)
|
||||
printf("%s\n", print_flows(tmpctx, "Simple minflow", gossmap,
|
||||
uncertainty->chan_extra_map, flows));
|
||||
|
||||
struct preimage preimage;
|
||||
|
||||
struct amount_msat maxfee = AMOUNT_MSAT(20*1000*1000);
|
||||
struct payment_info pinfo;
|
||||
pinfo.invstr = NULL;
|
||||
pinfo.label = NULL;
|
||||
pinfo.description = NULL;
|
||||
pinfo.payment_secret = NULL;
|
||||
pinfo.payment_metadata = NULL;
|
||||
pinfo.routehints = NULL;
|
||||
pinfo.destination = nodes[7];
|
||||
pinfo.amount = AMOUNT_MSAT(100 * 1000 * 1000);
|
||||
|
||||
assert(amount_msat_add(&pinfo.maxspend, maxfee, pinfo.amount));
|
||||
pinfo.maxdelay = 100;
|
||||
pinfo.final_cltv = 5;
|
||||
|
||||
pinfo.start_time = time_now();
|
||||
pinfo.stop_time = timeabs_add(pinfo.start_time, time_from_sec(10000));
|
||||
|
||||
pinfo.base_fee_penalty = 1e-5;
|
||||
pinfo.prob_cost_factor = 1e-5;
|
||||
pinfo.delay_feefactor = 1e-6;
|
||||
pinfo.min_prob_success = 0.9;
|
||||
pinfo.use_shadow = false;
|
||||
|
||||
randombytes_buf(&preimage, sizeof(preimage));
|
||||
sha256(&pinfo.payment_hash, &preimage, sizeof(preimage));
|
||||
|
||||
// char hex_preimage[600], hex_sha256[600];
|
||||
// assert(hex_encode(preimage.r, sizeof(preimage.r), hex_preimage, sizeof(hex_preimage)));
|
||||
// assert(hex_encode(pinfo.payment_hash.u.u8, sizeof(pinfo.payment_hash), hex_sha256, sizeof(hex_sha256)));
|
||||
// printf("preimage: %s\npayment_hash: %s\n", hex_preimage, hex_sha256);
|
||||
|
||||
struct disabledmap *disabledmap = disabledmap_new(tmpctx);
|
||||
|
||||
enum jsonrpc_errcode errcode;
|
||||
const char *err_msg;
|
||||
|
||||
u64 groupid = 1;
|
||||
u64 next_partid=1;
|
||||
|
||||
struct route **routes = get_routes(
|
||||
/* ctx */tmpctx,
|
||||
/* payment */&pinfo,
|
||||
/* source */&nodes[0],
|
||||
/* destination */&nodes[7],
|
||||
/* gossmap */gossmap,
|
||||
/* uncertainty */uncertainty,
|
||||
disabledmap,
|
||||
/* amount */ pinfo.amount,
|
||||
/* feebudget */maxfee,
|
||||
&next_partid,
|
||||
groupid,
|
||||
&errcode,
|
||||
&err_msg);
|
||||
|
||||
if (!routes) {
|
||||
printf("get_route failed with error %d: %s", errcode, err_msg);
|
||||
}
|
||||
if(routes)
|
||||
printf("get_routes: %s\n", print_routes(tmpctx, routes));
|
||||
|
||||
common_shutdown();
|
||||
}
|
Loading…
Reference in New Issue
Block a user