Skip to content

Commit 24d203c

Browse files
committed
askrene: fix infinite loop if refine_flows() cuts down our last flow with 1 remaining before maxparts.
1. We would find a flow 2 refine_flow would reduce it 3. we would need to find another, but we are at the limit 4. We remove the flow we found. 5. Goto 1. This can be fixed by disabling a channel which we caused us to reduce the flow, so we should always make forward progress. Signed-off-by: Rusty Russell <[email protected]>
1 parent e6104bc commit 24d203c

File tree

4 files changed

+29
-12
lines changed

4 files changed

+29
-12
lines changed

plugins/askrene/mcf.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1377,6 +1377,7 @@ linear_routes(const tal_t *ctx, struct route_query *rq,
13771377

13781378
while (!amount_msat_is_zero(amount_to_deliver)) {
13791379
size_t num_parts, parts_slots, excess_parts;
1380+
u32 bottleneck_idx;
13801381

13811382
/* FIXME: This algorithm to limit the number of parts is dumb
13821383
* for two reasons:
@@ -1424,7 +1425,7 @@ linear_routes(const tal_t *ctx, struct route_query *rq,
14241425
}
14251426

14261427
error_message =
1427-
refine_flows(ctx, rq, amount_to_deliver, &new_flows);
1428+
refine_flows(ctx, rq, amount_to_deliver, &new_flows, &bottleneck_idx);
14281429
if (error_message)
14291430
goto fail;
14301431

@@ -1457,6 +1458,10 @@ linear_routes(const tal_t *ctx, struct route_query *rq,
14571458
/* Leave exactly 1 slot for the next round of
14581459
* computations. */
14591460
excess_parts = 1;
1461+
/* If we only *had* one slot, remove the channel which refinement said limited
1462+
* the flow, so we don't use it again and infinite loop. */
1463+
if (parts_slots == 1)
1464+
bitmap_set_bit(rq->disabled_chans, bottleneck_idx);
14601465
} else
14611466
excess_parts = 0;
14621467
if (excess_parts > 0 &&

plugins/askrene/refine.c

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -235,16 +235,25 @@ static int revcmp_flows(const size_t *a, const size_t *b, struct flow **flows)
235235
// -> check that htlc_max are all satisfied
236236
// -> check that (x+1) at least one htlc_max is violated
237237
/* Given the channel constraints, return the maximum amount that can be
238-
* delivered. */
239-
static struct amount_msat path_max_deliverable(struct channel_data *path)
238+
* delivered. Sets *bottleneck_idx to one of the contraining channels' idx, if non-NULL */
239+
static struct amount_msat path_max_deliverable(struct channel_data *path,
240+
u32 *bottleneck_idx)
240241
{
241242
struct amount_msat deliver = AMOUNT_MSAT(-1);
242243
for (size_t i = 0; i < tal_count(path); i++) {
243244
deliver =
244245
amount_msat_sub_fee(deliver, path[i].fee_base_msat,
245246
path[i].fee_proportional_millionths);
246-
deliver = amount_msat_min(deliver, path[i].htlc_max);
247-
deliver = amount_msat_min(deliver, path[i].liquidity_max);
247+
if (amount_msat_greater(deliver, path[i].htlc_max)) {
248+
if (bottleneck_idx)
249+
*bottleneck_idx = path[i].idx;
250+
deliver = path[i].htlc_max;
251+
}
252+
if (amount_msat_greater(deliver, path[i].liquidity_max)) {
253+
if (bottleneck_idx)
254+
*bottleneck_idx = path[i].idx;
255+
deliver = path[i].liquidity_max;
256+
}
248257
}
249258
return deliver;
250259
}
@@ -477,9 +486,9 @@ static void write_selected_flows(const tal_t *ctx, size_t *flows_index,
477486
tal_free(tmp_flows);
478487
}
479488

480-
/* FIXME: on failure return error message */
481489
const char *refine_flows(const tal_t *ctx, struct route_query *rq,
482-
struct amount_msat deliver, struct flow ***flows)
490+
struct amount_msat deliver, struct flow ***flows,
491+
u32 *bottleneck_idx)
483492
{
484493
const tal_t *working_ctx = tal(ctx, tal_t);
485494
const char *error_message = NULL;
@@ -499,7 +508,7 @@ const char *refine_flows(const tal_t *ctx, struct route_query *rq,
499508
for (size_t i = 0; i < tal_count(channel_mpp_cache); i++) {
500509
// FIXME: does path_max_deliverable work for a single
501510
// channel with 0 fees?
502-
max_deliverable[i] = path_max_deliverable(channel_mpp_cache[i]);
511+
max_deliverable[i] = path_max_deliverable(channel_mpp_cache[i], bottleneck_idx);
503512
min_deliverable[i] = path_min_deliverable(channel_mpp_cache[i]);
504513
/* We use an array of indexes to keep track of the order
505514
* of the flows. Likewise flows can be removed by simply
@@ -578,7 +587,7 @@ void squash_flows(const tal_t *ctx, struct route_query *rq,
578587
struct short_channel_id_dir scidd;
579588
flows_index[i] = i;
580589
paths_str[i] = tal_strdup(working_ctx, "");
581-
max_deliverable[i] = path_max_deliverable(channel_mpp_cache[i]);
590+
max_deliverable[i] = path_max_deliverable(channel_mpp_cache[i], NULL);
582591

583592
for (size_t j = 0; j < tal_count(flow->path); j++) {
584593
scidd.scid =

plugins/askrene/refine.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ bool create_flow_reservations_verify(const struct route_query *rq,
2222
const struct flow *flow);
2323

2424
/* Modify flows to meet HTLC min/max requirements.
25-
* It takes into account the exact value of the fees expected at each hop. */
25+
* It takes into account the exact value of the fees expected at each hop.
26+
* If we reduce flows because it's too large for one channel, *bottleneck_idx
27+
* is set to the idx of a channel which caused a reduction (if non-NULL).
28+
*/
2629
const char *refine_flows(const tal_t *ctx, struct route_query *rq,
27-
struct amount_msat deliver, struct flow ***flows);
30+
struct amount_msat deliver, struct flow ***flows,
31+
u32 *bottleneck_idx);
2832

2933
/* Duplicated flows are merged into one. This saves in base fee and HTLC fees.
3034
*/

tests/test_askrene.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1538,7 +1538,6 @@ def test_simple_dummy_channel(node_factory):
15381538
)
15391539

15401540

1541-
@pytest.mark.xfail(strict=True)
15421541
def test_maxparts_infloop(node_factory, bitcoind):
15431542
# Three paths from l1 -> l5.
15441543
# FIXME: enhance explain_failure!

0 commit comments

Comments
 (0)