Skip to content

askrene: fix crash loading node bias with description - #9434

Open
vincenzopalazzo wants to merge 2 commits into
ElementsProject:masterfrom
vincenzopalazzo:askrene-fix-node-bias-load
Open

askrene: fix crash loading node bias with description#9434
vincenzopalazzo wants to merge 2 commits into
ElementsProject:masterfrom
vincenzopalazzo:askrene-fix-node-bias-load

Conversation

@vincenzopalazzo

@vincenzopalazzo vincenzopalazzo commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Fixes #9433.

Two commits, red then green: the first makes the existing
test_node_bias_persistence actually exercise layer persistence (it fails
on master with the exact crash below), the second fixes the crash.

Commit 1: tests only — the reproducer

test_node_bias_persistence restarted l2, but the layer and its node
bias records live in l1's datastore: the assert was comparing l1's
in-memory layer against itself, so load_node_bias() never ran. With
l1.restart() the layer is reloaded from the datastore at startup, which
triggers the crash on master:

cln-askrene: FATAL SIGNAL 6 (version a744936)
INFO    plugin-cln-askrene: Killing plugin: exited before replying to init
**BROKEN** plugin-cln-askrene: Plugin marked as important, shutting down lightningd!

Commit 2: the fix

load_node_bias() passed take(description) to two consecutive
set_node_bias() calls. The first call's tal_strdup() consumes
the take (tal_resize_ + tal_steal inside tal_dup_), so the
second take() was on freed memory and we aborted in
to_tal_hdr() with "Not a valid header" while loading the layer at
startup:

  call_error (tal.c: "Not a valid header")
  check_bounds / to_tal_hdr (tal.c:169/179)
  tal_resize_ (tal.c:750)
  tal_dup_ (tal.c:854)
  tal_strdup_ / tal_strdup_or_null (common/utils.c:192)
  set_node_bias (plugins/askrene/layer.c)
  load_node_bias (plugins/askrene/layer.c)
  populate_layer / load_layers
  init (plugins/askrene/askrene.c)

Since lightningd treats askrene as an important plugin, any persistent
layer containing a node bias with a description makes the node unable to
restart.

The description is already a copy off tmpctx, so the simplest thing:
don't take() it, set_node_bias() strdups it into the bias anyway
(as the issue notes, it only needs loading once).

Verified locally

  • commit 1 alone: pytest tests/test_askrene.py::test_node_bias_persistence
    fails — node crashes at restart (FATAL SIGNAL 6, exited before replying to init).
  • commit 1 + 2: same test passes, layer round-trips exactly
    (description, in_bias, out_bias, timestamp).

test_node_bias_persistence() restarted l2, but the layer and its
node bias records live in l1's datastore.  The assert compared
l1's in-memory layer against itself, so load_node_bias() was never
actually exercised and this test could not have caught the startup
crash in ElementsProject#9433.

Restarting l1 instead makes the test reload the layer from the
datastore at startup.  This is the reproducer for ElementsProject#9433; it fails
until the next commit.

Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
load_node_bias() passed take(description) to two consecutive
set_node_bias() calls.  The first call's tal_strdup() consumes the
take (tal_resize_ + tal_steal), so the second take() was on freed
memory and we aborted in to_tal_hdr() with "Not a valid header"
while loading the layer at startup.  Since askrene is an important
plugin, lightningd shuts down and the node cannot restart at all.

The description is already a copy off tmpctx, so simply don't take()
it: set_node_bias() strdups it into the bias anyway.

With this, the test from the previous commit passes.

Fixes: ElementsProject#9433
Reported-by: endothermicdev
Changelog-Fixed: askrene: node failed to start (`exited before replying to init`) when a persistent layer contains a node bias with a description
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>

@Andezion Andezion left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_node_bias() keys node_bias by node ID only (node_bias_hash_get(layer->node_biases, node)), so both calls in load_node_bias() (in-bias and out-bias) resolve to the same struct. That means the second call still does tal_free(bias->description) and re tal_strdups the identical string that the first call just set. Is that worth folding into a single "set both biases" helper at some point? What do you think?

@vincenzopalazzo

Copy link
Copy Markdown
Collaborator Author

Good point: both calls hit the same node_bias, so the second one frees and re-strdups the identical description.

But that's once per record at startup, and the one-direction API is what the RPC actually wants: askrene-bias-node sets a single side, and the both-zero-removes-the-entry rule lives inside set_node_bias(). A "set both" helper would end up duplicating that, which seems worse than one wasted strdup on load.

If we do care, the cheap fix is to pass NULL for the description on the in call and let the out call carry it: exactly one strdup, no new API. Happy to fold that in here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

askrene crash when loading node bias descriptions

2 participants