Skip to content

Commit a05278f

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 c8b5153 commit a05278f

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
@@ -1070,22 +1073,6 @@ struct flow **minflow(const tal_t *ctx,
10701073
return NULL;
10711074
}
10721075

1073-
static struct amount_msat linear_flows_cost(struct flow **flows,
1074-
struct amount_msat total_amount,
1075-
double delay_feefactor)
1076-
{
1077-
struct amount_msat total = AMOUNT_MSAT(0);
1078-
1079-
for (size_t i = 0; i < tal_count(flows); i++) {
1080-
if (!amount_msat_accumulate(&total,
1081-
linear_flow_cost(flows[i],
1082-
total_amount,
1083-
delay_feefactor)))
1084-
abort();
1085-
}
1086-
return total;
1087-
}
1088-
10891076
/* Initialize the data vectors for the single-path solver. */
10901077
static void init_linear_network_single_path(
10911078
const tal_t *ctx, const struct pay_parameters *params, struct graph **graph,
@@ -1266,6 +1253,13 @@ struct flow **single_path_flow(const tal_t *ctx, const struct route_query *rq,
12661253
return NULL;
12671254
}
12681255

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

1293-
/* Too much delay? */
1294-
while (finalcltv + flows_worst_delay(*flows) > maxdelay) {
1295-
delay_feefactor *= 2;
1296-
rq_log(tmpctx, rq, LOG_UNUSUAL,
1297-
"The worst flow delay is %" PRIu64
1298-
" (> %i), retrying with delay_feefactor %f...",
1299-
flows_worst_delay(*flows), maxdelay - finalcltv,
1300-
delay_feefactor);
1301-
tal_free(*flows);
1302-
*flows = solver(ctx, rq, srcnode, dstnode, amount, mu,
1303-
delay_feefactor);
1304-
if (!*flows || delay_feefactor > 10) {
1305-
ret = rq_log(
1306-
ctx, rq, LOG_UNUSUAL,
1307-
"Could not find route without excessive delays");
1317+
if (!new_flows) {
1318+
error_message = explain_failure(
1319+
ctx, rq, srcnode, dstnode, amount_to_deliver);
13081320
goto fail;
13091321
}
1310-
}
13111322

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

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

1381-
if (finalcltv + flows_worst_delay(*flows) > maxdelay) {
1382-
ret = rq_log(
1383-
ctx, rq, LOG_UNUSUAL,
1384-
"Could not find route without excessive cost or delays");
1385-
goto fail;
1386-
}
1413+
/* Too much delay? */
1414+
if (finalcltv + flows_worst_delay(new_flows) > maxdelay) {
1415+
if (delay_feefactor > 10) {
1416+
error_message =
1417+
rq_log(ctx, rq, LOG_UNUSUAL,
1418+
"Could not find route without "
1419+
"excessive delays");
1420+
goto fail;
1421+
}
13871422

1388-
/* The above did not take into account the extra funds to pay
1389-
* fees, so we try to adjust now. We could re-run MCF if this
1390-
* fails, but failure basically never happens where payment is
1391-
* still possible */
1392-
ret = refine_with_fees_and_limits(ctx, rq, amount, flows, probability);
1393-
if (ret)
1394-
goto fail;
1423+
delay_feefactor *= 2;
1424+
rq_log(tmpctx, rq, LOG_INFORM,
1425+
"The worst flow delay is %" PRIu64
1426+
" (> %i), retrying with delay_feefactor %f...",
1427+
flows_worst_delay(*flows), maxdelay - finalcltv,
1428+
delay_feefactor);
1429+
}
13951430

1396-
/* Again, a tiny corner case: refine step can make us exceed maxfee */
1397-
if (amount_msat_greater(flowset_fee(rq->plugin, *flows), maxfee)) {
1398-
rq_log(tmpctx, rq, LOG_UNUSUAL,
1399-
"After final refinement, fee was excessive: retrying");
1400-
goto too_expensive;
1431+
/* add the new flows to the final solution */
1432+
for (size_t i = 0; i < tal_count(new_flows); i++) {
1433+
tal_arr_expand(flows, new_flows[i]);
1434+
tal_steal(*flows, new_flows[i]);
1435+
*probability *= flow_probability(new_flows[i], rq);
1436+
create_flow_reservations(rq, &reservations,
1437+
new_flows[i]);
1438+
}
1439+
1440+
if (!amount_msat_sub(&feebudget, feebudget, all_fees) ||
1441+
!amount_msat_sub(&amount_to_deliver, amount_to_deliver,
1442+
all_deliver)) {
1443+
error_message =
1444+
rq_log(ctx, rq, LOG_BROKEN,
1445+
"%s: unexpected arithmetic operation "
1446+
"failure on amount_msat",
1447+
__func__);
1448+
goto fail;
1449+
}
14011450
}
14021451

1452+
/* transfer ownership */
1453+
*flows = tal_steal(ctx, *flows);
1454+
1455+
/* cleanup */
1456+
tal_free(working_ctx);
14031457
return NULL;
14041458
fail:
1405-
assert(ret != NULL);
1406-
return ret;
1459+
/* cleanup */
1460+
tal_free(working_ctx);
1461+
1462+
assert(error_message != NULL);
1463+
return error_message;
14071464
}
14081465

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

0 commit comments

Comments
 (0)