Skip to content

Commit c253cf7

Browse files
committed
[WIP] askrene: refactor 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 refactored the current implementation and made it a bit more similar to renepay's version. It should handle more cornercases... Changelog-None Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
1 parent 56dd8fd commit c253cf7

File tree

1 file changed

+168
-111
lines changed

1 file changed

+168
-111
lines changed

plugins/askrene/mcf.c

Lines changed: 168 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,6 +1256,13 @@ struct flow **single_path_flow(const tal_t *ctx, const struct route_query *rq,
12561256
return NULL;
12571257
}
12581258

1259+
/* FIXME: add extra constraint maximum route length, use an activation
1260+
* probability cost for each channel. Recall that every activation cost, eg.
1261+
* base fee and activation probability can only be properly added modifying the
1262+
* graph topology by creating an activation node for every half channel. */
1263+
/* FIXME: add extra constraint maximum number of routes, fixes issue 8331. */
1264+
/* FIXME: add a boolean option to make recipient pay for fees, fixes issue 8353.
1265+
*/
12591266
static const char *
12601267
linear_routes(const tal_t *ctx, struct route_query *rq,
12611268
const struct gossmap_node *srcnode,
@@ -1267,133 +1274,183 @@ linear_routes(const tal_t *ctx, struct route_query *rq,
12671274
const struct gossmap_node *,
12681275
struct amount_msat, u32, double))
12691276
{
1270-
*flows = NULL;
1271-
const char *ret;
1272-
double delay_feefactor = 1.0 / 1000000;
1273-
1274-
/* First up, don't care about fees (well, just enough to tiebreak!) */
1277+
const tal_t *working_ctx = tal(ctx, tal_t);
1278+
const char *error_message;
1279+
struct amount_msat amount_to_deliver = amount;
1280+
struct amount_msat feebudget = maxfee;
1281+
1282+
/* FIXME: mu is an integer from 0 to MU_MAX that we use to combine fees
1283+
* and probability costs, but I think we can make it a real number from
1284+
* 0 to 1. */
12751285
u32 mu = 1;
1276-
tal_free(*flows);
1277-
*flows = solver(ctx, rq, srcnode, dstnode, amount, mu, delay_feefactor);
1278-
if (!*flows) {
1279-
ret = explain_failure(ctx, rq, srcnode, dstnode, amount);
1280-
goto fail;
1281-
}
1286+
/* we start at 1e-6 and increase it exponentially (x2) up to 10. */
1287+
double delay_feefactor = 1e-6;
1288+
1289+
struct flow **new_flows = NULL;
1290+
1291+
*flows = tal_arr(working_ctx, struct flow *, 0);
1292+
1293+
/* Re-use the reservation system to make flows aware of each other. */
1294+
struct reserve_hop *reservations = new_reservations(working_ctx, rq);
1295+
1296+
while (!amount_msat_is_zero(amount_to_deliver)) {
1297+
new_flows = tal_free(new_flows);
12821298

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

1302-
/* Too expensive? */
1303-
too_expensive:
1304-
while (amount_msat_greater(flowset_fee(rq->plugin, *flows), maxfee)) {
1305-
struct flow **new_flows;
1306-
1307-
if (mu == 1)
1308-
mu = 10;
1309-
else
1310-
mu += 10;
1311-
rq_log(tmpctx, rq, LOG_UNUSUAL,
1312-
"The flows had a fee of %s, greater than max of %s, "
1313-
"retrying with mu of %u%%...",
1314-
fmt_amount_msat(tmpctx, flowset_fee(rq->plugin, *flows)),
1315-
fmt_amount_msat(tmpctx, maxfee), mu);
1316-
new_flows = solver(ctx, rq, srcnode, dstnode, amount,
1317-
mu > 100 ? 100 : mu, delay_feefactor);
1318-
if (!*flows || mu >= 100) {
1319-
ret = rq_log(
1320-
ctx, rq, LOG_UNUSUAL,
1321-
"Could not find route without excessive cost");
1322-
goto fail;
1307+
// TODO:
1308+
// trim flows to meet htlc_max constraints
1309+
// trim flows to deliver no more amount_to_deliver
1310+
//
1311+
// ? increase flows to deliver no less than amount_to_deliver,
1312+
// but
1313+
// take max_deliverable into consideration
1314+
//
1315+
// remove flows that have htlc_min violations, disable the
1316+
// culprit channel
1317+
1318+
// for each flow
1319+
// compute amounts and adjust to htlc max
1320+
// disable some channels to void violations of htlc_min
1321+
// call refine_with_fees_and_limits?
1322+
//
1323+
1324+
/* we finished removing flows and excess */
1325+
const struct amount_msat all_deliver =
1326+
flowset_delivers(rq->plugin, new_flows);
1327+
if (amount_msat_is_zero(all_deliver)) {
1328+
/* We removed all flows and we have not modified the
1329+
* MCF parameters. We will not have an infinite loop
1330+
* here because at least we have disabled some channels.
1331+
*/
1332+
continue;
13231333
}
13241334

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

1371-
if (finalcltv + flows_worst_delay(*flows) > maxdelay) {
1372-
ret = rq_log(
1373-
ctx, rq, LOG_UNUSUAL,
1374-
"Could not find route without excessive cost or delays");
1375-
goto fail;
1376-
}
1404+
/* Too much delay? */
1405+
if (finalcltv + flows_worst_delay(new_flows) > maxdelay) {
1406+
if (delay_feefactor > 10) {
1407+
error_message =
1408+
rq_log(ctx, rq, LOG_UNUSUAL,
1409+
"Could not find route without "
1410+
"excessive delays");
1411+
goto fail;
1412+
}
13771413

1378-
/* The above did not take into account the extra funds to pay
1379-
* fees, so we try to adjust now. We could re-run MCF if this
1380-
* fails, but failure basically never happens where payment is
1381-
* still possible */
1382-
ret = refine_with_fees_and_limits(ctx, rq, amount, flows, probability);
1383-
if (ret)
1384-
goto fail;
1414+
delay_feefactor *= 2;
1415+
rq_log(tmpctx, rq, LOG_UNUSUAL,
1416+
"The worst flow delay is %" PRIu64
1417+
" (> %i), retrying with delay_feefactor %f...",
1418+
flows_worst_delay(*flows), maxdelay - finalcltv,
1419+
delay_feefactor);
1420+
}
1421+
1422+
/* add the new flows to the final solution */
1423+
for (size_t i = 0; i < tal_count(new_flows); i++) {
1424+
tal_arr_expand(flows, new_flows[i]);
1425+
tal_steal(flows, new_flows[i]);
1426+
create_flow_reservations(rq, &reservations,
1427+
new_flows[i]);
1428+
}
13851429

1386-
/* Again, a tiny corner case: refine step can make us exceed maxfee */
1387-
if (amount_msat_greater(flowset_fee(rq->plugin, *flows), maxfee)) {
1388-
rq_log(tmpctx, rq, LOG_UNUSUAL,
1389-
"After final refinement, fee was excessive: retrying");
1390-
goto too_expensive;
1430+
if (!amount_msat_sub(&feebudget, feebudget, all_fees) ||
1431+
!amount_msat_sub(&amount_to_deliver, amount_to_deliver,
1432+
all_deliver)) {
1433+
error_message =
1434+
rq_log(ctx, rq, LOG_BROKEN,
1435+
"%s: unexpected arithmetic operation "
1436+
"failure on amount_msat",
1437+
__func__);
1438+
goto fail;
1439+
}
13911440
}
13921441

1442+
/* transfer ownership */
1443+
*flows = tal_steal(ctx, *flows);
1444+
1445+
/* cleanup */
1446+
tal_free(working_ctx);
13931447
return NULL;
13941448
fail:
1395-
assert(ret != NULL);
1396-
return ret;
1449+
/* cleanup */
1450+
tal_free(working_ctx);
1451+
1452+
assert(error_message != NULL);
1453+
return error_message;
13971454
}
13981455

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

0 commit comments

Comments
 (0)