Skip to content

Commit a3865c7

Browse files
committed
askrene: implement 10-second deadline.
We have another report of looping. This maxparts code is being completely rewritten, but it's good to have a catchall for any other cases which might emerge. I had to make it customizable since our tests under valgrind are SLOW! Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
1 parent 0e73118 commit a3865c7

File tree

6 files changed

+75
-8
lines changed

6 files changed

+75
-8
lines changed

doc/lightningd-config.5.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,10 @@ command, so they invoices can also be paid onchain.
553553

554554
Setting this makes `xpay` wait until all parts have failed/succeeded before returning. Usually this is unnecessary, as xpay will return on the first success (we have the preimage, if they don't take all the parts that's their problem) or failure (the destination could succeed another part, but it would mean it was only partially paid). The default is `false`.
555555

556+
* **askrene-timeout**=*SECONDS* [plugin `askrene`, *dynamic*]
557+
558+
This option makes the `getroutes` call fail if it takes more than this many seconds. Setting it to zero is a fun way to ensure your node never makes payments.
559+
556560
### Networking options
557561

558562
Note that for simple setups, the implicit *autolisten* option does the

plugins/askrene/askrene.c

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -614,13 +614,15 @@ static struct command_result *do_getroutes(struct command *cmd,
614614
/* Compute the routes. At this point we might select between multiple
615615
* algorithms. Right now there is only one algorithm available. */
616616
struct timemono time_start = time_mono();
617+
struct timemono deadline = timemono_add(time_start,
618+
time_from_sec(askrene->route_seconds));
617619
if (info->dev_algo == ALGO_SINGLE_PATH) {
618-
err = single_path_routes(rq, rq, srcnode, dstnode, info->amount,
620+
err = single_path_routes(rq, rq, deadline, srcnode, dstnode, info->amount,
619621
info->maxfee, info->finalcltv,
620622
info->maxdelay, &flows, &probability);
621623
} else {
622624
assert(info->dev_algo == ALGO_DEFAULT);
623-
err = default_routes(rq, rq, srcnode, dstnode, info->amount,
625+
err = default_routes(rq, rq, deadline, srcnode, dstnode, info->amount,
624626
info->maxfee, info->finalcltv,
625627
info->maxdelay, &flows, &probability);
626628
}
@@ -1295,7 +1297,8 @@ static const char *init(struct command *init_cmd,
12951297
const char *buf UNUSED, const jsmntok_t *config UNUSED)
12961298
{
12971299
struct plugin *plugin = init_cmd->plugin;
1298-
struct askrene *askrene = tal(plugin, struct askrene);
1300+
struct askrene *askrene = get_askrene(plugin);
1301+
12991302
askrene->plugin = plugin;
13001303
list_head_init(&askrene->layers);
13011304
askrene->reserved = new_reserve_htable(askrene);
@@ -1320,7 +1323,18 @@ static const char *init(struct command *init_cmd,
13201323

13211324
int main(int argc, char *argv[])
13221325
{
1326+
struct askrene *askrene;
13231327
setup_locale();
1324-
plugin_main(argv, init, NULL, PLUGIN_RESTARTABLE, true, NULL, commands, ARRAY_SIZE(commands),
1325-
NULL, 0, NULL, 0, NULL, 0, NULL);
1328+
1329+
askrene = tal(NULL, struct askrene);
1330+
askrene->route_seconds = 10;
1331+
plugin_main(argv, init, take(askrene), PLUGIN_RESTARTABLE, true, NULL, commands, ARRAY_SIZE(commands),
1332+
NULL, 0, NULL, 0, NULL, 0,
1333+
plugin_option_dynamic("askrene-timeout",
1334+
"int",
1335+
"How many seconds to try before giving up on calculating a route."
1336+
" Defaults to 10 seconds",
1337+
u32_option, u32_jsonfmt,
1338+
&askrene->route_seconds),
1339+
NULL);
13261340
}

plugins/askrene/askrene.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ struct askrene {
3434
struct node_id my_id;
3535
/* Aux command for layer */
3636
struct command *layer_cmd;
37+
/* How long before we abort trying to find a route? */
38+
u32 route_seconds;
3739
};
3840

3941
/* Information for a single route query. */

plugins/askrene/mcf.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,6 +1346,7 @@ static bool check_htlc_max_limits(struct route_query *rq, struct flow **flows)
13461346
*/
13471347
static const char *
13481348
linear_routes(const tal_t *ctx, struct route_query *rq,
1349+
struct timemono deadline,
13491350
const struct gossmap_node *srcnode,
13501351
const struct gossmap_node *dstnode, struct amount_msat amount,
13511352
struct amount_msat maxfee, u32 finalcltv, u32 maxdelay,
@@ -1379,6 +1380,13 @@ linear_routes(const tal_t *ctx, struct route_query *rq,
13791380
size_t num_parts, parts_slots, excess_parts;
13801381
u32 bottleneck_idx;
13811382

1383+
if (timemono_after(time_mono(), deadline)) {
1384+
error_message = rq_log(ctx, rq, LOG_BROKEN,
1385+
"%s: timed out after deadline",
1386+
__func__);
1387+
goto fail;
1388+
}
1389+
13821390
/* FIXME: This algorithm to limit the number of parts is dumb
13831391
* for two reasons:
13841392
* 1. it does not take into account that several loop
@@ -1641,25 +1649,27 @@ linear_routes(const tal_t *ctx, struct route_query *rq,
16411649
}
16421650

16431651
const char *default_routes(const tal_t *ctx, struct route_query *rq,
1652+
struct timemono deadline,
16441653
const struct gossmap_node *srcnode,
16451654
const struct gossmap_node *dstnode,
16461655
struct amount_msat amount, struct amount_msat maxfee,
16471656
u32 finalcltv, u32 maxdelay, struct flow ***flows,
16481657
double *probability)
16491658
{
1650-
return linear_routes(ctx, rq, srcnode, dstnode, amount, maxfee,
1659+
return linear_routes(ctx, rq, deadline, srcnode, dstnode, amount, maxfee,
16511660
finalcltv, maxdelay, flows, probability, minflow);
16521661
}
16531662

16541663
const char *single_path_routes(const tal_t *ctx, struct route_query *rq,
1664+
struct timemono deadline,
16551665
const struct gossmap_node *srcnode,
16561666
const struct gossmap_node *dstnode,
16571667
struct amount_msat amount,
16581668
struct amount_msat maxfee, u32 finalcltv,
16591669
u32 maxdelay, struct flow ***flows,
16601670
double *probability)
16611671
{
1662-
return linear_routes(ctx, rq, srcnode, dstnode, amount, maxfee,
1672+
return linear_routes(ctx, rq, deadline, srcnode, dstnode, amount, maxfee,
16631673
finalcltv, maxdelay, flows, probability,
16641674
single_path_flow);
16651675
}

plugins/askrene/mcf.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ struct amount_msat linear_flow_cost(const struct flow *flow,
6464
/* A wrapper to the min. cost flow solver that actually takes into consideration
6565
* the extra msats per channel needed to pay for fees. */
6666
const char *default_routes(const tal_t *ctx, struct route_query *rq,
67+
struct timemono deadline,
6768
const struct gossmap_node *srcnode,
6869
const struct gossmap_node *dstnode,
6970
struct amount_msat amount,
@@ -73,6 +74,7 @@ const char *default_routes(const tal_t *ctx, struct route_query *rq,
7374

7475
/* A wrapper to the single-path constrained solver. */
7576
const char *single_path_routes(const tal_t *ctx, struct route_query *rq,
77+
struct timemono deadline,
7678
const struct gossmap_node *srcnode,
7779
const struct gossmap_node *dstnode,
7880
struct amount_msat amount,

tests/test_askrene.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1185,7 +1185,9 @@ def test_real_data(node_factory, bitcoind):
11851185
l1, l2 = node_factory.line_graph(2, fundamount=AMOUNT,
11861186
opts=[{'gossip_store_file': outfile.name,
11871187
'allow_warning': True,
1188-
'dev-throttle-gossip': None},
1188+
'dev-throttle-gossip': None,
1189+
# This can be slow!
1190+
'askrene-timeout': TIMEOUT},
11891191
{'allow_warning': True}])
11901192

11911193
# These were obviously having a bad day at the time of the snapshot:
@@ -1573,3 +1575,36 @@ def test_maxparts_infloop(node_factory, bitcoind):
15731575
maxfee_msat=amount,
15741576
final_cltv=5,
15751577
maxparts=2)
1578+
1579+
1580+
def test_askrene_timeout(node_factory, bitcoind):
1581+
"""Test askrene's route timeout"""
1582+
l1, l2 = node_factory.line_graph(2, opts=[{'broken_log': 'linear_routes: timed out after deadline'}, {}])
1583+
1584+
assert l1.rpc.listconfigs('askrene-timeout')['configs']['askrene-timeout']['value_int'] == 10
1585+
l1.rpc.getroutes(source=l1.info['id'],
1586+
destination=l2.info['id'],
1587+
amount_msat=1,
1588+
layers=['auto.localchans'],
1589+
maxfee_msat=1,
1590+
final_cltv=5)
1591+
1592+
# It will exit instantly.
1593+
l1.rpc.setconfig('askrene-timeout', 0)
1594+
1595+
with pytest.raises(RpcError, match='linear_routes: timed out after deadline'):
1596+
l1.rpc.getroutes(source=l1.info['id'],
1597+
destination=l2.info['id'],
1598+
amount_msat=1,
1599+
layers=['auto.localchans'],
1600+
maxfee_msat=1,
1601+
final_cltv=5)
1602+
1603+
# We can put it back though.
1604+
l1.rpc.setconfig('askrene-timeout', 10)
1605+
l1.rpc.getroutes(source=l1.info['id'],
1606+
destination=l2.info['id'],
1607+
amount_msat=1,
1608+
layers=['auto.localchans'],
1609+
maxfee_msat=1,
1610+
final_cltv=5)

0 commit comments

Comments
 (0)