rpcserver: filter backup subscription events
What changed, and why it matters
This change fixes a bug where a backup notification stream would fire far too often. Previously, routine channel state updates were treated as backup-relevant, causing the stream to emit on every update. The patch switches to an explicit allow-list of true lifecycle events (open, close, pending, resolved, funding timeout) and documents that the underlying notifier also carries high-frequency state updates. There is no direct security exploit here, but the excess noise could hide real backup-relevant events or waste resources.
Treat as a reliability/DoS-noise hardening fix rather than a vulnerability patch. Operators relying on SubscribeChannelBackups should upgrade to avoid excessive backup traffic. No immediate exploit mitigation is required, but the change should be included in the next maintenance release.
Security signals we found
Behavioral change in event filtering that reduces unintended information leakage/noise
Deny-list replaced with allow-list, a defensive coding pattern
Documentation added warning consumers about high-frequency events in shared notification channel
No cryptographic, authentication, or memory-safety changes observed
Evidence from the diff
The commit modifies rpcserver.go’s SubscribeChannelBackups handler and a comment in channelnotifier/channelnotifier.go. The handler previously used a deny-list to skip Active/Inactive channel/link events, but because ChannelNotifier now also emits ChannelUpdateEvent (commitment updates), those updates fell through the deny-list and triggered backup notifications. The patch replaces the deny-list with an explicit allow-list of lifecycle event types (PendingOpenChannelEvent, OpenChannelEvent, ClosedChannelEvent, FullyResolvedChannelEvent, FundingTimeoutEvent) and treats all other events with a default continue. It also adds graceful handling when the updates channel is closed. The ChannelNotifier doc comment is updated to warn consumers that the subscription includes high-frequency state updates and that lifecycle-only consumers must filter explicitly.
Changed components
rpcserver.go: SubscribeChannelBackups RPC streamchannelnotifier/channelnotifier.go: SubscribeChannelEvents subscription semanticsInspect captured patch +28 / −13
diff --git a/channelnotifier/channelnotifier.go b/channelnotifier/channelnotifier.go
index 06f3e67..3f44203 100644
--- a/channelnotifier/channelnotifier.go
+++ b/channelnotifier/channelnotifier.go
@@ -131,6 +131,11 @@ func (c *ChannelNotifier) Stop() error {
// any time the Server is made aware of a new event. The subscription provides
// channel events from the point of subscription onwards.
//
+// NOTE: This subscription includes both channel lifecycle events and higher
+// frequency channel state updates, such as ChannelUpdateEvent. Callers that
+// only need lifecycle updates should explicitly filter for the event types they
+// consume.
+//
// TODO(carlaKC): update to allow subscriptions to specify a block height from
// which we would like to subscribe to events.
func (c *ChannelNotifier) SubscribeChannelEvents() (*subscribe.Client, error) {
diff --git a/rpcserver.go b/rpcserver.go
index 88f676e..6319655 100644
--- a/rpcserver.go
+++ b/rpcserver.go
@@ -8048,23 +8048,33 @@ func (r *rpcServer) SubscribeChannelBackups(req *lnrpc.ChannelBackupSubscription
select {
// A new event has been sent by the channel notifier, we'll
// assemble, then sling out a new event to the client.
- case e := <-chanSubscription.Updates():
+ case e, ok := <-chanSubscription.Updates():
+ if !ok {
+ // The subscription server closes the updates
+ // channel during shutdown or cancellation, so
+ // end the stream gracefully.
+ return nil
+ }
+
// TODO(roasbeef): batch dispatch ntnfs
switch e.(type) {
- // We only care about new/closed channels, so we'll
- // skip any events for active/inactive channels.
- // To make the subscription behave the same way as the
- // synchronous call and the file based backup, we also
- // include pending channels in the update.
- case channelnotifier.ActiveChannelEvent:
- continue
- case channelnotifier.InactiveChannelEvent:
- continue
- case channelnotifier.ActiveLinkEvent:
- continue
- case channelnotifier.InactiveLinkEvent:
+ // Only channel lifecycle events should trigger this
+ // subscription. Commitment updates can affect
+ // close-tx inputs embedded in an exported SCB, but
+ // emitting on that frequency would make this stream
+ // too noisy. To make the subscription behave the same
+ // way as the synchronous call and the file based
+ // backup, we also include pending channels in the
+ // update.
+ case channelnotifier.PendingOpenChannelEvent,
+ channelnotifier.OpenChannelEvent,
+ channelnotifier.ClosedChannelEvent,
+ channelnotifier.FullyResolvedChannelEvent,
+ channelnotifier.FundingTimeoutEvent:
+
+ default:
continue
}
Why this scored 25/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.