diff --git a/common/amount.c b/common/amount.c index beeaa14b4a65..1eae69cefc8c 100644 --- a/common/amount.c +++ b/common/amount.c @@ -532,6 +532,13 @@ struct amount_msat amount_msat_div(struct amount_msat msat, u64 div) return msat; } +struct amount_msat amount_msat_div_ceil(struct amount_msat msat, u64 div) +{ + u64 res = msat.millisatoshis / div; + msat.millisatoshis = res + (div * res == msat.millisatoshis ? 0 : 1); + return msat; +} + struct amount_sat amount_sat_div(struct amount_sat sat, u64 div) { sat.satoshis /= div; diff --git a/common/amount.h b/common/amount.h index dd6ad61bb262..b1cdbac1d570 100644 --- a/common/amount.h +++ b/common/amount.h @@ -104,7 +104,13 @@ WARN_UNUSED_RESULT bool amount_sat_add_sat_s64(struct amount_sat *val, WARN_UNUSED_RESULT bool amount_msat_accumulate(struct amount_msat *a, struct amount_msat b); +/* returns floor(msat/div) */ struct amount_msat amount_msat_div(struct amount_msat msat, u64 div); + +/* returns ceil(msat/div) */ +struct amount_msat amount_msat_div_ceil(struct amount_msat msat, u64 div); + +/* returns floor(sat/div) */ struct amount_sat amount_sat_div(struct amount_sat sat, u64 div); bool amount_sat_mul(struct amount_sat *res, struct amount_sat sat, u64 mul); diff --git a/common/test/run-amount.c b/common/test/run-amount.c index 5f8a96a0b0bc..0e9f295dc0c2 100644 --- a/common/test/run-amount.c +++ b/common/test/run-amount.c @@ -163,6 +163,75 @@ static void test_amount_with_fee(void) 2100000001234567890ULL); } +static void test_case_amount_div(u64 input, u64 div, u64 expected) +{ + struct amount_msat msat = amount_msat(input); + struct amount_msat expected_msat = amount_msat(expected); + struct amount_msat result_msat = amount_msat_div(msat, div); + assert(amount_msat_eq(result_msat, expected_msat)); +} + +static void test_case_amount_div_ceil(u64 input, u64 div, u64 expected) +{ + struct amount_msat msat = amount_msat(input); + struct amount_msat expected_msat = amount_msat(expected); + struct amount_msat result_msat = amount_msat_div_ceil(msat, div); + assert(amount_msat_eq(result_msat, expected_msat)); +} + +static void test_amount_div(void) +{ + test_case_amount_div(1, 1, 1); + test_case_amount_div(1, 2, 0); + test_case_amount_div(1, 3, 0); + + test_case_amount_div(2, 1, 2); + test_case_amount_div(2, 2, 1); + test_case_amount_div(2, 3, 0); + + test_case_amount_div(3, 1, 3); + test_case_amount_div(3, 2, 1); + test_case_amount_div(3, 3, 1); + test_case_amount_div(3, 4, 0); + + test_case_amount_div(10, 1, 10); + test_case_amount_div(10, 2, 5); + test_case_amount_div(10, 3, 3); + test_case_amount_div(10, 4, 2); + test_case_amount_div(10, 5, 2); + test_case_amount_div(10, 6, 1); + test_case_amount_div(10, 7, 1); + test_case_amount_div(10, 8, 1); + test_case_amount_div(10, 9, 1); + test_case_amount_div(10, 10, 1); + test_case_amount_div(10, 11, 0); + + test_case_amount_div_ceil(1, 1, 1); + test_case_amount_div_ceil(1, 2, 1); + test_case_amount_div_ceil(1, 3, 1); + + test_case_amount_div_ceil(2, 1, 2); + test_case_amount_div_ceil(2, 2, 1); + test_case_amount_div_ceil(2, 3, 1); + + test_case_amount_div_ceil(3, 1, 3); + test_case_amount_div_ceil(3, 2, 2); + test_case_amount_div_ceil(3, 3, 1); + test_case_amount_div_ceil(3, 4, 1); + + test_case_amount_div_ceil(10, 1, 10); + test_case_amount_div_ceil(10, 2, 5); + test_case_amount_div_ceil(10, 3, 4); + test_case_amount_div_ceil(10, 4, 3); + test_case_amount_div_ceil(10, 5, 2); + test_case_amount_div_ceil(10, 6, 2); + test_case_amount_div_ceil(10, 7, 2); + test_case_amount_div_ceil(10, 8, 2); + test_case_amount_div_ceil(10, 9, 2); + test_case_amount_div_ceil(10, 10, 1); + test_case_amount_div_ceil(10, 11, 1); +} + #define FAIL_MSAT(msatp, str) \ assert(!parse_amount_msat((msatp), (str), strlen(str))) #define PASS_MSAT(msatp, str, val) \ @@ -330,5 +399,6 @@ int main(int argc, char *argv[]) } test_amount_with_fee(); + test_amount_div(); common_shutdown(); } diff --git a/contrib/msggen/msggen/schema.json b/contrib/msggen/msggen/schema.json index b00e60f9ea7b..dff1dc5dc86e 100644 --- a/contrib/msggen/msggen/schema.json +++ b/contrib/msggen/msggen/schema.json @@ -16125,7 +16125,7 @@ "", "Layers are generally maintained by plugins, either to contain persistent information about capacities which have been discovered, or to contain transient information for this particular payment (such as blinded paths or routehints).", "", - "There are three automatic layers: *auto.localchans* contains information on local channels from this node (including non-public ones), and their exact current spendable capacities. *auto.sourcefree* overrides all channels (including those from previous layers) leading out of the *source* to be zero fee and zero delay. These are both useful in the case where the source is the current node. And *auto.no_mpp_support* forces getroutes to return a single flow, though only basic checks are done that the result is useful." + "There are three automatic layers: *auto.localchans* contains information on local channels from this node (including non-public ones), and their exact current spendable capacities. *auto.sourcefree* overrides all channels (including those from previous layers) leading out of the *source* to be zero fee and zero delay. These are both useful in the case where the source is the current node. And *auto.no_mpp_support* forces getroutes to return a single path solution which is useful for payments for which MPP is not supported." ], "categories": [ "readonly" diff --git a/doc/schemas/getroutes.json b/doc/schemas/getroutes.json index cfa2ac3a3b90..942ef99de0c6 100644 --- a/doc/schemas/getroutes.json +++ b/doc/schemas/getroutes.json @@ -11,7 +11,7 @@ "", "Layers are generally maintained by plugins, either to contain persistent information about capacities which have been discovered, or to contain transient information for this particular payment (such as blinded paths or routehints).", "", - "There are three automatic layers: *auto.localchans* contains information on local channels from this node (including non-public ones), and their exact current spendable capacities. *auto.sourcefree* overrides all channels (including those from previous layers) leading out of the *source* to be zero fee and zero delay. These are both useful in the case where the source is the current node. And *auto.no_mpp_support* forces getroutes to return a single flow, though only basic checks are done that the result is useful." + "There are three automatic layers: *auto.localchans* contains information on local channels from this node (including non-public ones), and their exact current spendable capacities. *auto.sourcefree* overrides all channels (including those from previous layers) leading out of the *source* to be zero fee and zero delay. These are both useful in the case where the source is the current node. And *auto.no_mpp_support* forces getroutes to return a single path solution which is useful for payments for which MPP is not supported." ], "categories": [ "readonly" diff --git a/plugins/askrene/askrene.c b/plugins/askrene/askrene.c index 3429391084a2..601cb62bca4e 100644 --- a/plugins/askrene/askrene.c +++ b/plugins/askrene/askrene.c @@ -9,6 +9,7 @@ #include "config.h" #include #include +#include #include #include #include @@ -18,11 +19,9 @@ #include #include #include -#include #include #include #include -#include #include /* "spendable" for a channel assumes a single HTLC: for additional HTLCs, @@ -332,76 +331,61 @@ const char *fmt_flow_full(const tal_t *ctx, return str; } -static struct amount_msat linear_flows_cost(struct flow **flows, - struct amount_msat total_amount, - double delay_feefactor) -{ - struct amount_msat total = AMOUNT_MSAT(0); - - for (size_t i = 0; i < tal_count(flows); i++) { - if (!amount_msat_accumulate(&total, - linear_flow_cost(flows[i], - total_amount, - delay_feefactor))) - abort(); - } - return total; -} +enum algorithm { + /* Min. Cost Flow by successive shortests paths. */ + ALGO_DEFAULT, + /* Algorithm that finds the optimal routing solution constrained to a + * single path. */ + ALGO_SINGLE_PATH, +}; -/* Returns an error message, or sets *routes */ -static const char *get_routes(const tal_t *ctx, - struct command *cmd, - const struct node_id *source, - const struct node_id *dest, - struct amount_msat amount, - struct amount_msat maxfee, - u32 finalcltv, - u32 maxdelay, - const char **layers, - struct gossmap_localmods *localmods, - const struct layer *local_layer, - bool single_path, - struct route ***routes, - struct amount_msat **amounts, - const struct additional_cost_htable *additional_costs, - double *probability) +static struct command_result * +param_algorithm(struct command *cmd, const char *name, const char *buffer, + const jsmntok_t *tok, enum algorithm **algo) { - struct askrene *askrene = get_askrene(cmd->plugin); - struct route_query *rq = tal(ctx, struct route_query); - struct flow **flows; - const struct gossmap_node *srcnode, *dstnode; - double delay_feefactor; - u32 mu; - const char *ret; - - if (gossmap_refresh(askrene->gossmap)) { - /* FIXME: gossmap_refresh callbacks to we can update in place */ - tal_free(askrene->capacities); - askrene->capacities = get_capacities(askrene, askrene->plugin, askrene->gossmap); - } + const char *algo_str = json_strdup(cmd, buffer, tok); + *algo = tal(cmd, enum algorithm); + if (streq(algo_str, "default")) + **algo = ALGO_DEFAULT; + else if (streq(algo_str, "single-path")) + **algo = ALGO_SINGLE_PATH; + else + return command_fail_badparam(cmd, name, buffer, tok, + "unknown algorithm"); + return NULL; +} - rq->cmd = cmd; - rq->plugin = cmd->plugin; - rq->gossmap = askrene->gossmap; - rq->reserved = askrene->reserved; - rq->layers = tal_arr(rq, const struct layer *, 0); - rq->capacities = tal_dup_talarr(rq, fp16_t, askrene->capacities); - rq->additional_costs = additional_costs; +struct getroutes_info { + struct command *cmd; + struct node_id *source, *dest; + struct amount_msat *amount, *maxfee; + u32 *finalcltv, *maxdelay; + const char **layers; + struct additional_cost_htable *additional_costs; + /* Non-NULL if we are told to use "auto.localchans" */ + struct layer *local_layer; + /* algorithm selection, only dev */ + enum algorithm *dev_algo; +}; +static void apply_layers(struct askrene *askrene, struct route_query *rq, + struct gossmap_localmods *localmods, + const struct getroutes_info *info) +{ /* Layers must exist, but might be special ones! */ - for (size_t i = 0; i < tal_count(layers); i++) { - const struct layer *l = find_layer(askrene, layers[i]); + for (size_t i = 0; i < tal_count(info->layers); i++) { + const struct layer *l = find_layer(askrene, info->layers[i]); if (!l) { - if (streq(layers[i], "auto.localchans")) { + if (streq(info->layers[i], "auto.localchans")) { plugin_log(rq->plugin, LOG_DBG, "Adding auto.localchans"); - l = local_layer; - } else if (streq(layers[i], "auto.no_mpp_support")) { + l = info->local_layer; + } else if (streq(info->layers[i], "auto.no_mpp_support")) { plugin_log(rq->plugin, LOG_DBG, "Adding auto.no_mpp_support, sorry"); - l = remove_small_channel_layer(layers, askrene, amount, localmods); + l = remove_small_channel_layer(info->layers, askrene, *info->amount, localmods); } else { - assert(streq(layers[i], "auto.sourcefree")); + assert(streq(info->layers[i], "auto.sourcefree")); plugin_log(rq->plugin, LOG_DBG, "Adding auto.sourcefree"); - l = source_free_layer(layers, askrene, source, localmods); + l = source_free_layer(info->layers, askrene, info->source, localmods); } } @@ -413,140 +397,14 @@ static const char *get_routes(const tal_t *ctx, * override them (incl local channels) */ layer_clear_overridden_capacities(l, askrene->gossmap, rq->capacities); } +} - /* Clear scids with reservations, too, so we don't have to look up - * all the time! */ - reserves_clear_capacities(askrene->reserved, askrene->gossmap, rq->capacities); - - gossmap_apply_localmods(askrene->gossmap, localmods); - - /* localmods can add channels, so we need to allocate biases array *afterwards* */ - rq->biases = tal_arrz(rq, s8, gossmap_max_chan_idx(askrene->gossmap) * 2); - - /* Note any channel biases */ - for (size_t i = 0; i < tal_count(rq->layers); i++) - layer_apply_biases(rq->layers[i], askrene->gossmap, rq->biases); - - srcnode = gossmap_find_node(askrene->gossmap, source); - if (!srcnode) { - ret = rq_log(ctx, rq, LOG_INFORM, - "Unknown source node %s", - fmt_node_id(tmpctx, source)); - goto fail; - } - - dstnode = gossmap_find_node(askrene->gossmap, dest); - if (!dstnode) { - ret = rq_log(ctx, rq, LOG_INFORM, - "Unknown destination node %s", - fmt_node_id(tmpctx, dest)); - goto fail; - } - - delay_feefactor = 1.0/1000000; - - /* First up, don't care about fees (well, just enough to tiebreak!) */ - mu = 1; - flows = minflow(rq, rq, srcnode, dstnode, amount, - mu, delay_feefactor, single_path); - if (!flows) { - ret = explain_failure(ctx, rq, srcnode, dstnode, amount); - goto fail; - } - - /* Too much delay? */ - while (finalcltv + flows_worst_delay(flows) > maxdelay) { - delay_feefactor *= 2; - rq_log(tmpctx, rq, LOG_UNUSUAL, - "The worst flow delay is %"PRIu64" (> %i), retrying with delay_feefactor %f...", - flows_worst_delay(flows), maxdelay - finalcltv, delay_feefactor); - flows = minflow(rq, rq, srcnode, dstnode, amount, - mu, delay_feefactor, single_path); - if (!flows || delay_feefactor > 10) { - ret = rq_log(ctx, rq, LOG_UNUSUAL, - "Could not find route without excessive delays"); - goto fail; - } - } - - /* Too expensive? */ -too_expensive: - while (amount_msat_greater(flowset_fee(rq->plugin, flows), maxfee)) { - struct flow **new_flows; - - if (mu == 1) - mu = 10; - else - mu += 10; - rq_log(tmpctx, rq, LOG_UNUSUAL, - "The flows had a fee of %s, greater than max of %s, retrying with mu of %u%%...", - fmt_amount_msat(tmpctx, flowset_fee(rq->plugin, flows)), - fmt_amount_msat(tmpctx, maxfee), - mu); - new_flows = minflow(rq, rq, srcnode, dstnode, amount, - mu > 100 ? 100 : mu, delay_feefactor, single_path); - if (!flows || mu >= 100) { - ret = rq_log(ctx, rq, LOG_UNUSUAL, - "Could not find route without excessive cost"); - goto fail; - } - - /* This is possible, because MCF's linear fees are not the same. */ - if (amount_msat_greater(flowset_fee(rq->plugin, new_flows), - flowset_fee(rq->plugin, flows))) { - struct amount_msat old_cost = linear_flows_cost(flows, amount, delay_feefactor); - struct amount_msat new_cost = linear_flows_cost(new_flows, amount, delay_feefactor); - if (amount_msat_greater_eq(new_cost, old_cost)) { - rq_log(tmpctx, rq, LOG_BROKEN, "Old flows cost %s:", - fmt_amount_msat(tmpctx, old_cost)); - for (size_t i = 0; i < tal_count(flows); i++) { - rq_log(tmpctx, rq, LOG_BROKEN, - "Flow %zu/%zu: %s (linear cost %s)", i, tal_count(flows), - fmt_flow_full(tmpctx, rq, flows[i]), - fmt_amount_msat(tmpctx, linear_flow_cost(flows[i], - amount, - delay_feefactor))); - } - rq_log(tmpctx, rq, LOG_BROKEN, "Old flows cost %s:", - fmt_amount_msat(tmpctx, new_cost)); - for (size_t i = 0; i < tal_count(new_flows); i++) { - rq_log(tmpctx, rq, LOG_BROKEN, - "Flow %zu/%zu: %s (linear cost %s)", i, tal_count(new_flows), - fmt_flow_full(tmpctx, rq, new_flows[i]), - fmt_amount_msat(tmpctx, linear_flow_cost(new_flows[i], - amount, - delay_feefactor))); - } - } - } - tal_free(flows); - flows = new_flows; - } - - if (finalcltv + flows_worst_delay(flows) > maxdelay) { - ret = rq_log(ctx, rq, LOG_UNUSUAL, - "Could not find route without excessive cost or delays"); - goto fail; - } - - /* The above did not take into account the extra funds to pay - * fees, so we try to adjust now. We could re-run MCF if this - * fails, but failure basically never happens where payment is - * still possible */ - ret = refine_with_fees_and_limits(ctx, rq, amount, &flows, probability); - if (ret) - goto fail; - - /* Again, a tiny corner case: refine step can make us exceed maxfee */ - if (amount_msat_greater(flowset_fee(rq->plugin, flows), maxfee)) { - rq_log(tmpctx, rq, LOG_UNUSUAL, - "After final refinement, fee was excessive: retrying"); - goto too_expensive; - } - - rq_log(tmpctx, rq, LOG_DBG, "Final answer has %zu flows with mu=%u", - tal_count(flows), mu); - +static void convert_flows_to_routes(const tal_t *ctx, struct route_query *rq, + struct route ***routes, + struct amount_msat **amounts, + u32 finalcltv, + struct flow **flows) +{ /* Convert back into routes, with delay and other information fixed */ *routes = tal_arr(ctx, struct route *, tal_count(flows)); *amounts = tal_arr(ctx, struct amount_msat, tal_count(flows)); @@ -584,17 +442,6 @@ static const char *get_routes(const tal_t *ctx, i, tal_count(flows), fmt_route(tmpctx, r, (*amounts)[i], finalcltv)); } - - gossmap_remove_localmods(askrene->gossmap, localmods); - - return NULL; - - /* Explicit failure path keeps the compiler (gcc version 12.3.0 -O3) from - * warning about uninitialized variables in the caller */ -fail: - assert(ret != NULL); - gossmap_remove_localmods(askrene->gossmap, localmods); - return ret; } void get_constraints(const struct route_query *rq, @@ -633,16 +480,40 @@ void get_constraints(const struct route_query *rq, reserve_sub(rq->reserved, &scidd, max); } -struct getroutes_info { - struct command *cmd; - struct node_id *source, *dest; - struct amount_msat *amount, *maxfee; - u32 *finalcltv, *maxdelay; - const char **layers; - struct additional_cost_htable *additional_costs; - /* Non-NULL if we are told to use "auto.localchans" */ - struct layer *local_layer; -}; +static void json_add_getroutes( + struct json_stream *js, + struct route **routes, + const struct amount_msat *amounts, + double probability, + u32 final_cltv) +{ + json_add_u64(js, "probability_ppm", (u64)(probability * 1000000)); + json_array_start(js, "routes"); + for (size_t i = 0; i < tal_count(routes); i++) { + json_object_start(js, NULL); + json_add_u64(js, "probability_ppm", + (u64)(routes[i]->success_prob * 1000000)); + json_add_amount_msat(js, "amount_msat", amounts[i]); + json_add_u32(js, "final_cltv", final_cltv); + json_array_start(js, "path"); + for (size_t j = 0; j < tal_count(routes[i]->hops); j++) { + struct short_channel_id_dir scidd; + const struct route_hop *r = &routes[i]->hops[j]; + json_object_start(js, NULL); + scidd.scid = r->scid; + scidd.dir = r->direction; + json_add_short_channel_id_dir( + js, "short_channel_id_dir", scidd); + json_add_node_id(js, "next_node_id", &r->node_id); + json_add_amount_msat(js, "amount_msat", r->amount); + json_add_u32(js, "delay", r->delay); + json_object_end(js); + } + json_array_end(js); + json_object_end(js); + } + json_array_end(js); +} static struct command_result *do_getroutes(struct command *cmd, struct gossmap_localmods *localmods, @@ -652,43 +523,133 @@ static struct command_result *do_getroutes(struct command *cmd, double probability; struct amount_msat *amounts; struct route **routes; + struct flow **flows; struct json_stream *response; - err = get_routes(cmd, cmd, - info->source, info->dest, - *info->amount, *info->maxfee, *info->finalcltv, - *info->maxdelay, info->layers, localmods, info->local_layer, - have_layer(info->layers, "auto.no_mpp_support"), - &routes, &amounts, info->additional_costs, &probability); + /* get me the global state structure */ + struct askrene *askrene = get_askrene(cmd->plugin); + + /* update the gossmap */ + if (gossmap_refresh(askrene->gossmap)) { + /* FIXME: gossmap_refresh callbacks to we can update in place */ + tal_free(askrene->capacities); + askrene->capacities = + get_capacities(askrene, askrene->plugin, askrene->gossmap); + } + + /* build this request structure */ + struct route_query *rq = tal(cmd, struct route_query); + rq->cmd = cmd; + rq->plugin = cmd->plugin; + rq->gossmap = askrene->gossmap; + rq->reserved = askrene->reserved; + rq->layers = tal_arr(rq, const struct layer *, 0); + rq->capacities = tal_dup_talarr(rq, fp16_t, askrene->capacities); + /* FIXME: we still need to do something useful with these */ + rq->additional_costs = info->additional_costs; + + /* apply selected layers to the localmods */ + apply_layers(askrene, rq, localmods, info); + + /* Clear scids with reservations, too, so we don't have to look up + * all the time! */ + reserves_clear_capacities(askrene->reserved, askrene->gossmap, + rq->capacities); + + /* we temporarily apply localmods */ + gossmap_apply_localmods(askrene->gossmap, localmods); + + /* I want to be able to disable channels while working on this query. + * Layers are for user interaction and cannot be used for this purpose. + */ + rq->disabled_chans = + tal_arrz(rq, bitmap, + 2 * BITMAP_NWORDS(gossmap_max_chan_idx(askrene->gossmap))); + + /* localmods can add channels, so we need to allocate biases array + * *afterwards* */ + rq->biases = + tal_arrz(rq, s8, gossmap_max_chan_idx(askrene->gossmap) * 2); + + /* Note any channel biases */ + for (size_t i = 0; i < tal_count(rq->layers); i++) + layer_apply_biases(rq->layers[i], askrene->gossmap, rq->biases); + + /* checkout the source */ + const struct gossmap_node *srcnode = + gossmap_find_node(askrene->gossmap, info->source); + if (!srcnode) { + err = rq_log(tmpctx, rq, LOG_INFORM, "Unknown source node %s", + fmt_node_id(tmpctx, info->source)); + goto fail; + } + + /* checkout the destination */ + const struct gossmap_node *dstnode = + gossmap_find_node(askrene->gossmap, info->dest); + if (!dstnode) { + err = rq_log(tmpctx, rq, LOG_INFORM, + "Unknown destination node %s", + fmt_node_id(tmpctx, info->dest)); + goto fail; + } + + /* auto.no_mpp_support layer overrides any choice of algorithm. */ + if (have_layer(info->layers, "auto.no_mpp_support") && + *info->dev_algo != ALGO_SINGLE_PATH) { + *info->dev_algo = ALGO_SINGLE_PATH; + rq_log(tmpctx, rq, LOG_DBG, + "Layer no_mpp_support is active we switch to a " + "single path algorithm."); + } + + /* Compute the routes. At this point we might select between multiple + * algorithms. Right now there is only one algorithm available. */ + struct timemono time_start = time_mono(); + if (*info->dev_algo == ALGO_SINGLE_PATH){ + err = single_path_routes( + rq, rq, srcnode, dstnode, *info->amount, + *info->maxfee, *info->finalcltv, *info->maxdelay, &flows, + &probability); + } else { + assert(*info->dev_algo == ALGO_DEFAULT); + err = default_routes(rq, rq, srcnode, dstnode, *info->amount, + *info->maxfee, *info->finalcltv, + *info->maxdelay, &flows, &probability); + } + struct timerel time_delta = timemono_between(time_mono(), time_start); + + /* log the time of computation */ + rq_log(tmpctx, rq, LOG_DBG, "get_routes %s %" PRIu64 " ms", + err ? "failed after" : "completed in", + time_to_msec(time_delta)); if (err) - return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "%s", err); + goto fail; + + /* otherwise we continue */ + assert(tal_count(flows) > 0); + rq_log(tmpctx, rq, LOG_DBG, "Final answer has %zu flows", + tal_count(flows)); + /* convert flows to routes */ + convert_flows_to_routes(rq, rq, &routes, &amounts, *info->finalcltv, + flows); + assert(tal_count(routes) == tal_count(flows)); + assert(tal_count(amounts) == tal_count(flows)); + + /* At last we remove the localmods from the gossmap. */ + gossmap_remove_localmods(askrene->gossmap, localmods); + + /* output the results */ response = jsonrpc_stream_success(cmd); - json_add_u64(response, "probability_ppm", (u64)(probability * 1000000)); - json_array_start(response, "routes"); - for (size_t i = 0; i < tal_count(routes); i++) { - json_object_start(response, NULL); - json_add_u64(response, "probability_ppm", (u64)(routes[i]->success_prob * 1000000)); - json_add_amount_msat(response, "amount_msat", amounts[i]); - json_add_u32(response, "final_cltv", *info->finalcltv); - json_array_start(response, "path"); - for (size_t j = 0; j < tal_count(routes[i]->hops); j++) { - struct short_channel_id_dir scidd; - const struct route_hop *r = &routes[i]->hops[j]; - json_object_start(response, NULL); - scidd.scid = r->scid; - scidd.dir = r->direction; - json_add_short_channel_id_dir(response, "short_channel_id_dir", scidd); - json_add_node_id(response, "next_node_id", &r->node_id); - json_add_amount_msat(response, "amount_msat", r->amount); - json_add_u32(response, "delay", r->delay); - json_object_end(response); - } - json_array_end(response); - json_object_end(response); - } - json_array_end(response); + json_add_getroutes(response, routes, amounts, probability, + *info->finalcltv); return command_finished(cmd, response); + +fail: + assert(err); + gossmap_remove_localmods(askrene->gossmap, localmods); + return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "%s", err); } static void add_localchan(struct gossmap_localmods *mods, @@ -810,6 +771,8 @@ static struct command_result *json_getroutes(struct command *cmd, p_req("final_cltv", param_u32, &info->finalcltv), p_opt_def("maxdelay", param_u32, &info->maxdelay, maxdelay_allowed), + p_opt_dev("dev_algorithm", param_algorithm, + &info->dev_algo, ALGO_DEFAULT), NULL)) return command_param_failed(); plugin_log(cmd->plugin, LOG_TRACE, "%s called: %.*s", __func__, diff --git a/plugins/askrene/askrene.h b/plugins/askrene/askrene.h index 8688835e975e..7f1352f6f5f7 100644 --- a/plugins/askrene/askrene.h +++ b/plugins/askrene/askrene.h @@ -2,6 +2,7 @@ #define LIGHTNING_PLUGINS_ASKRENE_ASKRENE_H #include "config.h" #include +#include #include #include #include @@ -60,6 +61,9 @@ struct route_query { /* Additional per-htlc cost for local channels */ const struct additional_cost_htable *additional_costs; + + /* channels we disable during computation to meet constraints */ + bitmap *disabled_chans; }; /* Given a gossmap channel, get the current known min/max */ diff --git a/plugins/askrene/mcf.c b/plugins/askrene/mcf.c index 6339510a5ce9..6ad1fc0e3f1f 100644 --- a/plugins/askrene/mcf.c +++ b/plugins/askrene/mcf.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -11,9 +12,11 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -157,6 +160,10 @@ * * */ +#define PANIC(message) \ + errx(1, "Panic in function %s line %d: %s", __func__, __LINE__, \ + message); + #define PARTS_BITS 2 #define CHANNEL_PARTS (1 << PARTS_BITS) @@ -167,6 +174,9 @@ static const double CHANNEL_PIVOTS[]={0,0.5,0.8,0.95}; static const s64 INFINITE = INT64_MAX; static const s64 MU_MAX = 100; +/* every payment under 1000sat will be routed through a single path */ +static const struct amount_msat SINGLE_PATH_THRESHOLD = AMOUNT_MSAT(1000000); + /* Let's try this encoding of arcs: * Each channel `c` has two possible directions identified by a bit * `half` or `!half`, and each one of them has to be @@ -297,48 +307,17 @@ struct pay_parameters { double base_fee_penalty; }; -/* Representation of the linear MCF network. - * This contains the topology of the extended network (after linearization and - * addition of arc duality). - * This contains also the arc probability and linear fee cost, as well as - * capacity; these quantities remain constant during MCF execution. */ -struct linear_network -{ - struct graph *graph; - - // probability and fee cost associated to an arc - double *arc_prob_cost; - s64 *arc_fee_cost; - s64 *capacity; -}; - -/* This is the structure that keeps track of the network properties while we - * seek for a solution. */ -struct residual_network { - /* residual capacity on arcs */ - s64 *cap; - - /* some combination of prob. cost and fee cost on arcs */ - s64 *cost; - - /* potential function on nodes */ - s64 *potential; - - /* auxiliary data, the excess of flow on nodes */ - s64 *excess; -}; - /* Helper function. * Given an arc of the network (not residual) give me the flow. */ static s64 get_arc_flow( - const struct residual_network *network, + const s64 *arc_residual_capacity, const struct graph *graph, const struct arc arc) { assert(!arc_is_dual(graph, arc)); struct arc dual = arc_dual(graph, arc); - assert(dual.idx < tal_count(network->cap)); - return network->cap[dual.idx]; + assert(dual.idx < tal_count(arc_residual_capacity)); + return arc_residual_capacity[dual.idx]; } /* Set *capacity to value, up to *cap_on_capacity. Reduce cap_on_capacity */ @@ -348,6 +327,37 @@ static void set_capacity(s64 *capacity, u64 value, u64 *cap_on_capacity) *cap_on_capacity -= *capacity; } +/* Helper to check whether a channel is available */ +static bool channel_is_available(const struct route_query *rq, + const struct gossmap_chan *chan, const int dir) +{ + const u32 c_idx = gossmap_chan_idx(rq->gossmap, chan); + return gossmap_chan_set(chan, dir) && chan->half[dir].enabled && + !bitmap_test_bit(rq->disabled_chans, c_idx * 2 + dir); +} + +/* FIXME: unit test this */ +/* The probability of forwarding a payment amount given a high and low liquidity + * bounds. + * @low: the liquidity is known to be greater or equal than "low" + * @high: the liquidity is known to be less than "high" + * @amount: how much is required to forward */ +static double pickhardt_richter_probability(struct amount_msat low, + struct amount_msat high, + struct amount_msat amount) +{ + struct amount_msat all_states, good_states; + if (amount_msat_greater_eq(amount, high)) + return 0.0; + if (!amount_msat_sub(&amount, amount, low)) + return 1.0; + if (!amount_msat_sub(&all_states, high, low)) + PANIC("we expect high > low"); + if (!amount_msat_sub(&good_states, all_states, amount)) + PANIC("we expect high > amount"); + return amount_msat_ratio(good_states, all_states); +} + // TODO(eduardo): unit test this /* Split a directed channel into parts with linear cost function. */ static void linearize_channel(const struct pay_parameters *params, @@ -367,9 +377,13 @@ static void linearize_channel(const struct pay_parameters *params, b = 1 + amount_msat_ratio_floor(maxcap, params->accuracy); /* An extra bound on capacity, here we use it to reduce the flow such - * that it does not exceed htlcmax. */ + * that it does not exceed htlcmax. + * Also there is no need to keep track of more capacity than the payment + * amount, this can help us prune some arcs. */ u64 cap_on_capacity = - amount_msat_ratio_floor(gossmap_chan_htlc_max(c, dir), params->accuracy); + MIN(amount_msat_ratio_floor(gossmap_chan_htlc_max(c, dir), + params->accuracy), + amount_msat_ratio_ceil(params->amount, params->accuracy)); set_capacity(&capacity[0], a, &cap_on_capacity); cost[0]=0; @@ -383,49 +397,6 @@ static void linearize_channel(const struct pay_parameters *params, } } -static struct residual_network * -alloc_residual_network(const tal_t *ctx, const size_t max_num_nodes, - const size_t max_num_arcs) -{ - struct residual_network *residual_network = - tal(ctx, struct residual_network); - - residual_network->cap = tal_arrz(residual_network, s64, max_num_arcs); - residual_network->cost = tal_arrz(residual_network, s64, max_num_arcs); - residual_network->potential = - tal_arrz(residual_network, s64, max_num_nodes); - residual_network->excess = - tal_arrz(residual_network, s64, max_num_nodes); - - return residual_network; -} - -static void init_residual_network( - const struct linear_network * linear_network, - struct residual_network* residual_network) -{ - const struct graph *graph = linear_network->graph; - const size_t max_num_arcs = graph_max_num_arcs(graph); - const size_t max_num_nodes = graph_max_num_nodes(graph); - - for (struct arc arc = {.idx = 0}; arc.idx < max_num_arcs; ++arc.idx) { - if (arc_is_dual(graph, arc) || !arc_enabled(graph, arc)) - continue; - - struct arc dual = arc_dual(graph, arc); - residual_network->cap[arc.idx] = - linear_network->capacity[arc.idx]; - residual_network->cap[dual.idx] = 0; - - residual_network->cost[arc.idx] = - residual_network->cost[dual.idx] = 0; - } - for (u32 i = 0; i < max_num_nodes; ++i) { - residual_network->potential[i] = 0; - residual_network->excess[i] = 0; - } -} - static int cmp_u64(const u64 *a, const u64 *b, void *unused) { if (*a < *b) @@ -445,9 +416,10 @@ static int cmp_double(const double *a, const double *b, void *unused) } static double get_median_ratio(const tal_t *working_ctx, - const struct linear_network* linear_network) + const struct graph *graph, + const double *arc_prob_cost, + const s64 *arc_fee_cost) { - const struct graph *graph = linear_network->graph; const size_t max_num_arcs = graph_max_num_arcs(graph); u64 *u64_arr = tal_arr(working_ctx, u64, max_num_arcs); double *double_arr = tal_arr(working_ctx, double, max_num_arcs); @@ -458,8 +430,8 @@ static double get_median_ratio(const tal_t *working_ctx, if (arc_is_dual(graph, arc) || !arc_enabled(graph, arc)) continue; assert(n < max_num_arcs/2); - u64_arr[n] = linear_network->arc_fee_cost[arc.idx]; - double_arr[n] = linear_network->arc_prob_cost[arc.idx]; + u64_arr[n] = arc_fee_cost[arc.idx]; + double_arr[n] = arc_prob_cost[arc.idx]; n++; } asort(u64_arr, n, cmp_u64, NULL); @@ -473,18 +445,17 @@ static double get_median_ratio(const tal_t *working_ctx, return u64_arr[n/2] / double_arr[n/2]; } -static void combine_cost_function( - const tal_t *working_ctx, - const struct linear_network* linear_network, - struct residual_network *residual_network, - const s8 *biases, - s64 mu) +static void combine_cost_function(const tal_t *working_ctx, + const struct graph *graph, + const double *arc_prob_cost, + const s64 *arc_fee_cost, const s8 *biases, + s64 mu, s64 *arc_cost) { /* probabilty and fee costs are not directly comparable! * Scale by ratio of (positive) medians. */ - const double k = get_median_ratio(working_ctx, linear_network); + const double k = + get_median_ratio(working_ctx, graph, arc_prob_cost, arc_fee_cost); const double ln_30 = log(30); - const struct graph *graph = linear_network->graph; const size_t max_num_arcs = graph_max_num_arcs(graph); for(struct arc arc = {.idx=0};arc.idx < max_num_arcs; ++arc.idx) @@ -492,8 +463,8 @@ static void combine_cost_function( if (arc_is_dual(graph, arc) || !arc_enabled(graph, arc)) continue; - const double pcost = linear_network->arc_prob_cost[arc.idx]; - const s64 fcost = linear_network->arc_fee_cost[arc.idx]; + const double pcost = arc_prob_cost[arc.idx]; + const s64 fcost = arc_fee_cost[arc.idx]; double combined; u32 chanidx; int chandir; @@ -513,13 +484,13 @@ static void combine_cost_function( * e^(-bias / (100/ln(30))) */ double bias_factor = exp(-bias / (100 / ln_30)); - residual_network->cost[arc.idx] = combined * bias_factor; + arc_cost[arc.idx] = combined * bias_factor; } else { - residual_network->cost[arc.idx] = combined; + arc_cost[arc.idx] = combined; } /* and the respective dual */ struct arc dual = arc_dual(graph, arc); - residual_network->cost[dual.idx] = -combined; + arc_cost[dual.idx] = -combined; } } @@ -576,31 +547,26 @@ struct amount_msat linear_flow_cost(const struct flow *flow, return msat_cost; } -/* FIXME: Instead of mapping one-to-one the indexes in the gossmap, try to - * reduce the number of nodes and arcs used by taking only those that are - * enabled. We might save some cpu if the work with a pruned network. */ -static struct linear_network * -init_linear_network(const tal_t *ctx, const struct pay_parameters *params) +static void init_linear_network(const tal_t *ctx, + const struct pay_parameters *params, + struct graph **graph, double **arc_prob_cost, + s64 **arc_fee_cost, s64 **arc_capacity) { - struct linear_network * linear_network = tal(ctx, struct linear_network); const struct gossmap *gossmap = params->rq->gossmap; - const size_t max_num_chans = gossmap_max_chan_idx(gossmap); const size_t max_num_arcs = max_num_chans * ARCS_PER_CHANNEL; const size_t max_num_nodes = gossmap_max_node_idx(gossmap); - linear_network->graph = - graph_new(ctx, max_num_nodes, max_num_arcs, ARC_DUAL_BITOFF); + *graph = graph_new(ctx, max_num_nodes, max_num_arcs, ARC_DUAL_BITOFF); + *arc_prob_cost = tal_arr(ctx, double, max_num_arcs); + for (size_t i = 0; i < max_num_arcs; ++i) + (*arc_prob_cost)[i] = DBL_MAX; - linear_network->arc_prob_cost = tal_arr(linear_network,double,max_num_arcs); - for(size_t i=0;iarc_prob_cost[i]=DBL_MAX; + *arc_fee_cost = tal_arr(ctx, s64, max_num_arcs); + for (size_t i = 0; i < max_num_arcs; ++i) + (*arc_fee_cost)[i] = INT64_MAX; - linear_network->arc_fee_cost = tal_arr(linear_network,s64,max_num_arcs); - for(size_t i=0;iarc_fee_cost[i]=INFINITE; - - linear_network->capacity = tal_arrz(linear_network,s64,max_num_arcs); + *arc_capacity = tal_arrz(ctx, s64, max_num_arcs); for(struct gossmap_node *node = gossmap_first_node(gossmap); node; @@ -613,16 +579,15 @@ init_linear_network(const tal_t *ctx, const struct pay_parameters *params) int half; const struct gossmap_chan *c = gossmap_nth_chan(gossmap, node, j, &half); + const u32 chan_id = gossmap_chan_idx(gossmap, c); - if (!gossmap_chan_set(c, half) || !c->half[half].enabled) + if (!channel_is_available(params->rq, c, half)) continue; /* If a channel insists on more than our total, remove it */ if (amount_msat_less(params->amount, gossmap_chan_htlc_min(c, half))) continue; - const u32 chan_id = gossmap_chan_idx(gossmap, c); - const struct gossmap_node *next = gossmap_nth_node(gossmap, c,!half); @@ -653,30 +618,29 @@ init_linear_network(const tal_t *ctx, const struct pay_parameters *params) // when the `i` hits the `next` node. for(size_t k=0;kgraph, arc, + graph_add_arc(*graph, arc, node_obj(node_id), node_obj(next_id)); - linear_network->capacity[arc.idx] = capacity[k]; - linear_network->arc_prob_cost[arc.idx] = prob_cost[k]; - linear_network->arc_fee_cost[arc.idx] = fee_cost; + (*arc_capacity)[arc.idx] = capacity[k]; + (*arc_prob_cost)[arc.idx] = prob_cost[k]; + (*arc_fee_cost)[arc.idx] = fee_cost; // + the respective dual - struct arc dual = arc_dual(linear_network->graph, arc); + struct arc dual = arc_dual(*graph, arc); - linear_network->capacity[dual.idx] = 0; - linear_network->arc_prob_cost[dual.idx] = -prob_cost[k]; - linear_network->arc_fee_cost[dual.idx] = -fee_cost; + (*arc_capacity)[dual.idx] = 0; + (*arc_prob_cost)[dual.idx] = -prob_cost[k]; + (*arc_fee_cost)[dual.idx] = -fee_cost; } } } - - return linear_network; } // flow on directed channels @@ -691,8 +655,8 @@ struct chan_flow * */ static struct node find_path_or_cycle( const tal_t *working_ctx, - const struct gossmap *gossmap, - const struct chan_flow *chan_flow, + const struct route_query *rq, + const struct chan_flow *chan_flow, const struct node source, const s64 *balance, @@ -700,6 +664,7 @@ static struct node find_path_or_cycle( int *prev_dir, u32 *prev_idx) { + const struct gossmap *gossmap = rq->gossmap; const size_t max_num_nodes = gossmap_max_node_idx(gossmap); bitmap *visited = tal_arrz(working_ctx, bitmap, BITMAP_NWORDS(max_num_nodes)); @@ -717,12 +682,11 @@ static struct node find_path_or_cycle( int dir; const struct gossmap_chan *c = gossmap_nth_chan(gossmap, cur, i, &dir); + const u32 c_idx = gossmap_chan_idx(gossmap, c); - if (!gossmap_chan_set(c, dir) || !c->half[dir].enabled) + if (!channel_is_available(rq, c, dir)) continue; - const u32 c_idx = gossmap_chan_idx(gossmap, c); - /* follow the flow */ if (chan_flow[c_idx].half[dir] > 0) { const struct gossmap_node *n = @@ -871,8 +835,8 @@ static struct flow ** get_flow_paths(const tal_t *ctx, const tal_t *working_ctx, const struct pay_parameters *params, - const struct linear_network *linear_network, - const struct residual_network *residual_network) + const struct graph *graph, + const s64 *arc_residual_capacity) { struct flow **flows = tal_arr(ctx,struct flow*,0); @@ -895,7 +859,6 @@ get_flow_paths(const tal_t *ctx, // Convert the arc based residual network flow into a flow in the // directed channel network. // Compute balance on the nodes. - const struct graph *graph = linear_network->graph; for (struct node n = {.idx = 0}; n.idx < max_num_nodes; n.idx++) { for(struct arc arc = node_adjacency_begin(graph,n); !node_adjacency_end(arc); @@ -904,7 +867,7 @@ get_flow_paths(const tal_t *ctx, if(arc_is_dual(graph, arc)) continue; struct node m = arc_head(graph,arc); - s64 flow = get_arc_flow(residual_network, + s64 flow = get_arc_flow(arc_residual_capacity, graph, arc); u32 chanidx; int chandir; @@ -925,7 +888,7 @@ get_flow_paths(const tal_t *ctx, while (balance[source.idx] < 0) { prev_chan[source.idx] = NULL; struct node sink = find_path_or_cycle( - working_ctx, params->rq->gossmap, chan_flow, source, + working_ctx, params->rq, chan_flow, source, balance, prev_chan, prev_dir, prev_idx); if (balance[sink.idx] > 0) @@ -947,6 +910,46 @@ get_flow_paths(const tal_t *ctx, return flows; } +/* Given a single path build a flow set. */ +static struct flow ** +get_flow_singlepath(const tal_t *ctx, const struct pay_parameters *params, + const struct graph *graph, const struct gossmap *gossmap, + const struct node source, const struct node destination, + const u64 pay_amount, const struct arc *prev) +{ + struct flow **flows = tal_arr(ctx, struct flow *, 0); + + size_t length = 0; + + for (u32 cur_idx = destination.idx; cur_idx != source.idx;) { + assert(cur_idx != INVALID_INDEX); + length++; + struct arc arc = prev[cur_idx]; + struct node next = arc_tail(graph, arc); + cur_idx = next.idx; + } + struct flow *f = tal(ctx, struct flow); + f->path = tal_arr(f, const struct gossmap_chan *, length); + f->dirs = tal_arr(f, int, length); + + for (u32 cur_idx = destination.idx; cur_idx != source.idx;) { + int chandir; + u32 chanidx; + struct arc arc = prev[cur_idx]; + arc_to_parts(arc, &chanidx, &chandir, NULL, NULL); + + length--; + f->path[length] = gossmap_chan_byidx(gossmap, chanidx); + f->dirs[length] = chandir; + + struct node next = arc_tail(graph, arc); + cur_idx = next.idx; + } + f->delivers = params->amount; + tal_arr_expand(&flows, f); + return flows; +} + // TODO(eduardo): choose some default values for the minflow parameters /* eduardo: I think it should be clear that this module deals with linear * flows, ie. base fees are not considered. Hence a flow along a path is @@ -965,8 +968,7 @@ struct flow **minflow(const tal_t *ctx, const struct gossmap_node *target, struct amount_msat amount, u32 mu, - double delay_feefactor, - bool single_part) + double delay_feefactor) { struct flow **flow_paths; /* We allocate everything off this, and free it at the end, @@ -978,10 +980,14 @@ struct flow **minflow(const tal_t *ctx, params->source = source; params->target = target; params->amount = amount; - params->accuracy = AMOUNT_MSAT(1000); - /* FIXME: params->accuracy = amount_msat_max(amount_msat_div(amount, - * 1000), AMOUNT_MSAT(1)); + /* -> At most 1M units of flow are allowed, that reduces the + * computational burden for algorithms that depend on it, eg. "capacity + * scaling" and "successive shortest path". + * -> Using Ceil operation instead of Floor so that + * accuracy x 1000 >= amount * */ + params->accuracy = amount_msat_max( + AMOUNT_MSAT(1), amount_msat_div_ceil(amount, 1000)); // template the channel partition into linear arcs params->cap_fraction[0]=0; @@ -998,17 +1004,25 @@ struct flow **minflow(const tal_t *ctx, params->base_fee_penalty = base_fee_penalty_estimate(amount); // build the uncertainty network with linearization and residual arcs - struct linear_network *linear_network= init_linear_network(working_ctx, params); - const struct graph *graph = linear_network->graph; + struct graph *graph; + double *arc_prob_cost; + s64 *arc_fee_cost; + s64 *arc_capacity; + init_linear_network(working_ctx, params, &graph, &arc_prob_cost, + &arc_fee_cost, &arc_capacity); + const size_t max_num_arcs = graph_max_num_arcs(graph); const size_t max_num_nodes = graph_max_num_nodes(graph); - struct residual_network *residual_network = - alloc_residual_network(working_ctx, max_num_nodes, max_num_arcs); + s64 *arc_cost; + s64 *node_potential; + s64 *node_excess; + arc_cost = tal_arrz(working_ctx, s64, max_num_arcs); + node_potential = tal_arrz(working_ctx, s64, max_num_nodes); + node_excess = tal_arrz(working_ctx, s64, max_num_nodes); const struct node dst = {.idx = gossmap_node_idx(rq->gossmap, target)}; const struct node src = {.idx = gossmap_node_idx(rq->gossmap, source)}; - init_residual_network(linear_network,residual_network); /* Since we have constraint accuracy, ask to find a payment solution * that can pay a bit more than the actual value rathen than undershoot it. @@ -1016,22 +1030,22 @@ struct flow **minflow(const tal_t *ctx, const u64 pay_amount = amount_msat_ratio_ceil(params->amount, params->accuracy); - if (!simple_feasibleflow(working_ctx, linear_network->graph, src, dst, - residual_network->cap, pay_amount)) { + if (!simple_feasibleflow(working_ctx, graph, src, dst, + arc_capacity, pay_amount)) { rq_log(tmpctx, rq, LOG_INFORM, "%s failed: unable to find a feasible flow.", __func__); goto fail; } - combine_cost_function(working_ctx, linear_network, residual_network, - rq->biases, mu); + combine_cost_function(working_ctx, graph, arc_prob_cost, arc_fee_cost, + rq->biases, mu, arc_cost); /* We solve a linear MCF problem. */ if (!mcf_refinement(working_ctx, - linear_network->graph, - residual_network->excess, - residual_network->cap, - residual_network->cost, - residual_network->potential)) { + graph, + node_excess, + arc_capacity, + arc_cost, + node_potential)) { rq_log(tmpctx, rq, LOG_BROKEN, "%s: MCF optimization step failed", __func__); goto fail; @@ -1041,7 +1055,7 @@ struct flow **minflow(const tal_t *ctx, * Actual amounts considering fees are computed for every * channel in the routes. */ flow_paths = get_flow_paths(ctx, working_ctx, params, - linear_network, residual_network); + graph, arc_capacity); if(!flow_paths){ rq_log(tmpctx, rq, LOG_BROKEN, "%s: failed to extract flow paths from the MCF solution", @@ -1049,34 +1063,483 @@ struct flow **minflow(const tal_t *ctx, goto fail; } tal_free(working_ctx); + return flow_paths; + +fail: + tal_free(working_ctx); + return NULL; +} + +/* Initialize the data vectors for the single-path solver. */ +static void init_linear_network_single_path( + const tal_t *ctx, const struct pay_parameters *params, struct graph **graph, + double **arc_prob_cost, s64 **arc_fee_cost, s64 **arc_capacity) +{ + const size_t max_num_chans = gossmap_max_chan_idx(params->rq->gossmap); + const size_t max_num_arcs = max_num_chans * ARCS_PER_CHANNEL; + const size_t max_num_nodes = gossmap_max_node_idx(params->rq->gossmap); + + *graph = graph_new(ctx, max_num_nodes, max_num_arcs, ARC_DUAL_BITOFF); + *arc_prob_cost = tal_arr(ctx, double, max_num_arcs); + for (size_t i = 0; i < max_num_arcs; ++i) + (*arc_prob_cost)[i] = DBL_MAX; + + *arc_fee_cost = tal_arr(ctx, s64, max_num_arcs); + for (size_t i = 0; i < max_num_arcs; ++i) + (*arc_fee_cost)[i] = INT64_MAX; + *arc_capacity = tal_arrz(ctx, s64, max_num_arcs); + + const struct gossmap *gossmap = params->rq->gossmap; + + for (struct gossmap_node *node = gossmap_first_node(gossmap); node; + node = gossmap_next_node(gossmap, node)) { + const u32 node_id = gossmap_node_idx(gossmap, node); + + for (size_t j = 0; j < node->num_chans; ++j) { + int half; + const struct gossmap_chan *c = + gossmap_nth_chan(gossmap, node, j, &half); + struct amount_msat mincap, maxcap; + const u32 chan_id = gossmap_chan_idx(gossmap, c); + + if (!channel_is_available(params->rq, c, half)) + continue; - /* This is dumb, but if you don't support MPP you don't deserve any - * better. Pile it into the largest part if not already. */ - if (single_part) { - struct flow *best = flow_paths[0]; - for (size_t i = 1; i < tal_count(flow_paths); i++) { - if (amount_msat_greater(flow_paths[i]->delivers, best->delivers)) - best = flow_paths[i]; + /* If a channel cannot forward the total amount we don't + * use it. */ + if (amount_msat_less(params->amount, + gossmap_chan_htlc_min(c, half)) || + amount_msat_greater(params->amount, + gossmap_chan_htlc_max(c, half))) + continue; + + get_constraints(params->rq, c, half, &mincap, &maxcap); + /* Assume if min > max, min is wrong */ + if (amount_msat_greater(mincap, maxcap)) + mincap = maxcap; + /* It is preferable to work on 1msat past the known + * bound. */ + if (!amount_msat_accumulate(&maxcap, amount_msat(1))) + PANIC("maxcap + 1msat overflows"); + + /* If amount is greater than the known liquidity upper + * bound we get infinite probability cost. */ + if (amount_msat_greater_eq(params->amount, maxcap)) + continue; + + const struct gossmap_node *next = + gossmap_nth_node(gossmap, c, !half); + + const u32 next_id = gossmap_node_idx(gossmap, next); + + /* channel to self? */ + if (node_id == next_id) + continue; + + struct arc arc = + arc_from_parts(chan_id, half, 0, false); + + graph_add_arc(*graph, arc, node_obj(node_id), + node_obj(next_id)); + + (*arc_capacity)[arc.idx] = 1; + (*arc_prob_cost)[arc.idx] = + (-1.0) * log(pickhardt_richter_probability( + mincap, maxcap, params->amount)); + + struct amount_msat fee; + if (!amount_msat_fee(&fee, params->amount, + c->half[half].base_fee, + c->half[half].proportional_fee)) + PANIC("fee overflow"); + u32 fee_msat; + if (!amount_msat_to_u32(fee, &fee_msat)) + PANIC("fee does not fit in u32"); + (*arc_fee_cost)[arc.idx] = + fee_msat + + params->delay_feefactor * c->half[half].delay; } - for (size_t i = 0; i < tal_count(flow_paths); i++) { - if (flow_paths[i] == best) + } +} + +/* Similar to minflow but computes routes that have a single path. */ +struct flow **single_path_flow(const tal_t *ctx, const struct route_query *rq, + const struct gossmap_node *source, + const struct gossmap_node *target, + struct amount_msat amount, u32 mu, + double delay_feefactor) +{ + struct flow **flow_paths; + /* We allocate everything off this, and free it at the end, + * as we can be called multiple times without cleaning tmpctx! */ + tal_t *working_ctx = tal(NULL, char); + struct pay_parameters *params = tal(working_ctx, struct pay_parameters); + + params->rq = rq; + params->source = source; + params->target = target; + params->amount = amount; + /* for the single-path solver the accuracy does not detriment + * performance */ + params->accuracy = amount; + params->delay_feefactor = delay_feefactor; + params->base_fee_penalty = base_fee_penalty_estimate(amount); + + struct graph *graph; + double *arc_prob_cost; + s64 *arc_fee_cost; + s64 *arc_capacity; + + init_linear_network_single_path(working_ctx, params, &graph, + &arc_prob_cost, &arc_fee_cost, + &arc_capacity); + + const struct node dst = {.idx = gossmap_node_idx(rq->gossmap, target)}; + const struct node src = {.idx = gossmap_node_idx(rq->gossmap, source)}; + + const size_t max_num_nodes = graph_max_num_nodes(graph); + const size_t max_num_arcs = graph_max_num_arcs(graph); + + s64 *potential = tal_arrz(working_ctx, s64, max_num_nodes); + s64 *distance = tal_arrz(working_ctx, s64, max_num_nodes); + s64 *arc_cost = tal_arrz(working_ctx, s64, max_num_arcs); + struct arc *prev = tal_arrz(working_ctx, struct arc, max_num_nodes); + + combine_cost_function(working_ctx, graph, arc_prob_cost, arc_fee_cost, + rq->biases, mu, arc_cost); + + /* We solve a linear cost flow problem. */ + if (!dijkstra_path(working_ctx, graph, src, dst, + /* prune = */ true, arc_capacity, + /*threshold = */ 1, arc_cost, potential, prev, + distance)) { + /* This might fail if we are unable to find a suitable route, it + * doesn't mean the plugin is broken, that's why we LOG_INFORM. */ + rq_log(tmpctx, rq, LOG_INFORM, + "%s: could not find a feasible single path", __func__); + goto fail; + } + const u64 pay_amount = + amount_msat_ratio_ceil(params->amount, params->accuracy); + + /* We dissect the flow into payment routes. + * Actual amounts considering fees are computed for every + * channel in the routes. */ + flow_paths = get_flow_singlepath(ctx, params, graph, rq->gossmap, + src, dst, pay_amount, prev); + if (!flow_paths) { + rq_log(tmpctx, rq, LOG_BROKEN, + "%s: failed to extract flow paths from the single-path " + "solution", + __func__); + goto fail; + } + if (tal_count(flow_paths) != 1) { + rq_log( + tmpctx, rq, LOG_BROKEN, + "%s: single-path solution returned a multi route solution", + __func__); + goto fail; + } + tal_free(working_ctx); + return flow_paths; + +fail: + tal_free(working_ctx); + return NULL; +} + +/* Get the scidd for the i'th hop in flow */ +static void get_scidd(const struct gossmap *gossmap, const struct flow *flow, + size_t i, struct short_channel_id_dir *scidd) +{ + scidd->scid = gossmap_chan_scid(gossmap, flow->path[i]); + scidd->dir = flow->dirs[i]; +} + +/* We use an fp16_t approximatin for htlc_max/min: this gets the exact value. */ +static struct amount_msat +get_chan_htlc_max(const struct route_query *rq, const struct gossmap_chan *c, + const struct short_channel_id_dir *scidd) +{ + struct amount_msat htlc_max; + + gossmap_chan_get_update_details(rq->gossmap, c, scidd->dir, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + &htlc_max); + return htlc_max; +} + +static struct amount_msat +get_chan_htlc_min(const struct route_query *rq, const struct gossmap_chan *c, + const struct short_channel_id_dir *scidd) +{ + struct amount_msat htlc_min; + + gossmap_chan_get_update_details(rq->gossmap, c, scidd->dir, NULL, NULL, + NULL, NULL, NULL, NULL, &htlc_min, + NULL); + return htlc_min; +} + +static bool check_htlc_limits(struct route_query *rq, struct flow **flows) +{ + + for (size_t k = 0; k < tal_count(flows); k++) { + struct flow *flow = flows[k]; + size_t pathlen = tal_count(flow->path); + struct amount_msat hop_amt = flow->delivers; + for (size_t i = pathlen - 1; i < pathlen; i--) { + const struct half_chan *h = flow_edge(flow, i); + struct short_channel_id_dir scidd; + + get_scidd(rq->gossmap, flow, i, &scidd); + struct amount_msat htlc_min = + get_chan_htlc_min(rq, flow->path[i], &scidd); + struct amount_msat htlc_max = + get_chan_htlc_max(rq, flow->path[i], &scidd); + if (amount_msat_greater(hop_amt, htlc_max) || + amount_msat_less(hop_amt, htlc_min)) + return false; + + if (!amount_msat_add_fee(&hop_amt, h->base_fee, + h->proportional_fee)) + abort(); + } + } + return true; +} + +/* FIXME: add extra constraint maximum route length, use an activation + * probability cost for each channel. Recall that every activation cost, eg. + * base fee and activation probability can only be properly added modifying the + * graph topology by creating an activation node for every half channel. */ +/* FIXME: add extra constraint maximum number of routes, fixes issue 8331. */ +/* FIXME: add a boolean option to make recipient pay for fees, fixes issue 8353. + */ +static const char * +linear_routes(const tal_t *ctx, struct route_query *rq, + const struct gossmap_node *srcnode, + const struct gossmap_node *dstnode, struct amount_msat amount, + struct amount_msat maxfee, u32 finalcltv, u32 maxdelay, + struct flow ***flows, double *probability, + struct flow **(*solver)(const tal_t *, const struct route_query *, + const struct gossmap_node *, + const struct gossmap_node *, + struct amount_msat, u32, double)) +{ + const tal_t *working_ctx = tal(ctx, tal_t); + const char *error_message; + struct amount_msat amount_to_deliver = amount; + struct amount_msat feebudget = maxfee; + + /* FIXME: mu is an integer from 0 to MU_MAX that we use to combine fees + * and probability costs, but I think we can make it a real number from + * 0 to 1. */ + u32 mu = 1; + /* we start at 1e-6 and increase it exponentially (x2) up to 10. */ + double delay_feefactor = 1e-6; + + struct flow **new_flows = NULL; + struct amount_msat all_deliver; + + *flows = tal_arr(working_ctx, struct flow *, 0); + + /* Re-use the reservation system to make flows aware of each other. */ + struct reserve_hop *reservations = new_reservations(working_ctx, rq); + + while (!amount_msat_is_zero(amount_to_deliver)) { + new_flows = tal_free(new_flows); + + /* If the amount_to_deliver is very small we better use a single + * path computation because: + * 1. we save cpu cycles + * 2. we have better control over htlc_min violations. + * We need to make the distinction here because after + * refine_with_fees_and_limits we might have a set of flows that + * do not deliver the entire payment amount by just a small + * amount. */ + if(amount_msat_less_eq(amount_to_deliver, SINGLE_PATH_THRESHOLD)){ + new_flows = single_path_flow(working_ctx, rq, srcnode, dstnode, + amount_to_deliver, mu, delay_feefactor); + } else { + + new_flows = + solver(working_ctx, rq, srcnode, dstnode, + amount_to_deliver, mu, delay_feefactor); + } + + if (!new_flows) { + error_message = explain_failure( + ctx, rq, srcnode, dstnode, amount_to_deliver); + goto fail; + } + + error_message = refine_with_fees_and_limits( + ctx, rq, amount_to_deliver, &new_flows); + if (error_message) + goto fail; + + /* we finished removing flows and excess */ + all_deliver = flowset_delivers(rq->plugin, new_flows); + if (amount_msat_is_zero(all_deliver)) { + /* We removed all flows and we have not modified the + * MCF parameters. We will not have an infinite loop + * here because at least we have disabled some channels. + */ + continue; + } + + /* We might want to overpay sometimes, eg. shadow routing, but + * right now if all_deliver > amount_to_deliver means a bug. */ + assert(amount_msat_greater_eq(amount_to_deliver, all_deliver)); + + /* we should have fixed all htlc violations, "don't trust, + * verify" */ + assert(check_htlc_limits(rq, new_flows)); + + /* no flows should send 0 amount */ + for (size_t i = 0; i < tal_count(new_flows); i++) { + assert(!amount_msat_is_zero(new_flows[i]->delivers)); + } + + /* Is this set of flows too expensive? + * We can check if the new flows are within the fee budget, + * however in some cases we have discarded some flows at this + * point and the new flows do not deliver all the value we need + * so that a further solver iteration is needed. Hence we + * check if the fees paid by these new flows are below the + * feebudget proportionally adjusted by the amount this set of + * flows deliver with respect to the total remaining amount, + * ie. we avoid "consuming" all the feebudget if we still need + * to run MCF again for some remaining amount. */ + const struct amount_msat all_fees = + flowset_fee(rq->plugin, new_flows); + const double deliver_fraction = + amount_msat_ratio(all_deliver, amount_to_deliver); + struct amount_msat partial_feebudget; + if (!amount_msat_scale(&partial_feebudget, feebudget, + deliver_fraction)) { + error_message = + rq_log(ctx, rq, LOG_BROKEN, + "%s: failed to scale the fee budget (%s) by " + "fraction (%lf)", + __func__, fmt_amount_msat(tmpctx, feebudget), + deliver_fraction); + goto fail; + } + if (amount_msat_greater(all_fees, partial_feebudget)) { + if (mu < MU_MAX) { + /* all_fees exceed the strong budget limit, try + * to fix it increasing mu. */ + if (mu == 1) + mu = 10; + else + mu += 10; + mu = MIN(mu, MU_MAX); + rq_log( + tmpctx, rq, LOG_UNUSUAL, + "The flows had a fee of %s, greater than " + "max of %s, retrying with mu of %u%%...", + fmt_amount_msat(tmpctx, all_fees), + fmt_amount_msat(tmpctx, partial_feebudget), + mu); continue; - if (!amount_msat_accumulate(&best->delivers, - flow_paths[i]->delivers)) { - rq_log(tmpctx, rq, LOG_BROKEN, - "%s: failed to extract accumulate flow paths %s+%s", - __func__, - fmt_amount_msat(tmpctx, best->delivers), - fmt_amount_msat(tmpctx, flow_paths[i]->delivers)); + } else if (amount_msat_greater(all_fees, feebudget)) { + /* we cannot increase mu anymore and all_fees + * already exceeds feebudget we fail. */ + error_message = + rq_log(ctx, rq, LOG_UNUSUAL, + "Could not find route without " + "excessive cost"); goto fail; + } else { + /* mu cannot be increased but at least all_fees + * does not exceed feebudget, we give it a shot. + */ + rq_log( + tmpctx, rq, LOG_UNUSUAL, + "The flows had a fee of %s, greater than " + "max of %s, but still within the fee " + "budget %s, we accept those flows.", + fmt_amount_msat(tmpctx, all_fees), + fmt_amount_msat(tmpctx, partial_feebudget), + fmt_amount_msat(tmpctx, feebudget)); } } - flow_paths[0] = best; - tal_resize(&flow_paths, 1); + + /* Too much delay? */ + if (finalcltv + flows_worst_delay(new_flows) > maxdelay) { + if (delay_feefactor > 10) { + error_message = + rq_log(ctx, rq, LOG_UNUSUAL, + "Could not find route without " + "excessive delays"); + goto fail; + } + + delay_feefactor *= 2; + rq_log(tmpctx, rq, LOG_UNUSUAL, + "The worst flow delay is %" PRIu64 + " (> %i), retrying with delay_feefactor %f...", + flows_worst_delay(*flows), maxdelay - finalcltv, + delay_feefactor); + } + + /* add the new flows to the final solution */ + for (size_t i = 0; i < tal_count(new_flows); i++) { + tal_arr_expand(flows, new_flows[i]); + tal_steal(*flows, new_flows[i]); + create_flow_reservations(rq, &reservations, + new_flows[i]); + } + + if (!amount_msat_sub(&feebudget, feebudget, all_fees) || + !amount_msat_sub(&amount_to_deliver, amount_to_deliver, + all_deliver)) { + error_message = + rq_log(ctx, rq, LOG_BROKEN, + "%s: unexpected arithmetic operation " + "failure on amount_msat", + __func__); + goto fail; + } } - return flow_paths; -fail: + /* transfer ownership */ + *flows = tal_steal(ctx, *flows); + + /* cleanup */ tal_free(working_ctx); return NULL; +fail: + /* cleanup */ + tal_free(working_ctx); + + assert(error_message != NULL); + return error_message; +} + +const char *default_routes(const tal_t *ctx, struct route_query *rq, + const struct gossmap_node *srcnode, + const struct gossmap_node *dstnode, + struct amount_msat amount, struct amount_msat maxfee, + u32 finalcltv, u32 maxdelay, struct flow ***flows, + double *probability) +{ + return linear_routes(ctx, rq, srcnode, dstnode, amount, maxfee, + finalcltv, maxdelay, flows, probability, minflow); +} + +const char *single_path_routes(const tal_t *ctx, struct route_query *rq, + const struct gossmap_node *srcnode, + const struct gossmap_node *dstnode, + struct amount_msat amount, + struct amount_msat maxfee, u32 finalcltv, + u32 maxdelay, struct flow ***flows, + double *probability) +{ + return linear_routes(ctx, rq, srcnode, dstnode, amount, maxfee, + finalcltv, maxdelay, flows, probability, + single_path_flow); } diff --git a/plugins/askrene/mcf.h b/plugins/askrene/mcf.h index f8100e766dd6..448aee27a40c 100644 --- a/plugins/askrene/mcf.h +++ b/plugins/askrene/mcf.h @@ -31,8 +31,29 @@ struct flow **minflow(const tal_t *ctx, const struct gossmap_node *target, struct amount_msat amount, u32 mu, - double delay_feefactor, - bool single_part); + double delay_feefactor); + +/** + * API for min cost single path. + * @ctx: context to allocate returned flows from + * @rq: the route_query we're processing (for logging) + * @source: the source to start from + * @target: the target to pay + * @amount: the amount we want to reach @target + * @mu: 0 = corresponds to only probabilities, 100 corresponds to only fee. + * @delay_feefactor: convert 1 block delay into msat. + * + * @delay_feefactor converts 1 block delay into msat, as if it were an additional + * fee. So if a CLTV delay on a node is 5 blocks, that's treated as if it + * were a fee of 5 * @delay_feefactor. + * + * Returns an array with one flow which deliver amount to target, or NULL. + */ +struct flow **single_path_flow(const tal_t *ctx, const struct route_query *rq, + const struct gossmap_node *source, + const struct gossmap_node *target, + struct amount_msat amount, u32 mu, + double delay_feefactor); /* To sanity check: this is the approximation mcf uses for the cost * of each channel. */ @@ -40,4 +61,23 @@ struct amount_msat linear_flow_cost(const struct flow *flow, struct amount_msat total_amount, double delay_feefactor); +/* A wrapper to the min. cost flow solver that actually takes into consideration + * the extra msats per channel needed to pay for fees. */ +const char *default_routes(const tal_t *ctx, struct route_query *rq, + const struct gossmap_node *srcnode, + const struct gossmap_node *dstnode, + struct amount_msat amount, + struct amount_msat maxfee, u32 finalcltv, + u32 maxdelay, struct flow ***flows, + double *probability); + +/* A wrapper to the single-path constrained solver. */ +const char *single_path_routes(const tal_t *ctx, struct route_query *rq, + const struct gossmap_node *srcnode, + const struct gossmap_node *dstnode, + struct amount_msat amount, + struct amount_msat maxfee, u32 finalcltv, + u32 maxdelay, struct flow ***flows, + double *probability); + #endif /* LIGHTNING_PLUGINS_ASKRENE_MCF_H */ diff --git a/plugins/askrene/refine.c b/plugins/askrene/refine.c index 51f030ade8e8..b5dd58a5208d 100644 --- a/plugins/askrene/refine.c +++ b/plugins/askrene/refine.c @@ -1,4 +1,5 @@ #include "config.h" +#include #include #include #include @@ -6,6 +7,14 @@ #include #include +/* Channel data for fast retrieval. */ +struct channel_data { + struct amount_msat htlc_min, htlc_max, liquidity_max; + u32 fee_base_msat, fee_proportional_millionths; + struct short_channel_id_dir scidd; +}; + + /* We (ab)use the reservation system to place temporary reservations * on channels while we are refining each flow. This has the effect * of making flows aware of each other. */ @@ -26,8 +35,8 @@ static void destroy_reservations(struct reserve_hop *rhops, struct askrene *askr reserve_remove(askrene->reserved, &rhops[i]); } -static struct reserve_hop *new_reservations(const tal_t *ctx, - const struct route_query *rq) +struct reserve_hop *new_reservations(const tal_t *ctx, + const struct route_query *rq) { struct reserve_hop *rhops = tal_arr(ctx, struct reserve_hop, 0); @@ -80,28 +89,28 @@ static void add_reservation(struct reserve_hop **reservations, tal_arr_expand(reservations, rhop); } -static void subtract_reservation(struct reserve_hop **reservations, - const struct route_query *rq, - const struct gossmap_chan *chan, - const struct short_channel_id_dir *scidd, - struct amount_msat amt) -{ - struct reserve_hop *prev; - struct askrene *askrene = get_askrene(rq->plugin); - - prev = find_reservation(*reservations, scidd); - assert(prev); - - reserve_remove(askrene->reserved, prev); - if (!amount_msat_sub(&prev->amount, prev->amount, amt)) - abort(); - /* Adding a zero reserve is weird, but legal and easy! */ - reserve_add(askrene->reserved, prev, rq->cmd->id); -} - -static void create_flow_reservations(const struct route_query *rq, - struct reserve_hop **reservations, - const struct flow *flow) +// static void subtract_reservation(struct reserve_hop **reservations, +// const struct route_query *rq, +// const struct gossmap_chan *chan, +// const struct short_channel_id_dir *scidd, +// struct amount_msat amt) +// { +// struct reserve_hop *prev; +// struct askrene *askrene = get_askrene(rq->plugin); +// +// prev = find_reservation(*reservations, scidd); +// assert(prev); +// +// reserve_remove(askrene->reserved, prev); +// if (!amount_msat_sub(&prev->amount, prev->amount, amt)) +// abort(); +// /* Adding a zero reserve is weird, but legal and easy! */ +// reserve_add(askrene->reserved, prev, rq->cmd->id); +// } + +void create_flow_reservations(const struct route_query *rq, + struct reserve_hop **reservations, + const struct flow *flow) { struct amount_msat msat; @@ -126,40 +135,40 @@ static void create_flow_reservations(const struct route_query *rq, } } -static void remove_flow_reservations(const struct route_query *rq, - struct reserve_hop **reservations, - const struct flow *flow) -{ - struct amount_msat msat = flow->delivers; - for (int i = tal_count(flow->path) - 1; i >= 0; i--) { - const struct half_chan *h = flow_edge(flow, i); - struct amount_msat amount_to_reserve; - struct short_channel_id_dir scidd; - - get_scidd(rq->gossmap, flow, i, &scidd); - - /* Reserve more for local channels if it reduces capacity */ - if (!amount_msat_add(&amount_to_reserve, msat, - get_additional_per_htlc_cost(rq, &scidd))) - abort(); - - subtract_reservation(reservations, rq, flow->path[i], &scidd, - amount_to_reserve); - if (!amount_msat_add_fee(&msat, - h->base_fee, h->proportional_fee)) - plugin_err(rq->plugin, "Adding fee to amount"); - } -} - -static void change_flow_delivers(const struct route_query *rq, - struct flow *flow, - struct reserve_hop **reservations, - struct amount_msat new) -{ - remove_flow_reservations(rq, reservations, flow); - flow->delivers = new; - create_flow_reservations(rq, reservations, flow); -} +// static void remove_flow_reservations(const struct route_query *rq, +// struct reserve_hop **reservations, +// const struct flow *flow) +// { +// struct amount_msat msat = flow->delivers; +// for (int i = tal_count(flow->path) - 1; i >= 0; i--) { +// const struct half_chan *h = flow_edge(flow, i); +// struct amount_msat amount_to_reserve; +// struct short_channel_id_dir scidd; +// +// get_scidd(rq->gossmap, flow, i, &scidd); +// +// /* Reserve more for local channels if it reduces capacity */ +// if (!amount_msat_add(&amount_to_reserve, msat, +// get_additional_per_htlc_cost(rq, &scidd))) +// abort(); +// +// subtract_reservation(reservations, rq, flow->path[i], &scidd, +// amount_to_reserve); +// if (!amount_msat_add_fee(&msat, +// h->base_fee, h->proportional_fee)) +// plugin_err(rq->plugin, "Adding fee to amount"); +// } +// } + +// static void change_flow_delivers(const struct route_query *rq, +// struct flow *flow, +// struct reserve_hop **reservations, +// struct amount_msat new) +// { +// remove_flow_reservations(rq, reservations, flow); +// flow->delivers = new; +// create_flow_reservations(rq, reservations, flow); +// } /* We use an fp16_t approximatin for htlc_max/min: this gets the exact value. */ static struct amount_msat get_chan_htlc_max(const struct route_query *rq, @@ -195,56 +204,56 @@ enum why_capped { /* Get exact maximum we can deliver with this flow. Returns reason * why this is the limit (max_hltc or capacity), and optionally sets scidd */ -static enum why_capped flow_max_capacity(const struct route_query *rq, - const struct flow *flow, - struct amount_msat *deliverable, - struct short_channel_id_dir *scidd_why, - struct amount_msat *amount_why) -{ - struct amount_msat max_msat = AMOUNT_MSAT(-1ULL); - enum why_capped why_capped = CAPPED_CAPACITY; - - for (int i = tal_count(flow->path) - 1; i >= 0; i--) { - const struct half_chan *h = flow_edge(flow, i); - struct amount_msat min, max, htlc_max; - struct short_channel_id_dir scidd; - - get_scidd(rq->gossmap, flow, i, &scidd); - /* We can pass constraints due to addition of fees! */ - get_constraints(rq, flow->path[i], flow->dirs[i], &min, &max); - - if (amount_msat_greater(max_msat, max)) { - why_capped = CAPPED_CAPACITY; - if (scidd_why) - *scidd_why = scidd; - if (amount_why) - *amount_why = max; - max_msat = max; - } - - htlc_max = get_chan_htlc_max(rq, flow->path[i], &scidd); - if (amount_msat_greater(max_msat, htlc_max)) { - why_capped = CAPPED_HTLC_MAX; - if (scidd_why) - *scidd_why = scidd; - if (amount_why) - *amount_why = htlc_max; - max_msat = htlc_max; - } - if (!amount_msat_add_fee(&max_msat, h->base_fee, h->proportional_fee)) - max_msat = AMOUNT_MSAT(-1ULL); - } - - /* Calculate deliverable max */ - *deliverable = max_msat; - for (size_t i = 0; i < tal_count(flow->path); i++) { - const struct half_chan *h = flow_edge(flow, i); - *deliverable = amount_msat_sub_fee(*deliverable, - h->base_fee, - h->proportional_fee); - } - return why_capped; -} +// static enum why_capped flow_max_capacity(const struct route_query *rq, +// const struct flow *flow, +// struct amount_msat *deliverable, +// struct short_channel_id_dir *scidd_why, +// struct amount_msat *amount_why) +// { +// struct amount_msat max_msat = AMOUNT_MSAT(-1ULL); +// enum why_capped why_capped = CAPPED_CAPACITY; +// +// for (int i = tal_count(flow->path) - 1; i >= 0; i--) { +// const struct half_chan *h = flow_edge(flow, i); +// struct amount_msat min, max, htlc_max; +// struct short_channel_id_dir scidd; +// +// get_scidd(rq->gossmap, flow, i, &scidd); +// /* We can pass constraints due to addition of fees! */ +// get_constraints(rq, flow->path[i], flow->dirs[i], &min, &max); +// +// if (amount_msat_greater(max_msat, max)) { +// why_capped = CAPPED_CAPACITY; +// if (scidd_why) +// *scidd_why = scidd; +// if (amount_why) +// *amount_why = max; +// max_msat = max; +// } +// +// htlc_max = get_chan_htlc_max(rq, flow->path[i], &scidd); +// if (amount_msat_greater(max_msat, htlc_max)) { +// why_capped = CAPPED_HTLC_MAX; +// if (scidd_why) +// *scidd_why = scidd; +// if (amount_why) +// *amount_why = htlc_max; +// max_msat = htlc_max; +// } +// if (!amount_msat_add_fee(&max_msat, h->base_fee, h->proportional_fee)) +// max_msat = AMOUNT_MSAT(-1ULL); +// } +// +// /* Calculate deliverable max */ +// *deliverable = max_msat; +// for (size_t i = 0; i < tal_count(flow->path); i++) { +// const struct half_chan *h = flow_edge(flow, i); +// *deliverable = amount_msat_sub_fee(*deliverable, +// h->base_fee, +// h->proportional_fee); +// } +// return why_capped; +// } /* We have a basic set of flows, but we need to add fees, and take into @@ -258,349 +267,454 @@ static enum why_capped flow_max_capacity(const struct route_query *rq, * this happens, as we can make it up by buffing up the other flows * (or, it's simply impossible). */ -static const char *constrain_flow(const tal_t *ctx, - struct route_query *rq, - struct flow *flow) +// static const char *constrain_flow(const tal_t *ctx, +// struct route_query *rq, +// struct flow *flow) +// { +// struct amount_msat deliverable, msat, amount_capped; +// enum why_capped why_capped; +// struct short_channel_id_dir scidd_capped; +// +// why_capped = flow_max_capacity(rq, flow, &deliverable, +// &scidd_capped, &amount_capped); +// if (amount_msat_less(deliverable, flow->delivers)) { +// rq_log(tmpctx, rq, LOG_INFORM, +// "Flow reduced to deliver %s not %s, because %s %s %s", +// fmt_amount_msat(tmpctx, deliverable), +// fmt_amount_msat(tmpctx, flow->delivers), +// fmt_short_channel_id_dir(tmpctx, &scidd_capped), +// why_capped == CAPPED_HTLC_MAX +// ? "advertizes htlc_maximum_msat" +// : "has remaining capacity", +// fmt_amount_msat(tmpctx, amount_capped)); +// flow->delivers = deliverable; +// } +// +// /* Now, check if any of them violate htlc_min */ +// msat = flow->delivers; +// for (int i = tal_count(flow->path) - 1; i >= 0; i--) { +// const struct half_chan *h = flow_edge(flow, i); +// struct amount_msat min; +// struct short_channel_id_dir scidd; +// +// get_scidd(rq->gossmap, flow, i, &scidd); +// min = get_chan_htlc_min(rq, flow->path[i], &scidd); +// +// if (amount_msat_less(msat, min)) { +// return rq_log(ctx, rq, LOG_UNUSUAL, +// "Amount %s below minimum %s across %s", +// fmt_amount_msat(tmpctx, msat), +// fmt_amount_msat(tmpctx, min), +// fmt_short_channel_id_dir(tmpctx, &scidd)); +// } +// if (!amount_msat_add_fee(&msat, +// h->base_fee, h->proportional_fee)) +// plugin_err(rq->plugin, "Adding fee to amount"); +// } +// return NULL; +// } + +// static struct amount_msat flow_remaining_capacity(const struct route_query *rq, +// struct reserve_hop **reservations, +// const struct flow *flow) +// { +// struct amount_msat max, diff; +// +// /* Remove ourselves from reservation temporarily, so we don't +// * accidentally cap! */ +// remove_flow_reservations(rq, reservations, flow); +// flow_max_capacity(rq, flow, &max, NULL, NULL); +// +// /* This seems to happen. Which is strange, since flows should +// already be constrained. */ +// if (!amount_msat_sub(&diff, max, flow->delivers)) { +// plugin_log(rq->plugin, LOG_BROKEN, +// "Flow delivers %s but max only %s? flow=%s", +// fmt_amount_msat(tmpctx, flow->delivers), +// fmt_amount_msat(tmpctx, max), +// fmt_flow_full(rq, rq, flow)); +// for (size_t i = 0; i < tal_count(*reservations); i++) { +// plugin_log(rq->plugin, LOG_BROKEN, +// "Reservation #%zi: %s on %s", +// i, +// fmt_amount_msat(tmpctx, (*reservations)[i].amount), +// fmt_short_channel_id_dir(tmpctx, &(*reservations)[i].scidd)); +// } +// diff = AMOUNT_MSAT(0); +// } +// create_flow_reservations(rq, reservations, flow); +// return diff; +// } + +// /* What's the "best" flow to add to? */ +// static struct flow *pick_most_likely_flow(const struct route_query *rq, +// struct flow **flows, +// struct reserve_hop **reservations, +// struct amount_msat additional) +// { +// double best_prob = 0; +// struct flow *best_flow = NULL; +// +// for (size_t i = 0; i < tal_count(flows); i++) { +// struct amount_msat cap; +// double prob = flow_probability(flows[i], rq); +// if (prob < best_prob) +// continue; +// cap = flow_remaining_capacity(rq, reservations, flows[i]); +// if (amount_msat_less(cap, additional)) +// continue; +// best_prob = prob; +// best_flow = flows[i]; +// } +// +// return best_flow; +// } + +static void remove_htlc_min_violations(const tal_t *ctx, struct route_query *rq, + const struct flow *flow, + const struct channel_data *channels) { - struct amount_msat deliverable, msat, amount_capped; - enum why_capped why_capped; - struct short_channel_id_dir scidd_capped; - - why_capped = flow_max_capacity(rq, flow, &deliverable, - &scidd_capped, &amount_capped); - if (amount_msat_less(deliverable, flow->delivers)) { - rq_log(tmpctx, rq, LOG_INFORM, - "Flow reduced to deliver %s not %s, because %s %s %s", - fmt_amount_msat(tmpctx, deliverable), - fmt_amount_msat(tmpctx, flow->delivers), - fmt_short_channel_id_dir(tmpctx, &scidd_capped), - why_capped == CAPPED_HTLC_MAX - ? "advertizes htlc_maximum_msat" - : "has remaining capacity", - fmt_amount_msat(tmpctx, amount_capped)); - flow->delivers = deliverable; + + struct amount_msat msat = flow->delivers; + for (size_t i = tal_count(flow->path) - 1; i < tal_count(flow->path); + i--) { + if (amount_msat_less(msat, channels[i].htlc_min)) { + rq_log( + ctx, rq, LOG_UNUSUAL, + "Sending %s across %s would violate htlc_min (~%s)", + fmt_amount_msat(ctx, msat), + fmt_short_channel_id_dir(ctx, &channels[i].scidd), + fmt_amount_msat(ctx, channels[i].htlc_min)); + break; + } + if (!amount_msat_add_fee( + &msat, channels[i].fee_base_msat, + channels[i].fee_proportional_millionths)) { + plugin_err(rq->plugin, "%s: Adding fee to amount", + __func__); + // TODO: fail this function and report to caller + } } +} - /* Now, check if any of them violate htlc_min */ - msat = flow->delivers; - for (int i = tal_count(flow->path) - 1; i >= 0; i--) { +/* If one flow is constrained by htlc_max, we might be able to simply + * duplicate it. This is naive: it could still fail due to total + * capacity, but it is a corner case anyway. */ +// static bool duplicate_one_flow(const struct route_query *rq, +// struct reserve_hop **reservations, +// struct flow ***flows) +// { +// for (size_t i = 0; i < tal_count(*flows); i++) { +// struct flow *flow = (*flows)[i], *new_flow; +// struct amount_msat max, new_amount; +// /* Don't create 0 flow (shouldn't happen, but be sure) */ +// if (amount_msat_less(flow->delivers, AMOUNT_MSAT(2))) +// continue; +// +// if (flow_max_capacity(rq, flow, &max, NULL, NULL) +// != CAPPED_HTLC_MAX) +// continue; +// +// new_flow = tal(*flows, struct flow); +// new_flow->path = tal_dup_talarr(new_flow, +// const struct gossmap_chan *, +// flow->path); +// new_flow->dirs = tal_dup_talarr(new_flow, int, +// flow->dirs); +// new_flow->delivers = amount_msat_div(flow->delivers, 2); +// create_flow_reservations(rq, reservations, new_flow); +// +// if (!amount_msat_sub(&new_amount, +// flow->delivers, new_flow->delivers)) +// abort(); +// change_flow_delivers(rq, flow, reservations, new_amount); +// tal_arr_expand(flows, new_flow); +// return true; +// } +// return false; +// } + +/* Stolen whole-cloth from @Lagrang3 in renepay's flow.c. Wrong + * because of htlc overhead in reservations! */ +// static double edge_probability(const struct route_query *rq, +// const struct short_channel_id_dir *scidd, +// struct amount_msat sent) +// { +// struct amount_msat numerator, denominator; +// struct amount_msat mincap, maxcap, additional; +// const struct gossmap_chan *c = gossmap_find_chan(rq->gossmap, &scidd->scid); +// +// get_constraints(rq, c, scidd->dir, &mincap, &maxcap); +// +// /* We add an extra per-htlc reservation for the *next* HTLC, so we "over-reserve" +// * on local channels. Undo that! */ +// additional = get_additional_per_htlc_cost(rq, scidd); +// if (!amount_msat_accumulate(&mincap, additional) +// || !amount_msat_accumulate(&maxcap, additional)) +// abort(); +// +// if (amount_msat_less_eq(sent, mincap)) +// return 1.0; +// else if (amount_msat_greater(sent, maxcap)) +// return 0.0; +// +// /* Linear probability: 1 - (spend - min) / (max - min) */ +// +// /* spend > mincap, from above. */ +// if (!amount_msat_sub(&numerator, sent, mincap)) +// abort(); +// /* This can only fail is maxcap was < mincap, +// * so we would be captured above */ +// if (!amount_msat_sub(&denominator, maxcap, mincap)) +// abort(); +// return 1.0 - amount_msat_ratio(numerator, denominator); +// } + +/* Cache channel data along the path used by this flow. */ +static struct channel_data *new_channel_path_cache(const tal_t *ctx, + struct route_query *rq, + struct flow *flow) +{ + const size_t pathlen = tal_count(flow->path); + struct channel_data *path = tal_arr(ctx, struct channel_data, pathlen); + + for (size_t i = 0; i < pathlen; i++) { + /* knowledge on liquidity bounds */ + struct amount_msat known_min, known_max; const struct half_chan *h = flow_edge(flow, i); - struct amount_msat min; struct short_channel_id_dir scidd; get_scidd(rq->gossmap, flow, i, &scidd); - min = get_chan_htlc_min(rq, flow->path[i], &scidd); - - if (amount_msat_less(msat, min)) { - return rq_log(ctx, rq, LOG_UNUSUAL, - "Amount %s below minimum %s across %s", - fmt_amount_msat(tmpctx, msat), - fmt_amount_msat(tmpctx, min), - fmt_short_channel_id_dir(tmpctx, &scidd)); - } - if (!amount_msat_add_fee(&msat, - h->base_fee, h->proportional_fee)) - plugin_err(rq->plugin, "Adding fee to amount"); + get_constraints(rq, flow->path[i], flow->dirs[i], &known_min, + &known_max); + + path[i].htlc_min = get_chan_htlc_min(rq, flow->path[i], &scidd); + path[i].htlc_max = get_chan_htlc_max(rq, flow->path[i], &scidd); + path[i].fee_base_msat = h->base_fee; + path[i].fee_proportional_millionths = h->proportional_fee; + path[i].liquidity_max = known_max; + path[i].scidd = scidd; } - return NULL; + return path; } -static struct amount_msat flow_remaining_capacity(const struct route_query *rq, - struct reserve_hop **reservations, - const struct flow *flow) +/* Cache channel data along multiple paths. */ +static struct channel_data **new_channel_mpp_cache(const tal_t *ctx, + struct route_query *rq, + struct flow **flows) { - struct amount_msat max, diff; - - /* Remove ourselves from reservation temporarily, so we don't - * accidentally cap! */ - remove_flow_reservations(rq, reservations, flow); - flow_max_capacity(rq, flow, &max, NULL, NULL); - - /* This seems to happen. Which is strange, since flows should - already be constrained. */ - if (!amount_msat_sub(&diff, max, flow->delivers)) { - plugin_log(rq->plugin, LOG_BROKEN, - "Flow delivers %s but max only %s? flow=%s", - fmt_amount_msat(tmpctx, flow->delivers), - fmt_amount_msat(tmpctx, max), - fmt_flow_full(rq, rq, flow)); - for (size_t i = 0; i < tal_count(*reservations); i++) { - plugin_log(rq->plugin, LOG_BROKEN, - "Reservation #%zi: %s on %s", - i, - fmt_amount_msat(tmpctx, (*reservations)[i].amount), - fmt_short_channel_id_dir(tmpctx, &(*reservations)[i].scidd)); - } - diff = AMOUNT_MSAT(0); + const size_t npaths = tal_count(flows); + struct channel_data **paths = + tal_arr(ctx, struct channel_data *, npaths); + for (size_t i = 0; i < npaths; i++) { + paths[i] = new_channel_path_cache(paths, rq, flows[i]); } - create_flow_reservations(rq, reservations, flow); - return diff; + return paths; } -/* What's the "best" flow to add to? */ -static struct flow *pick_most_likely_flow(const struct route_query *rq, - struct flow **flows, - struct reserve_hop **reservations, - struct amount_msat additional) +/* Given the channel constraints, return the maximum amount that can be + * delivered. */ +static struct amount_msat path_max_deliverable(struct channel_data *path) { - double best_prob = 0; - struct flow *best_flow = NULL; - - for (size_t i = 0; i < tal_count(flows); i++) { - struct amount_msat cap; - double prob = flow_probability(flows[i], rq); - if (prob < best_prob) - continue; - cap = flow_remaining_capacity(rq, reservations, flows[i]); - if (amount_msat_less(cap, additional)) - continue; - best_prob = prob; - best_flow = flows[i]; + struct amount_msat deliver = AMOUNT_MSAT(-1); + for (size_t i = 0; i < tal_count(path); i++) { + deliver = + amount_msat_sub_fee(deliver, path[i].fee_base_msat, + path[i].fee_proportional_millionths); + deliver = amount_msat_min(deliver, path[i].htlc_max); + deliver = amount_msat_min(deliver, path[i].liquidity_max); } - - return best_flow; + return deliver; } -/* A secondary check for htlc_min violations, after excess trimming. */ -static const char *flow_violates_min(const tal_t *ctx, - struct route_query *rq, - const struct flow *flow) +/* Given the channel constraints, return the maximum amount that can be + * delivered for the least sendable. ie. the minimum amount one can deliver such + * that all htlc_min are satisfied and forwarding fees are not overpayed. + * Notice: + * - the minimum amount that can be delivered is trivially the htlc_min in the + * last hop: least_delvr, + * - the minimum amount that can be sent is the least we can insert at be + * begining of the path to ensure all htlc_min are satisfied along the way + * until the destination, including least_delvr at the destination: + * least_send, + * - usually the sender tries to route with the least routing costs, that + * includes maximizing the amount at the destination, so least_send_max_delvr + * (what this function computes) is the maximum we can deliver at the + * destination when sending least_send, this already implies that htlc_min are + * satisfied. For simplicity we call this min_deliverable even though + * technically it isn't. */ +static struct amount_msat path_min_deliverable(struct channel_data *path) { - struct amount_msat msat = flow->delivers; - for (int i = tal_count(flow->path) - 1; i >= 0; i--) { - const struct half_chan *h = flow_edge(flow, i); - struct amount_msat min; - struct short_channel_id_dir scidd; - get_scidd(rq->gossmap, flow, i, &scidd); - - min = get_chan_htlc_min(rq, flow->path[i], &scidd); - if (amount_msat_less(msat, min)) { - return rq_log(ctx, rq, LOG_UNUSUAL, - "Sending %s across %s would violate htlc_min (~%s)", - fmt_amount_msat(tmpctx, msat), - fmt_short_channel_id_dir(tmpctx, &scidd), - fmt_amount_msat(tmpctx, min)); - } - if (!amount_msat_add_fee(&msat, h->base_fee, h->proportional_fee)) - plugin_err(rq->plugin, "Adding fee to amount"); + struct amount_msat least_send = AMOUNT_MSAT(0); + const size_t pathlen = tal_count(path); + for (size_t i = pathlen - 1; i < pathlen; i--) { + least_send = amount_msat_max(least_send, path[i].htlc_min); + if (!amount_msat_add_fee(&least_send, path[i].fee_base_msat, + path[i].fee_proportional_millionths)) + abort(); } - return NULL; + struct amount_msat max_destination = least_send; + for (size_t i = 0; i < pathlen; i++) { + max_destination = + amount_msat_sub_fee(max_destination, path[i].fee_base_msat, + path[i].fee_proportional_millionths); + assert( + amount_msat_greater_eq(max_destination, path[i].htlc_min)); + } + return max_destination; } -/* If one flow is constrained by htlc_max, we might be able to simply - * duplicate it. This is naive: it could still fail due to total - * capacity, but it is a corner case anyway. */ -static bool duplicate_one_flow(const struct route_query *rq, - struct reserve_hop **reservations, - struct flow ***flows) +/* Reverse order: bigger first */ +static int revcmp_flows(const size_t *a, const size_t *b, struct flow **flows) { - for (size_t i = 0; i < tal_count(*flows); i++) { - struct flow *flow = (*flows)[i], *new_flow; - struct amount_msat max, new_amount; - /* Don't create 0 flow (shouldn't happen, but be sure) */ - if (amount_msat_less(flow->delivers, AMOUNT_MSAT(2))) - continue; - - if (flow_max_capacity(rq, flow, &max, NULL, NULL) - != CAPPED_HTLC_MAX) - continue; - - new_flow = tal(*flows, struct flow); - new_flow->path = tal_dup_talarr(new_flow, - const struct gossmap_chan *, - flow->path); - new_flow->dirs = tal_dup_talarr(new_flow, int, - flow->dirs); - new_flow->delivers = amount_msat_div(flow->delivers, 2); - create_flow_reservations(rq, reservations, new_flow); - - if (!amount_msat_sub(&new_amount, - flow->delivers, new_flow->delivers)) - abort(); - change_flow_delivers(rq, flow, reservations, new_amount); - tal_arr_expand(flows, new_flow); - return true; - } - return false; + if (amount_msat_eq(flows[*a]->delivers, flows[*b]->delivers)) + return 0; + if (amount_msat_greater(flows[*a]->delivers, flows[*b]->delivers)) + return -1; + return 1; } -/* Stolen whole-cloth from @Lagrang3 in renepay's flow.c. Wrong - * because of htlc overhead in reservations! */ -static double edge_probability(const struct route_query *rq, - const struct short_channel_id_dir *scidd, - struct amount_msat sent) +static struct amount_msat sum_all_deliver(struct flow **flows, + size_t *flows_index) { - struct amount_msat numerator, denominator; - struct amount_msat mincap, maxcap, additional; - const struct gossmap_chan *c = gossmap_find_chan(rq->gossmap, &scidd->scid); - - get_constraints(rq, c, scidd->dir, &mincap, &maxcap); - - /* We add an extra per-htlc reservation for the *next* HTLC, so we "over-reserve" - * on local channels. Undo that! */ - additional = get_additional_per_htlc_cost(rq, scidd); - if (!amount_msat_accumulate(&mincap, additional) - || !amount_msat_accumulate(&maxcap, additional)) - abort(); - - if (amount_msat_less_eq(sent, mincap)) - return 1.0; - else if (amount_msat_greater(sent, maxcap)) - return 0.0; - - /* Linear probability: 1 - (spend - min) / (max - min) */ - - /* spend > mincap, from above. */ - if (!amount_msat_sub(&numerator, sent, mincap)) - abort(); - /* This can only fail is maxcap was < mincap, - * so we would be captured above */ - if (!amount_msat_sub(&denominator, maxcap, mincap)) - abort(); - return 1.0 - amount_msat_ratio(numerator, denominator); + struct amount_msat all_deliver = AMOUNT_MSAT(0); + for (size_t i = 0; i < tal_count(flows_index); i++) { + if (!amount_msat_accumulate(&all_deliver, + flows[flows_index[i]]->delivers)) + abort(); + } + return all_deliver; } -const char * -refine_with_fees_and_limits(const tal_t *ctx, - struct route_query *rq, - struct amount_msat deliver, - struct flow ***flows, - double *flowset_probability) +/* It reduces the amount of the flows and/or removes some flows in order to + * deliver no more than max_deliver. It will leave at least one flow. + * Returns the total delivery amount. */ +static struct amount_msat remove_excess(struct flow **flows, + size_t **flows_index, + struct amount_msat max_deliver) { - struct reserve_hop *reservations = new_reservations(NULL, rq); - struct amount_msat more_to_deliver; - const char *flow_constraint_error = NULL; - const char *ret; - bool tried_split_flow = false; - - for (size_t i = 0; i < tal_count(*flows);) { - struct flow *flow = (*flows)[i]; - - flow_constraint_error = constrain_flow(tmpctx, rq, flow); - if (!flow_constraint_error) { - create_flow_reservations(rq, &reservations, flow); - i++; - continue; - } + if (tal_count(flows) == 0) + return AMOUNT_MSAT(0); - rq_log(tmpctx, rq, LOG_UNUSUAL, "Flow %zu/%zu was too constrained (%s) so removing it", - i, tal_count(*flows), flow_constraint_error); - /* This flow was reduced to 0 / impossible, remove */ - tal_arr_remove(flows, i); - } + struct amount_msat all_deliver, excess; + all_deliver = sum_all_deliver(flows, *flows_index); - /* Due to granularity of MCF, we can deliver slightly more than expected: - * trim one in that case. */ - if (!amount_msat_sub(&more_to_deliver, deliver, - flowset_delivers(rq->plugin, *flows))) { - struct amount_msat excess; - if (!amount_msat_sub(&excess, - flowset_delivers(rq->plugin, *flows), - deliver)) - abort(); - for (size_t i = 0; i < tal_count(*flows); i++) { - const char *err; - struct amount_msat trimmed; - if (!amount_msat_sub(&trimmed, (*flows)[i]->delivers, excess)) - continue; - - rq_log(tmpctx, rq, LOG_DBG, - "%s extra, trimming flow %zu/%zu", - fmt_amount_msat(tmpctx, excess), i, tal_count(*flows)); - change_flow_delivers(rq, (*flows)[i], &reservations, trimmed); - /* In theory, this can violate min_htlc! Thanks @Lagrang3! */ - err = flow_violates_min(tmpctx, rq, (*flows)[i]); - if (err) { - /* This flow was reduced to 0 / impossible, remove */ - remove_flow_reservations(rq, &reservations, (*flows)[i]); - tal_arr_remove(flows, i); - i--; - /* If this causes failure, indicate why! */ - flow_constraint_error = err; - continue; - } - break; - } + /* early exit: there is no excess */ + if (!amount_msat_sub(&excess, all_deliver, max_deliver) || + amount_msat_is_zero(excess)) + return all_deliver; - /* Usually this should shed excess, *BUT* maybe one - * was deleted instead for being below minimum */ - if (!amount_msat_sub(&more_to_deliver, deliver, - flowset_delivers(rq->plugin, *flows))) { - ret = rq_log(ctx, rq, LOG_UNUSUAL, - "Flowset delivers %s instead of %s, can't shed excess?", - fmt_amount_msat(tmpctx, flowset_delivers(rq->plugin, *flows)), - fmt_amount_msat(tmpctx, deliver)); - goto out; - } + asort(*flows_index, tal_count(*flows_index), revcmp_flows, flows); - rq_log(tmpctx, rq, LOG_DBG, - "After dealing with excess, more_to_deliver=%s", - fmt_amount_msat(tmpctx, more_to_deliver)); + /* Remove the smaller parts if they deliver less than the + * excess. */ + for (int i = tal_count(*flows_index) - 1; i >= 0; i--) { + if (!amount_msat_sub(&excess, excess, + flows[(*flows_index)[i]]->delivers)) + break; + if (!amount_msat_sub(&all_deliver, all_deliver, + flows[(*flows_index)[i]]->delivers)) + abort(); + tal_arr_remove(flows_index, i); } - /* The residual is minimal. In theory we could add one msat at a time - * to the most probably flow which has capacity. For speed, we break it - * into the number of flows, then assign each one. */ - while (!amount_msat_is_zero(more_to_deliver) && tal_count(*flows)) { - struct flow *f; - struct amount_msat extra, new_delivers; - - /* How much more do we deliver? Round up if we can */ - extra = amount_msat_div(more_to_deliver, tal_count(*flows)); - if (amount_msat_less(extra, more_to_deliver)) { - if (!amount_msat_accumulate(&extra, AMOUNT_MSAT(1))) - abort(); - } + /* If we still have some excess, remove it from the + * current flows in the same proportion every flow contributes to the + * total. */ + struct amount_msat old_excess = excess; + struct amount_msat old_deliver = all_deliver; + for (size_t i = 0; i < tal_count(*flows_index); i++) { + double fraction = amount_msat_ratio( + flows[(*flows_index)[i]]->delivers, old_deliver); + struct amount_msat remove; + + if (!amount_msat_scale(&remove, old_excess, fraction)) + abort(); - /* This happens when we have a single flow, and hit - * htlc_max. For this corner case, we split into an - * additional flow, but only once! */ - f = pick_most_likely_flow(rq, *flows, &reservations, extra); - if (!f) { - if (tried_split_flow || !duplicate_one_flow(rq, &reservations, flows)) { - ret = rq_log(ctx, rq, LOG_BROKEN, - "We couldn't quite afford it, we need to send %s more for fees: please submit a bug report!", - fmt_amount_msat(tmpctx, more_to_deliver)); - goto out; - } - tried_split_flow = true; - continue; - } + /* rounding errors: don't remove more than excess */ + remove = amount_msat_min(remove, excess); - if (!amount_msat_add(&new_delivers, f->delivers, extra)) + if (!amount_msat_sub(&excess, excess, remove)) abort(); - change_flow_delivers(rq, f, &reservations, new_delivers); - /* Should not happen, since extra comes from div... */ - if (!amount_msat_sub(&more_to_deliver, more_to_deliver, extra)) + if (!amount_msat_sub(&all_deliver, all_deliver, remove) || + !amount_msat_sub(&flows[(*flows_index)[i]]->delivers, + flows[(*flows_index)[i]]->delivers, + remove)) abort(); } - if (!amount_msat_eq(deliver, flowset_delivers(rq->plugin, *flows))) { - /* This should only happen if there were no flows */ - if (tal_count(*flows) == 0) { - ret = tal_steal(ctx, flow_constraint_error); - goto out; - } - plugin_err(rq->plugin, "Flows delivered only %s of %s?", - fmt_amount_msat(tmpctx, flowset_delivers(rq->plugin, *flows)), - fmt_amount_msat(tmpctx, deliver)); - } - - ret = NULL; - - /* Total flowset probability is now easily calculated given reservations - * contains the total amounts through each channel (once we remove them) */ - destroy_reservations(reservations, get_askrene(rq->plugin)); - tal_add_destructor2(reservations, destroy_reservations, get_askrene(rq->plugin)); + /* any rounding error left, take it from the first */ + assert(tal_count(*flows_index) > 0); + if (!amount_msat_sub(&all_deliver, all_deliver, excess) || + !amount_msat_sub(&flows[(*flows_index)[0]]->delivers, + flows[(*flows_index)[0]]->delivers, excess)) + abort(); + return all_deliver; +} - *flowset_probability = 1.0; - for (size_t i = 0; i < tal_count(reservations); i++) { - const struct reserve_hop *r = &reservations[i]; - *flowset_probability *= edge_probability(rq, &r->scidd, r->amount); +/* FIXME: on failure return error message */ +const char *refine_with_fees_and_limits(const tal_t *ctx, + struct route_query *rq, + struct amount_msat deliver, + struct flow ***flows) +{ + const tal_t *working_ctx = tal(ctx, tal_t); + struct amount_msat *max_deliverable; + struct amount_msat *min_deliverable; + struct channel_data **channel_mpp_cache; + size_t *flows_index; + + /* we might need to access this data multiple times, so we cache + * it */ + channel_mpp_cache = new_channel_mpp_cache(working_ctx, rq, *flows); + max_deliverable = tal_arrz(working_ctx, struct amount_msat, + tal_count(channel_mpp_cache)); + min_deliverable = tal_arrz(working_ctx, struct amount_msat, + tal_count(channel_mpp_cache)); + flows_index = tal_arrz(working_ctx, size_t, tal_count(*flows)); + for (size_t i = 0; i < tal_count(channel_mpp_cache); i++) { + // FIXME: does path_max_deliverable work for a single + // channel with 0 fees? + max_deliverable[i] = path_max_deliverable(channel_mpp_cache[i]); + min_deliverable[i] = path_min_deliverable(channel_mpp_cache[i]); + /* We use an array of indexes to keep track of the order + * of the flows. Likewise flows can be removed by simply + * shrinking the flows_index array. */ + flows_index[i] = i; + } + + /* do not deliver more than HTLC_MAX allow us */ + for (size_t i = 0; i < tal_count(flows_index); i++) { + (*flows)[flows_index[i]]->delivers = + amount_msat_min((*flows)[flows_index[i]]->delivers, + max_deliverable[flows_index[i]]); + } + + /* remove excess from MCF granularity if any */ + remove_excess(*flows, &flows_index, deliver); + + /* detect htlc_min violations */ + for(size_t i=0; idelivers, min_deliverable[k])){ + i++; + continue; + } + /* htlc_min is not met for this flow */ + tal_arr_remove(&flows_index, i); + remove_htlc_min_violations(working_ctx, rq, (*flows)[k], + channel_mpp_cache[k]); + } + + /* remove 0 amount flows if any */ + asort(flows_index, tal_count(flows_index), revcmp_flows, *flows); + for (int i = tal_count(flows_index) - 1; i >= 0; i--) { + if(!amount_msat_is_zero((*flows)[flows_index[i]]->delivers)) + break; + tal_arr_remove(&flows_index, i); } -out: - tal_free(reservations); - return ret; + tal_free(working_ctx); + return NULL; } diff --git a/plugins/askrene/refine.h b/plugins/askrene/refine.h index 66befec9b1e1..94660a2c4f2e 100644 --- a/plugins/askrene/refine.h +++ b/plugins/askrene/refine.h @@ -7,6 +7,13 @@ struct route_query; struct amount_msat; struct flow; +struct reserve_hop *new_reservations(const tal_t *ctx, + const struct route_query *rq); + +void create_flow_reservations(const struct route_query *rq, + struct reserve_hop **reservations, + const struct flow *flow); + /* We got an answer from min-cost-flow, but we now need to: * 1. Add fixup exact delivery amounts since MCF deals in larger granularity than msat. * 2. Add fees which accumulate through the route. @@ -17,10 +24,8 @@ struct flow; * * Returns NULL on success, or an error message for the caller. */ -const char * -refine_with_fees_and_limits(const tal_t *ctx, - struct route_query *rq, - struct amount_msat deliver, - struct flow ***flows, - double *flowset_probability); +const char *refine_with_fees_and_limits(const tal_t *ctx, + struct route_query *rq, + struct amount_msat deliver, + struct flow ***flows); #endif /* LIGHTNING_PLUGINS_ASKRENE_REFINE_H */ diff --git a/tests/test_askrene.py b/tests/test_askrene.py index 8944fc262001..21a194ba065b 100644 --- a/tests/test_askrene.py +++ b/tests/test_askrene.py @@ -568,25 +568,88 @@ def test_getroutes(node_factory): 'amount_msat': 5500005, 'delay': 99 + 6}]]) - # We realize that this is impossible in a single path: - with pytest.raises(RpcError, match="The shortest path is 0x2x1, but 0x2x1/1 marked disabled by layer auto.no_mpp_support."): - l1.rpc.getroutes(source=nodemap[0], - destination=nodemap[2], - amount_msat=10000000, - layers=['auto.no_mpp_support'], - maxfee_msat=1000, - final_cltv=99) - # But this will work. - check_getroute_paths(l1, - nodemap[0], - nodemap[2], - 9000000, - [[{'short_channel_id_dir': '0x2x3/1', - 'next_node_id': nodemap[2], - 'amount_msat': 9000009, - 'delay': 99 + 6}]], - layers=['auto.no_mpp_support']) +def test_getroutes_single_path(node_factory): + """Test getroutes generating single path payments""" + gsfile, nodemap = generate_gossip_store( + [ + GenChannel(0, 1), + GenChannel(1, 2, capacity_sats=9000), + GenChannel(1, 2, capacity_sats=10000), + ] + ) + # Set up l1 with this as the gossip_store + l1 = node_factory.get_node(gossip_store_file=gsfile.name) + + # To be able to route this amount two parts are needed, therefore a single + # pay search will fail. + # FIXME: the explanation for the failure is wrong + with pytest.raises(RpcError): + l1.rpc.getroutes( + source=nodemap[1], + destination=nodemap[2], + amount_msat=10000001, + layers=["auto.no_mpp_support"], + maxfee_msat=1000, + final_cltv=99, + ) + + # For this amount, only one solution is possible + check_getroute_paths( + l1, + nodemap[1], + nodemap[2], + 10000000, + [ + [ + { + "short_channel_id_dir": "1x2x2/1", + "next_node_id": nodemap[2], + "amount_msat": 10000010, + "delay": 99 + 6, + } + ] + ], + layers=["auto.no_mpp_support"], + ) + + # To be able to route this amount two parts are needed, therefore a single + # pay search will fail. + # FIXME: the explanation for the failure is wrong + with pytest.raises(RpcError): + l1.rpc.getroutes( + source=nodemap[0], + destination=nodemap[2], + amount_msat=10000001, + layers=["auto.no_mpp_support"], + maxfee_msat=1000, + final_cltv=99, + ) + + # For this amount, only one solution is possible + check_getroute_paths( + l1, + nodemap[0], + nodemap[2], + 10000000, + [ + [ + { + "short_channel_id_dir": "0x1x0/1", + "next_node_id": nodemap[1], + "amount_msat": 10000020, + "delay": 99 + 6 + 6, + }, + { + "short_channel_id_dir": "1x2x2/1", + "next_node_id": nodemap[2], + "amount_msat": 10000010, + "delay": 99 + 6, + }, + ] + ], + layers=["auto.no_mpp_support"], + ) def test_getroutes_fee_fallback(node_factory): @@ -1141,7 +1204,7 @@ def test_real_data(node_factory, bitcoind): # CI, it's slow. if SLOW_MACHINE: limit = 25 - expected = (6, 25, 1544756, 142986, 91) + expected = (5, 25, 1567536, 142772, 91) else: limit = 100 expected = (9, 95, 6347877, 566288, 92) @@ -1258,7 +1321,7 @@ def test_real_biases(node_factory, bitcoind): # CI, it's slow. if SLOW_MACHINE: limit = 25 - expected = ({1: 5, 2: 7, 4: 7, 8: 11, 16: 14, 32: 19, 64: 25, 100: 25}, 0) + expected = ({1: 6, 2: 6, 4: 7, 8: 12, 16: 14, 32: 19, 64: 25, 100: 25}, 0) else: limit = 100 expected = ({1: 23, 2: 31, 4: 40, 8: 53, 16: 70, 32: 82, 64: 96, 100: 96}, 0)