mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-22 06:41:44 +01:00
I used `amount_msat_eq(x, AMOUNT_MSAT(0))` because I forgot this function existed. I probably missed it because the name is surprising, so add "is" in there to make it clear it's a boolean function. You'll note almost all the places which did use it are Eduardo's and Lisa's code, so maybe it's just me. Fix up a few places which I could use it, too. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
428 lines
12 KiB
C
428 lines
12 KiB
C
#include "config.h"
|
|
#include <ccan/bitmap/bitmap.h>
|
|
#include <plugins/renepay/mcf.h>
|
|
#include <plugins/renepay/routebuilder.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
static void uncertainty_remove_routes(struct uncertainty *uncertainty,
|
|
struct route **routes)
|
|
{
|
|
const size_t N = tal_count(routes);
|
|
for (size_t i = 0; i < N; i++)
|
|
uncertainty_remove_htlcs(uncertainty, routes[i]);
|
|
}
|
|
|
|
/* Shave-off amounts that do not meet the liquidity constraints. Disable
|
|
* channels that produce an htlc_max bottleneck. */
|
|
static enum renepay_errorcode
|
|
flow_adjust_htlcmax_constraints(struct flow *flow, struct gossmap *gossmap,
|
|
struct chan_extra_map *chan_extra_map,
|
|
bitmap *disabled_bitmap)
|
|
{
|
|
assert(flow);
|
|
assert(gossmap);
|
|
assert(chan_extra_map);
|
|
assert(disabled_bitmap);
|
|
assert(!amount_msat_is_zero(flow_delivers(flow)));
|
|
|
|
enum renepay_errorcode errorcode;
|
|
|
|
struct amount_msat max_deliverable;
|
|
const struct gossmap_chan *bad_channel;
|
|
|
|
errorcode = flow_maximum_deliverable(&max_deliverable, flow, gossmap,
|
|
chan_extra_map, &bad_channel);
|
|
|
|
if (!errorcode) {
|
|
assert(!amount_msat_is_zero(max_deliverable));
|
|
|
|
// no issues
|
|
flow->amount =
|
|
amount_msat_min(flow_delivers(flow), max_deliverable);
|
|
|
|
return errorcode;
|
|
}
|
|
|
|
if (errorcode == RENEPAY_BAD_CHANNEL) {
|
|
// this is a channel that we can disable
|
|
// FIXME: log this error? disabling both directions?
|
|
bitmap_set_bit(disabled_bitmap,
|
|
gossmap_chan_idx(gossmap, bad_channel) * 2 + 0);
|
|
bitmap_set_bit(disabled_bitmap,
|
|
gossmap_chan_idx(gossmap, bad_channel) * 2 + 1);
|
|
}
|
|
|
|
// we had an unexpected error
|
|
return errorcode;
|
|
}
|
|
|
|
static enum renepay_errorcode
|
|
route_check_constraints(struct route *route, struct gossmap *gossmap,
|
|
struct uncertainty *uncertainty,
|
|
bitmap *disabled_bitmap)
|
|
{
|
|
assert(route);
|
|
assert(route->hops);
|
|
const size_t pathlen = tal_count(route->hops);
|
|
if (!amount_msat_eq(route->amount, route->hops[pathlen - 1].amount))
|
|
return RENEPAY_PRECONDITION_ERROR;
|
|
if (!amount_msat_eq(route->amount_sent, route->hops[0].amount))
|
|
return RENEPAY_PRECONDITION_ERROR;
|
|
|
|
for (size_t i = 0; i < pathlen; i++) {
|
|
struct route_hop *hop = &route->hops[i];
|
|
int dir = hop->direction;
|
|
struct gossmap_chan *chan =
|
|
gossmap_find_chan(gossmap, &hop->scid);
|
|
assert(chan);
|
|
struct chan_extra *ce =
|
|
uncertainty_find_channel(uncertainty, hop->scid);
|
|
|
|
// check that we stay within the htlc max and min limits
|
|
if (amount_msat_greater(hop->amount,
|
|
gossmap_chan_htlc_max(chan, dir)) ||
|
|
amount_msat_less(hop->amount,
|
|
gossmap_chan_htlc_min(chan, dir))) {
|
|
bitmap_set_bit(disabled_bitmap,
|
|
gossmap_chan_idx(gossmap, chan) * 2 +
|
|
dir);
|
|
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;
|
|
}
|
|
|
|
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);
|
|
|
|
va_list ap;
|
|
const char *str;
|
|
|
|
va_start(ap, fmt);
|
|
str = tal_vfmt(this_ctx, fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (ecode)
|
|
*ecode = error_value;
|
|
|
|
if (fail)
|
|
*fail = tal_fmt(ctx, "%s", str);
|
|
|
|
this_ctx = tal_free(this_ctx);
|
|
}
|
|
|
|
/* Routes are computed and saved in the payment for later use. */
|
|
struct route **get_routes(const tal_t *ctx,
|
|
struct payment_info *payment_info,
|
|
|
|
const struct node_id *source,
|
|
const struct node_id *destination,
|
|
struct gossmap *gossmap,
|
|
struct uncertainty *uncertainty,
|
|
struct disabledmap *disabledmap,
|
|
|
|
struct amount_msat amount_to_deliver,
|
|
struct amount_msat feebudget,
|
|
|
|
u64 *next_partid,
|
|
u64 groupid,
|
|
|
|
enum jsonrpc_errcode *ecode,
|
|
const char **fail)
|
|
{
|
|
assert(gossmap);
|
|
assert(uncertainty);
|
|
|
|
const tal_t *this_ctx = tal(ctx, tal_t);
|
|
struct route **routes = tal_arr(ctx, struct route *, 0);
|
|
|
|
double probability_budget = payment_info->min_prob_success;
|
|
const double base_probability = payment_info->base_prob_success;
|
|
double delay_feefactor = payment_info->delay_feefactor;
|
|
const double base_fee_penalty = payment_info->base_fee_penalty;
|
|
const double prob_cost_factor = payment_info->prob_cost_factor;
|
|
const unsigned int maxdelay = payment_info->maxdelay;
|
|
bool delay_feefactor_updated = true;
|
|
|
|
bitmap *disabled_bitmap =
|
|
tal_disabledmap_get_bitmap(this_ctx, disabledmap, gossmap);
|
|
|
|
if (!disabled_bitmap) {
|
|
tal_report_error(ctx, ecode, fail, PLUGIN_ERROR,
|
|
"Failed to build disabled_bitmap.");
|
|
goto function_fail;
|
|
}
|
|
if (amount_msat_is_zero(amount_to_deliver)) {
|
|
tal_report_error(ctx, ecode, fail, PLUGIN_ERROR,
|
|
"amount to deliver is zero");
|
|
goto function_fail;
|
|
}
|
|
|
|
/* Also disable every channel that we don't have in the chan_extra_map.
|
|
* We might have channels in the gossmap that are not usable for
|
|
* probability computations for example if we don't know their capacity.
|
|
* We can tell the solver to ignore those channels by disabling them
|
|
* here.
|
|
*/
|
|
for (struct gossmap_chan *chan = gossmap_first_chan(gossmap); chan;
|
|
chan = gossmap_next_chan(gossmap, chan)) {
|
|
const u32 chan_id = gossmap_chan_idx(gossmap, chan);
|
|
struct short_channel_id scid = gossmap_chan_scid(gossmap, chan);
|
|
struct chan_extra *ce =
|
|
chan_extra_map_get(uncertainty->chan_extra_map, scid);
|
|
if (!ce) {
|
|
bitmap_set_bit(disabled_bitmap, chan_id * 2 + 0);
|
|
bitmap_set_bit(disabled_bitmap, chan_id * 2 + 1);
|
|
}
|
|
}
|
|
|
|
const struct gossmap_node *src, *dst;
|
|
src = gossmap_find_node(gossmap, source);
|
|
if (!src) {
|
|
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) {
|
|
tal_report_error(
|
|
ctx, ecode, fail, PAY_ROUTE_NOT_FOUND,
|
|
"Destination is unknown in the network gossip.");
|
|
goto function_fail;
|
|
}
|
|
|
|
char *errmsg;
|
|
|
|
while (!amount_msat_is_zero(amount_to_deliver)) {
|
|
|
|
/* TODO: choose an algorithm, could be something like
|
|
* payment->algorithm, that we set up based on command line
|
|
* options and that can be changed according to some conditions
|
|
* met during the payment process, eg. add "select_solver" pay
|
|
* mod. */
|
|
/* TODO: use uncertainty instead of chan_extra */
|
|
|
|
/* Min. Cost Flow algorithm to find optimal flows. */
|
|
struct flow **flows =
|
|
minflow(this_ctx, gossmap, src, dst,
|
|
uncertainty_get_chan_extra_map(uncertainty),
|
|
disabled_bitmap, amount_to_deliver, feebudget,
|
|
probability_budget,
|
|
base_probability,
|
|
delay_feefactor,
|
|
base_fee_penalty,
|
|
prob_cost_factor, &errmsg);
|
|
delay_feefactor_updated = false;
|
|
|
|
if (!flows) {
|
|
tal_report_error(
|
|
ctx, ecode, fail, PAY_ROUTE_NOT_FOUND,
|
|
"minflow couldn't find a feasible flow: %s",
|
|
errmsg);
|
|
goto function_fail;
|
|
}
|
|
|
|
enum renepay_errorcode errorcode;
|
|
for (size_t i = 0; i < tal_count(flows); i++) {
|
|
|
|
// 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)",
|
|
__func__,
|
|
fmt_amount_msat(this_ctx, flows[i]->amount),
|
|
fmt_amount_msat(this_ctx,
|
|
amount_to_deliver));
|
|
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;
|
|
}
|
|
|
|
// a bound check, we shouldn't deliver a zero amount, it
|
|
// would mean a bug somewhere
|
|
if (amount_msat_is_zero(flows[i]->amount)) {
|
|
tal_report_error(ctx, ecode, fail, PLUGIN_ERROR,
|
|
"flow conveys a zero amount");
|
|
goto function_fail;
|
|
}
|
|
|
|
const double prob = flow_probability(
|
|
flows[i], gossmap,
|
|
uncertainty_get_chan_extra_map(uncertainty), true);
|
|
if (prob < 0) {
|
|
// should not happen
|
|
tal_report_error(ctx, ecode, fail, PLUGIN_ERROR,
|
|
"flow_probability failed");
|
|
goto function_fail;
|
|
}
|
|
|
|
// 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]);
|
|
|
|
if (!r) {
|
|
tal_report_error(
|
|
ctx, ecode, fail, PLUGIN_ERROR,
|
|
"%s failed to build route from flow.",
|
|
__func__);
|
|
goto function_fail;
|
|
}
|
|
|
|
const struct amount_msat fee = route_fees(r);
|
|
const struct amount_msat delivering = route_delivers(r);
|
|
|
|
// 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 does not exceed our settings
|
|
const unsigned int delay = route_delay(r);
|
|
if (delay > maxdelay) {
|
|
if (!delay_feefactor_updated) {
|
|
delay_feefactor *= 2;
|
|
delay_feefactor_updated = true;
|
|
}
|
|
|
|
/* FIXME: What is a sane limit? */
|
|
if (delay_feefactor > 1000) {
|
|
tal_report_error(
|
|
ctx, ecode, fail,
|
|
PAY_ROUTE_TOO_EXPENSIVE,
|
|
"CLTV delay exceeds our CLTV "
|
|
"budget, delay=%u (maxdelay=%u)",
|
|
delay, maxdelay);
|
|
goto function_fail;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// check that the route satisfy all constraints
|
|
errorcode = route_check_constraints(
|
|
r, gossmap, uncertainty, disabled_bitmap);
|
|
|
|
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;
|
|
}
|
|
|
|
// 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).",
|
|
__func__,
|
|
fmt_amount_msat(this_ctx, fee),
|
|
fmt_amount_msat(this_ctx, feebudget));
|
|
goto function_fail;
|
|
}
|
|
|
|
// 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)",
|
|
__func__,
|
|
fmt_amount_msat(this_ctx, delivering),
|
|
fmt_amount_msat(this_ctx,
|
|
amount_to_deliver));
|
|
goto function_fail;
|
|
}
|
|
|
|
// update the probability target
|
|
if (prob < 1e-10) {
|
|
// probability is too small for division
|
|
probability_budget = 1.0;
|
|
} else {
|
|
/* prob here is a conditional probability, the
|
|
* next flow will have a conditional
|
|
* probability prob2 and we would like that
|
|
* prob*prob2 >= probability_budget hence
|
|
* probability_budget/prob becomes the next
|
|
* iteration's target. */
|
|
probability_budget =
|
|
MIN(1.0, probability_budget / prob);
|
|
}
|
|
|
|
// route added
|
|
(*next_partid)++;
|
|
uncertainty_commit_htlcs(uncertainty, r);
|
|
tal_arr_expand(&routes, r);
|
|
}
|
|
}
|
|
|
|
/* remove the temporary routes from the uncertainty network */
|
|
uncertainty_remove_routes(uncertainty, routes);
|
|
|
|
/* ownership */
|
|
for (size_t i = 0; i < tal_count(routes); i++)
|
|
routes[i] = tal_steal(routes, routes[i]);
|
|
|
|
tal_free(this_ctx);
|
|
return routes;
|
|
|
|
function_fail:
|
|
/* remove the temporary routes from the uncertainty network */
|
|
uncertainty_remove_routes(uncertainty, routes);
|
|
|
|
/* Discard any routes we have constructed here. */
|
|
tal_free(this_ctx);
|
|
return tal_free(routes);
|
|
}
|