Skip to content

Commit e61d0b3

Browse files
committed
askrene: rework the caller of the MCF solver
We use a wrapper around the MCF solver that takes care of finding the best linearization parameters and fixing the flow values to meet the htlc_min and htlc_max constraints. We have reworked the current implementation and made it a bit more similar to renepay's version. Changelog-None. Signed-off-by: Lagrang3 <[email protected]>
1 parent 453467a commit e61d0b3

File tree

1 file changed

+183
-126
lines changed

1 file changed

+183
-126
lines changed

plugins/askrene/mcf.c

Lines changed: 183 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ static const double CHANNEL_PIVOTS[]={0,0.5,0.8,0.95};
169169
static const s64 INFINITE = INT64_MAX;
170170
static const s64 MU_MAX = 100;
171171

172+
/* every payment under 1000sat will be routed through a single path */
173+
static const struct amount_msat SINGLE_PATH_THRESHOLD = AMOUNT_MSAT(1000000);
174+
172175
/* Let's try this encoding of arcs:
173176
* Each channel `c` has two possible directions identified by a bit
174177
* `half` or `!half`, and each one of them has to be
@@ -1064,22 +1067,6 @@ struct flow **minflow(const tal_t *ctx,
10641067
return NULL;
10651068
}
10661069

1067-
static struct amount_msat linear_flows_cost(struct flow **flows,
1068-
struct amount_msat total_amount,
1069-
double delay_feefactor)
1070-
{
1071-
struct amount_msat total = AMOUNT_MSAT(0);
1072-
1073-
for (size_t i = 0; i < tal_count(flows); i++) {
1074-
if (!amount_msat_accumulate(&total,
1075-
linear_flow_cost(flows[i],
1076-
total_amount,
1077-
delay_feefactor)))
1078-
abort();
1079-
}
1080-
return total;
1081-
}
1082-
10831070
/* Initialize the data vectors for the single-path solver. */
10841071
static void init_linear_network_single_path(
10851072
const tal_t *ctx, const struct pay_parameters *params, struct graph **graph,
@@ -1260,6 +1247,13 @@ struct flow **single_path_flow(const tal_t *ctx, const struct route_query *rq,
12601247
return NULL;
12611248
}
12621249

1250+
/* FIXME: add extra constraint maximum route length, use an activation
1251+
* probability cost for each channel. Recall that every activation cost, eg.
1252+
* base fee and activation probability can only be properly added modifying the
1253+
* graph topology by creating an activation node for every half channel. */
1254+
/* FIXME: add extra constraint maximum number of routes, fixes issue 8331. */
1255+
/* FIXME: add a boolean option to make recipient pay for fees, fixes issue 8353.
1256+
*/
12631257
static const char *
12641258
linear_routes(const tal_t *ctx, struct route_query *rq,
12651259
const struct gossmap_node *srcnode,
@@ -1271,133 +1265,196 @@ linear_routes(const tal_t *ctx, struct route_query *rq,
12711265
const struct gossmap_node *,
12721266
struct amount_msat, u32, double))
12731267
{
1274-
*flows = NULL;
1275-
const char *ret;
1276-
double delay_feefactor = 1.0 / 1000000;
1277-
1278-
/* First up, don't care about fees (well, just enough to tiebreak!) */
1268+
const tal_t *working_ctx = tal(ctx, tal_t);
1269+
const char *error_message;
1270+
struct amount_msat amount_to_deliver = amount;
1271+
struct amount_msat feebudget = maxfee;
1272+
1273+
/* FIXME: mu is an integer from 0 to MU_MAX that we use to combine fees
1274+
* and probability costs, but I think we can make it a real number from
1275+
* 0 to 1. */
12791276
u32 mu = 1;
1280-
tal_free(*flows);
1281-
*flows = solver(ctx, rq, srcnode, dstnode, amount, mu, delay_feefactor);
1282-
if (!*flows) {
1283-
ret = explain_failure(ctx, rq, srcnode, dstnode, amount);
1284-
goto fail;
1285-
}
1277+
/* we start at 1e-6 and increase it exponentially (x2) up to 10. */
1278+
double delay_feefactor = 1e-6;
1279+
1280+
struct flow **new_flows = NULL;
1281+
struct amount_msat all_deliver;
1282+
1283+
*flows = tal_arr(working_ctx, struct flow *, 0);
1284+
1285+
/* Re-use the reservation system to make flows aware of each other. */
1286+
struct reserve_hop *reservations = new_reservations(working_ctx, rq);
1287+
1288+
/* compute the probability one flow at a time. */
1289+
*probability = 1.0;
1290+
1291+
while (!amount_msat_is_zero(amount_to_deliver)) {
1292+
new_flows = tal_free(new_flows);
1293+
1294+
/* If the amount_to_deliver is very small we better use a single
1295+
* path computation because:
1296+
* 1. we save cpu cycles
1297+
* 2. we have better control over htlc_min violations.
1298+
* We need to make the distinction here because after
1299+
* refine_with_fees_and_limits we might have a set of flows that
1300+
* do not deliver the entire payment amount by just a small
1301+
* amount. */
1302+
if(amount_msat_less_eq(amount_to_deliver, SINGLE_PATH_THRESHOLD)){
1303+
new_flows = single_path_flow(working_ctx, rq, srcnode, dstnode,
1304+
amount_to_deliver, mu, delay_feefactor);
1305+
} else {
1306+
new_flows =
1307+
solver(working_ctx, rq, srcnode, dstnode,
1308+
amount_to_deliver, mu, delay_feefactor);
1309+
}
12861310

1287-
/* Too much delay? */
1288-
while (finalcltv + flows_worst_delay(*flows) > maxdelay) {
1289-
delay_feefactor *= 2;
1290-
rq_log(tmpctx, rq, LOG_UNUSUAL,
1291-
"The worst flow delay is %" PRIu64
1292-
" (> %i), retrying with delay_feefactor %f...",
1293-
flows_worst_delay(*flows), maxdelay - finalcltv,
1294-
delay_feefactor);
1295-
tal_free(*flows);
1296-
*flows = solver(ctx, rq, srcnode, dstnode, amount, mu,
1297-
delay_feefactor);
1298-
if (!*flows || delay_feefactor > 10) {
1299-
ret = rq_log(
1300-
ctx, rq, LOG_UNUSUAL,
1301-
"Could not find route without excessive delays");
1311+
if (!new_flows) {
1312+
error_message = explain_failure(
1313+
ctx, rq, srcnode, dstnode, amount_to_deliver);
13021314
goto fail;
13031315
}
1304-
}
13051316

1306-
/* Too expensive? */
1307-
too_expensive:
1308-
while (amount_msat_greater(flowset_fee(rq->plugin, *flows), maxfee)) {
1309-
struct flow **new_flows;
1310-
1311-
if (mu == 1)
1312-
mu = 10;
1313-
else
1314-
mu += 10;
1315-
rq_log(tmpctx, rq, LOG_UNUSUAL,
1316-
"The flows had a fee of %s, greater than max of %s, "
1317-
"retrying with mu of %u%%...",
1318-
fmt_amount_msat(tmpctx, flowset_fee(rq->plugin, *flows)),
1319-
fmt_amount_msat(tmpctx, maxfee), mu);
1320-
new_flows = solver(ctx, rq, srcnode, dstnode, amount,
1321-
mu > 100 ? 100 : mu, delay_feefactor);
1322-
if (!*flows || mu >= 100) {
1323-
ret = rq_log(
1324-
ctx, rq, LOG_UNUSUAL,
1325-
"Could not find route without excessive cost");
1317+
error_message =
1318+
refine_flows(ctx, rq, amount_to_deliver, &new_flows);
1319+
if (error_message)
13261320
goto fail;
1321+
1322+
/* we finished removing flows and excess */
1323+
all_deliver = flowset_delivers(rq->plugin, new_flows);
1324+
if (amount_msat_is_zero(all_deliver)) {
1325+
/* We removed all flows and we have not modified the
1326+
* MCF parameters. We will not have an infinite loop
1327+
* here because at least we have disabled some channels.
1328+
*/
1329+
continue;
1330+
}
1331+
1332+
/* We might want to overpay sometimes, eg. shadow routing, but
1333+
* right now if all_deliver > amount_to_deliver means a bug. */
1334+
assert(amount_msat_greater_eq(amount_to_deliver, all_deliver));
1335+
1336+
/* no flows should send 0 amount */
1337+
for (size_t i = 0; i < tal_count(new_flows); i++) {
1338+
// FIXME: replace all assertions with LOG_BROKEN
1339+
assert(!amount_msat_is_zero(new_flows[i]->delivers));
13271340
}
13281341

1329-
/* This is possible, because MCF's linear fees are not the same.
1330-
*/
1331-
if (amount_msat_greater(flowset_fee(rq->plugin, new_flows),
1332-
flowset_fee(rq->plugin, *flows))) {
1333-
struct amount_msat old_cost =
1334-
linear_flows_cost(*flows, amount, delay_feefactor);
1335-
struct amount_msat new_cost = linear_flows_cost(
1336-
new_flows, amount, delay_feefactor);
1337-
if (amount_msat_greater_eq(new_cost, old_cost)) {
1338-
rq_log(tmpctx, rq, LOG_BROKEN,
1339-
"Old flows cost %s:",
1340-
fmt_amount_msat(tmpctx, old_cost));
1341-
for (size_t i = 0; i < tal_count(*flows); i++) {
1342-
rq_log(
1343-
tmpctx, rq, LOG_BROKEN,
1344-
"Flow %zu/%zu: %s (linear cost %s)",
1345-
i, tal_count(*flows),
1346-
fmt_flow_full(tmpctx, rq, (*flows)[i]),
1347-
fmt_amount_msat(
1348-
tmpctx, linear_flow_cost(
1349-
(*flows)[i], amount,
1350-
delay_feefactor)));
1351-
}
1352-
rq_log(tmpctx, rq, LOG_BROKEN,
1353-
"Old flows cost %s:",
1354-
fmt_amount_msat(tmpctx, new_cost));
1355-
for (size_t i = 0; i < tal_count(new_flows);
1356-
i++) {
1357-
rq_log(
1358-
tmpctx, rq, LOG_BROKEN,
1359-
"Flow %zu/%zu: %s (linear cost %s)",
1360-
i, tal_count(new_flows),
1361-
fmt_flow_full(tmpctx, rq,
1362-
new_flows[i]),
1363-
fmt_amount_msat(
1364-
tmpctx,
1365-
linear_flow_cost(
1366-
new_flows[i], amount,
1367-
delay_feefactor)));
1368-
}
1342+
/* Is this set of flows too expensive?
1343+
* We can check if the new flows are within the fee budget,
1344+
* however in some cases we have discarded some flows at this
1345+
* point and the new flows do not deliver all the value we need
1346+
* so that a further solver iteration is needed. Hence we
1347+
* check if the fees paid by these new flows are below the
1348+
* feebudget proportionally adjusted by the amount this set of
1349+
* flows deliver with respect to the total remaining amount,
1350+
* ie. we avoid "consuming" all the feebudget if we still need
1351+
* to run MCF again for some remaining amount. */
1352+
const struct amount_msat all_fees =
1353+
flowset_fee(rq->plugin, new_flows);
1354+
const double deliver_fraction =
1355+
amount_msat_ratio(all_deliver, amount_to_deliver);
1356+
struct amount_msat partial_feebudget;
1357+
if (!amount_msat_scale(&partial_feebudget, feebudget,
1358+
deliver_fraction)) {
1359+
error_message =
1360+
rq_log(ctx, rq, LOG_BROKEN,
1361+
"%s: failed to scale the fee budget (%s) by "
1362+
"fraction (%lf)",
1363+
__func__, fmt_amount_msat(tmpctx, feebudget),
1364+
deliver_fraction);
1365+
goto fail;
1366+
}
1367+
if (amount_msat_greater(all_fees, partial_feebudget)) {
1368+
if (mu < MU_MAX) {
1369+
/* all_fees exceed the strong budget limit, try
1370+
* to fix it increasing mu. */
1371+
if (mu == 1)
1372+
mu = 10;
1373+
else
1374+
mu += 10;
1375+
mu = MIN(mu, MU_MAX);
1376+
rq_log(
1377+
tmpctx, rq, LOG_INFORM,
1378+
"The flows had a fee of %s, greater than "
1379+
"max of %s, retrying with mu of %u%%...",
1380+
fmt_amount_msat(tmpctx, all_fees),
1381+
fmt_amount_msat(tmpctx, partial_feebudget),
1382+
mu);
1383+
continue;
1384+
} else if (amount_msat_greater(all_fees, feebudget)) {
1385+
/* we cannot increase mu anymore and all_fees
1386+
* already exceeds feebudget we fail. */
1387+
error_message =
1388+
rq_log(ctx, rq, LOG_UNUSUAL,
1389+
"Could not find route without "
1390+
"excessive cost");
1391+
goto fail;
1392+
} else {
1393+
/* mu cannot be increased but at least all_fees
1394+
* does not exceed feebudget, we give it a shot.
1395+
*/
1396+
rq_log(
1397+
tmpctx, rq, LOG_UNUSUAL,
1398+
"The flows had a fee of %s, greater than "
1399+
"max of %s, but still within the fee "
1400+
"budget %s, we accept those flows.",
1401+
fmt_amount_msat(tmpctx, all_fees),
1402+
fmt_amount_msat(tmpctx, partial_feebudget),
1403+
fmt_amount_msat(tmpctx, feebudget));
13691404
}
13701405
}
1371-
tal_free(*flows);
1372-
*flows = new_flows;
1373-
}
13741406

1375-
if (finalcltv + flows_worst_delay(*flows) > maxdelay) {
1376-
ret = rq_log(
1377-
ctx, rq, LOG_UNUSUAL,
1378-
"Could not find route without excessive cost or delays");
1379-
goto fail;
1380-
}
1407+
/* Too much delay? */
1408+
if (finalcltv + flows_worst_delay(new_flows) > maxdelay) {
1409+
if (delay_feefactor > 10) {
1410+
error_message =
1411+
rq_log(ctx, rq, LOG_UNUSUAL,
1412+
"Could not find route without "
1413+
"excessive delays");
1414+
goto fail;
1415+
}
13811416

1382-
/* The above did not take into account the extra funds to pay
1383-
* fees, so we try to adjust now. We could re-run MCF if this
1384-
* fails, but failure basically never happens where payment is
1385-
* still possible */
1386-
ret = refine_with_fees_and_limits(ctx, rq, amount, flows, probability);
1387-
if (ret)
1388-
goto fail;
1417+
delay_feefactor *= 2;
1418+
rq_log(tmpctx, rq, LOG_INFORM,
1419+
"The worst flow delay is %" PRIu64
1420+
" (> %i), retrying with delay_feefactor %f...",
1421+
flows_worst_delay(*flows), maxdelay - finalcltv,
1422+
delay_feefactor);
1423+
}
13891424

1390-
/* Again, a tiny corner case: refine step can make us exceed maxfee */
1391-
if (amount_msat_greater(flowset_fee(rq->plugin, *flows), maxfee)) {
1392-
rq_log(tmpctx, rq, LOG_UNUSUAL,
1393-
"After final refinement, fee was excessive: retrying");
1394-
goto too_expensive;
1425+
/* add the new flows to the final solution */
1426+
for (size_t i = 0; i < tal_count(new_flows); i++) {
1427+
tal_arr_expand(flows, new_flows[i]);
1428+
tal_steal(*flows, new_flows[i]);
1429+
*probability *= flow_probability(new_flows[i], rq);
1430+
create_flow_reservations(rq, &reservations,
1431+
new_flows[i]);
1432+
}
1433+
1434+
if (!amount_msat_sub(&feebudget, feebudget, all_fees) ||
1435+
!amount_msat_sub(&amount_to_deliver, amount_to_deliver,
1436+
all_deliver)) {
1437+
error_message =
1438+
rq_log(ctx, rq, LOG_BROKEN,
1439+
"%s: unexpected arithmetic operation "
1440+
"failure on amount_msat",
1441+
__func__);
1442+
goto fail;
1443+
}
13951444
}
13961445

1446+
/* transfer ownership */
1447+
*flows = tal_steal(ctx, *flows);
1448+
1449+
/* cleanup */
1450+
tal_free(working_ctx);
13971451
return NULL;
13981452
fail:
1399-
assert(ret != NULL);
1400-
return ret;
1453+
/* cleanup */
1454+
tal_free(working_ctx);
1455+
1456+
assert(error_message != NULL);
1457+
return error_message;
14011458
}
14021459

14031460
const char *default_routes(const tal_t *ctx, struct route_query *rq,

0 commit comments

Comments
 (0)