askrene: regression test that triggers assertion
What changed, and why it matters
This commit adds a skipped regression test that demonstrates a crash in Core Lightning's routing plugin (askrene). When a user-defined channel has an unusually high routing fee, the plugin's single-path solver hits an internal assertion and aborts. The test is currently disabled with @unittest.skip, so it documents the bug rather than fixing it. A remote user who can call the getroutes RPC with crafted layer parameters could likely trigger the plugin to crash, causing a denial of service.
Treat as a known crash bug / denial-of-service issue. The skipped test should be enabled only after fixing combine_cost_function to handle INFINITE cost without aborting. Review fee-cost arithmetic for overflow or unbounded growth, and ensure the solver returns a clean 'excessive cost' error instead of aborting the plugin. Consider restricting creation of channels with fee rates that can produce INFINITE costs, or clamping computed costs.
Security signals we found
Assertion failure leading to plugin abort (SIGABRT)
Crash reachable through JSON-RPC getroutes
Denial-of-service vector in routing plugin
Regression test supplied but skipped, indicating known unfixed bug
Evidence from the diff
The commit adds test_excessive_fee_cost in tests/test_askrene.py. It creates an askrene layer with a synthetic channel whose fee_proportional_millionths is 100000 (10%) and then calls getroutes for half a bitcoin with maxfee_msat=1000. The commit message shows this triggers an assertion failure in plugins/askrene/mcf.c:474 (combine_cost_function: Assertion `fcost != INFINITE’ failed.), which aborts the cln-askrene plugin. The test is marked @unittest.skip, so it is a reproducer, not a fix. The crash path is single_path_flow -> linear_routes -> single_path_routes -> do_getroutes -> json_getroutes, reachable via RPC.
Changed components
plugins/askrene/mcf.cplugins/askrene/askrene.ccln-askrene plugingetroutes RPC handlerInspect captured patch +36 / −0
diff --git a/tests/test_askrene.py b/tests/test_askrene.py
index 613cfa1b..f9b3e6ae 100644
--- a/tests/test_askrene.py
+++ b/tests/test_askrene.py
@@ -1959,3 +1959,39 @@ def test_splice_dying_channel(node_factory, bitcoind):
wait_for(lambda: [c['active'] for c in l3.rpc.listchannels()['channels']] == [True] * 6)
routes = l3.rpc.getroutes(l1.info['id'], l2.info['id'], '200001sat', [], 100000, 6)['routes']
assert set([only_one(r['path'])['short_channel_id_dir'] for r in routes]) == set([pre_splice_scidd, post_splice_scidd])
+
+
+@unittest.skip
+def test_excessive_fee_cost(node_factory):
+ """Produce a arc with very large fee cost that triggers an assertion in
+ askrene's single path solver."""
+ l1 = node_factory.get_node()
+ node1 = "020000000000000000000000000000000000000000000000000000000000000001"
+ one_btc = 100000000000
+ l1.rpc.askrene_create_layer("mylayer")
+ l1.rpc.askrene_create_channel(
+ layer="mylayer",
+ source=l1.info["id"],
+ destination=node1,
+ short_channel_id="0x0x0",
+ capacity_msat=one_btc,
+ )
+ l1.rpc.askrene_update_channel(
+ layer="mylayer",
+ short_channel_id_dir="0x0x0/1",
+ enabled=True,
+ htlc_minimum_msat=0,
+ htlc_maximum_msat=one_btc,
+ fee_base_msat=0,
+ fee_proportional_millionths=100000, # 10%
+ cltv_expiry_delta=18,
+ )
+ with pytest.raises(RpcError, match=r"Could not find route without excessive cost"):
+ l1.rpc.getroutes(
+ source=l1.info["id"],
+ destination=node1,
+ amount_msat=one_btc // 2,
+ layers=["mylayer", "auto.no_mpp_support"],
+ maxfee_msat=1000,
+ final_cltv=5,
+ )
Why this scored 58/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.