Skip to content

htlcswitch: decouple on-chain intercept cleanup from blocking interceptor sends #10932

Description

@ziggie1984

Context

PR #10895 adds cleanup for on-chain held HTLC entries via
preimageBeacon.CancelSubscription -> RemoveOnChainIntercept.

That cleanup path is currently synchronous:

  1. CancelSubscription calls p.cancelInterceptor(inKey).
  2. In production, p.cancelInterceptor is
    InterceptableSwitch.RemoveOnChainIntercept.
  3. RemoveOnChainIntercept sends the circuit key over the unbuffered
    onchainInterceptDone channel.
  4. The send completes only when the InterceptableSwitch event loop receives
    it.

Liveness concern

The same switch event loop also performs synchronous interceptor delivery:

  1. An interceptor client connects or reconnects.
  2. setInterceptor replays held HTLCs with
    s.heldHtlcSet.forEach(s.sendForward).
  3. sendForward calls the routerrpc interceptor callback.
  4. The routerrpc callback calls stream.Send.
  5. If the external interceptor client stalls or stops reading, stream.Send
    can block.

While blocked in stream.Send, the switch loop cannot receive
onchainInterceptDone. If a contractcourt resolver then cancels a witness
subscription, resolver cleanup can block behind progress of the external
interceptor stream.

This is not a remote peer exploit and not a new funds-loss path by itself; it
is a robustness/liveness issue with an operator-controlled interceptor service.
The switch loop is already blocked by a stalled interceptor, but synchronous
resolver cleanup increases the blast radius by pinning resolver goroutines too.

Possible follow-ups

Short-term options:

  • Make on-chain intercept cleanup asynchronous from the witness subscription
    cancellation path, so resolver cleanup does not wait on the switch loop.
  • Add a dedicated cleanup queue with explicit shutdown semantics.

Larger design option:

  • Decouple interceptor stream.Send from the InterceptableSwitch event loop
    so stalled external clients cannot block unrelated switch-loop work.

References

Metadata

Metadata

Assignees

No one assigned

    Type

    Fields

    No fields configured for Bug.

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions