connectd: fix race when we supply a new address.
What changed, and why it matters
This commit fixes a timing bug in Core Lightning's connection handling. When a user manually told a node to connect to a peer, an automatic reconnection attempt could fail first and report an error back to the user, even though the manual command would have succeeded moments later. The fix makes the node only report the failure to the user if the failure came from the user's own connect command, not from a background auto-reconnect. It is a reliability fix, not a security vulnerability.
Treat as a normal bugfix. No urgent security action required. Users and operators may benefit from reduced spurious connect failures after upgrading.
Security signals we found
Race condition between autoreconnect and explicit connect RPC
Incorrect error propagation causing spurious RPC failure
No authentication bypass, memory corruption, or cryptographic weakness present
Evidence from the diff
In connect_control.c, connect_failed() previously failed all pending connect commands whenever any connection attempt failed. This caused a race where an autoreconnect attempt (with no addresses yet) failed and immediately errored pending RPC connect commands, even though a connect command had just supplied a new address to connectd. The patch restricts command failure to failures whose reason starts with “connect command”, so autoreconnect failures no longer incorrectly fail user RPCs. The test marker @pytest.mark.flaky is removed from test_route_by_old_scid because the flake is now fixed.
Changed components
lightningd/connect_control.cconnectd autoreconnect logicRPC connect command handlingInspect captured patch +15 / −5
diff --git a/lightningd/connect_control.c b/lightningd/connect_control.c
index e374ae96..f2710db5 100644
--- a/lightningd/connect_control.c
+++ b/lightningd/connect_control.c
@@ -277,10 +277,21 @@ static void connect_failed(struct lightningd *ld,
connect_nsec,
connect_attempted);
- /* We can have multiple connect commands: fail them all */
- while ((c = find_connect(ld, id)) != NULL) {
- /* They delete themselves from list */
- was_pending(command_fail(c->cmd, errcode, "%s", errmsg));
+ /* There's a race between autoreconnect and connect commands. This
+ * matters because the autoreconnect might have failed, but that was before
+ * the connect_to_peer command gave connectd a new address. This we wait for
+ * one we explicitly asked for before failing.
+ *
+ * A similar pattern could occur with multiple connect commands, however connectd
+ * does simply combine those, so we don't get a response per request, and it's a
+ * very rare corner case (which, unlike the above, doesn't happen in CI!).
+ */
+ if (strstarts(connect_reason, "connect command")) {
+ /* We can have multiple connect commands: fail them all */
+ while ((c = find_connect(ld, id)) != NULL) {
+ /* They delete themselves from list */
+ was_pending(command_fail(c->cmd, errcode, "%s", errmsg));
+ }
}
}
diff --git a/tests/test_splicing.py b/tests/test_splicing.py
index 0c5b71ac..966c75f6 100644
--- a/tests/test_splicing.py
+++ b/tests/test_splicing.py
@@ -499,7 +499,6 @@ def test_splice_stuck_htlc(node_factory, bitcoind, executor):
assert l1.db_query("SELECT count(*) as c FROM channeltxs;")[0]['c'] == 0
-@pytest.mark.flaky(reruns=5)
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_route_by_old_scid(node_factory, bitcoind):
l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True, opts={'experimental-splicing': None, 'may_reconnect': True})
Why this scored 24/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.