From 72ef9e1eeb6ab60a22e5b1c99cf5545a70f7aa25 Mon Sep 17 00:00:00 2001 From: cdecker Date: Tue, 13 Oct 2026 17:07:33 +0200 Subject: [PATCH] plugin: Add FORGET_CHANNEL to stuck HSM types Type 34 (WIRE_HSMD_FORGET_CHANNEL) is sent synchronously by lightningd's delete_channel() once onchaind completes for a closed channel. In bgsync mode no signer is attached and the StagingHsmServer only answers the canned startup messages, so the request sits in the stager forever and blocks lightningd's main loop. Because the type was not sticky the node did not report itself as stuck, so update_state called getinfo on the blocked lightningd and the state poll stalled: no BlockSync self-preempt, no 60s stuck-grace kill and no stuck webhook to summon the signer. The session then idled until the 1h session wall. On EU bgsync this accounted for ~95% of the ~2,029 cap-hit sessions/day, with 1,936 "forgetting peer" events/day repeating on every visit to the same nodes (Loki, 2026-10-12). Marking the type stuck makes update_state skip the hanging getinfo, fires the stuck webhook so the signer gets summoned, and kills the session after ~60s instead of ~3,540s. The forget then commits for good once a user session attaches the signer. --- libs/gl-plugin/src/stager.rs | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/libs/gl-plugin/src/stager.rs b/libs/gl-plugin/src/stager.rs index 05ecbc697..b4a858bf7 100644 --- a/libs/gl-plugin/src/stager.rs +++ b/libs/gl-plugin/src/stager.rs @@ -109,6 +109,7 @@ impl Stage { 20, // WIRE_HSMD_SIGN_REMOTE_HTLC_TX 21, // WIRE_HSMD_SIGN_MUTUAL_CLOSE_TX 28, // WIRE_HSMD_CHECK_PUBKEY + 34, // WIRE_HSMD_FORGET_CHANNEL (lightningd delete_channel, sync) 142, // WIRE_HSMD_SIGN_ANY_DELAYED_PAYMENT_TO_US 143, // WIRE_HSMD_SIGN_ANY_REMOTE_HTLC_TO_US 144, // WIRE_HSMD_SIGN_ANY_PENALTY_TO_US @@ -260,4 +261,40 @@ mod test { f1.await.unwrap(); f2.await.unwrap(); } + + #[tokio::test] + async fn test_stuck_request_types() { + // WIRE_HSMD_FORGET_CHANNEL (34) is queued but never answered: this is + // the bgsync deadlock, lightningd's delete_channel() blocks on it + // synchronously, so the node must report itself as stuck. + let stage = Stage::new(); + stage + .send(pb::HsmRequest { + request_id: 0, + context: None, + raw: vec![0, 34, 0, 0, 0, 0, 0, 0, 0, 1], + signer_state: vec![], + requests: vec![], + }) + .await + .unwrap(); + assert_eq!(stage.stuck_request_types().await, vec![34]); + assert!(stage.is_stuck().await); + + // WIRE_HSMD_DERIVE_SECRET (27) is not on the sticky list, a queued + // request of this type alone must not mark the node as stuck. + let stage = Stage::new(); + stage + .send(pb::HsmRequest { + request_id: 0, + context: None, + raw: vec![0, 27, 0, 0, 0, 0, 0, 0, 0, 1], + signer_state: vec![], + requests: vec![], + }) + .await + .unwrap(); + assert_eq!(stage.stuck_request_types().await, Vec::::new()); + assert!(!stage.is_stuck().await); + } }