Skip to content

discovery: account for future gossip memory - #11158

Closed
moscowchill wants to merge 2 commits into
lightningnetwork:masterfrom
moscowchill:fix/account-future-gossip-memory
Closed

moscowchill wants to merge 2 commits into
lightningnetwork:masterfrom
moscowchill:fix/account-future-gossip-memory

Conversation

@moscowchill

@moscowchill moscowchill commented Sep 1, 2026 •

Copy link
Copy Markdown

Change Description

The future gossip cache currently charges each decoded message as one unit. A ChannelAnnouncement1 feature vector can decode into 65,536 map entries, so the existing 1,000-message limit does not bound the memory retained by the cache.

This PR converts the cache to a retained-memory budget while preserving the existing upper bound of 1,000 ordinary messages. Every future message receives a conservative maximum-wire-body charge, and decoded channel-announcement feature entries receive an additional per-entry charge. A maximally dense announcement therefore consumes 1,114,109 cache bytes and the production cache retains at most 58 such objects.

Validation order, replay behavior, and monotonic cache keys remain unchanged. The change is scoped to bounding retained cache state; transient decoding work remains subject to the existing gossip processing limits.

Steps to Test

go test ./discovery -count=1
go test ./lnwire -count=1
go test -race ./discovery -run '^(TestFutureChanAnnCacheBounds|TestFutureMsgCacheEviction|TestPrematureAnnouncementProcessing)$' -count=10

Pull Request Checklist

Testing

  • Your PR passes all CI checks.
  • Tests covering the positive and negative error paths are included.
  • The bug fix contains a test exercising weighted admission and eviction.

Code Style and Documentation

  • The change is substantial and focused.
  • The change follows the code documentation and 80-column guidelines.
  • The commit follows the ideal Git commit structure.
  • New logging uses an appropriate subsystem and level.
  • No lncli command is added.
  • A release-note entry is included.

Future gossip entries currently have a constant cache cost even when a decoded channel announcement retains a dense feature map.

Charge a maximum wire body per message plus each decoded feature-map entry, preserving the ordinary message count while bounding dense retained state.

Signed-off-by: moscowchill <gasgeverij@proton.me>
Signed-off-by: moscowchill <gasgeverij@proton.me>
@moscowchill
moscowchill marked this pull request as ready for review September 1, 2026 18:22
@github-actions github-actions Bot added the severity-high Requires knowledgeable engineer review label Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

🟠 PR Severity: HIGH

gh pr diff | 3 files | 155 lines changed

🟠 High (1 file)
  • discovery/gossiper.go - modifies the future gossip message cache admission/eviction logic in the gossip protocol subsystem
🟢 Low (2 files)
  • discovery/gossiper_test.go - test-only changes
  • docs/release-notes/release-notes-0.22.0.md - release notes

Analysis

The core change is in discovery/gossiper.go, which falls under discovery/* (gossip protocol) — classified HIGH. It changes how the future-message cache accounts for memory (switching from a per-message count limit to a retained-memory budget) to bound worst-case memory usage from densely-packed ChannelAnnouncement1 feature vectors. This affects cache admission/eviction behavior in a core gossip component, warranting review by an engineer familiar with the gossip subsystem. No file-count or line-count bump thresholds were crossed (only 1 non-test/non-doc file, ~48 non-test lines changed), and no other critical packages are touched.


To override, add a severity-override-{critical,high,medium,low} label.

@Lrifton92 Lrifton92 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed the retained-memory accounting and reproduced the bounds independently — the approach looks correct and the constants are safe. A few notes:

Bounds check out. With lnwire.MaxMsgBody = 65533 and futureFeatureEntrySize = 16, a maximally dense ChannelAnnouncement1 (65,536 feature bits) is charged 65533 + 65536*16 = 1,114,109 bytes, and the cache budget 1000 * 65533 = 65,533,000 retains floor(65,533,000 / 1,114,109) = 58 such objects. Both figures in the description are exact.

futureFeatureEntrySize = 16 is a genuine upper bound. RawFeatureVector.features is a map[FeatureBit]struct{} with a uint16 key and zero-size value, so a full bucket retains 8 tophash + 8*2 key + 0 value + 8 overflow = 32 bytes for 8 entries, i.e. ~4 bytes/entry amortized (a few more with Go's load-factor slack and overflow buckets). Charging 16 per entry comfortably over-approximates the real retained cost, which is the safe direction for a memory budget. The comment on the constant matches the runtime layout.

Coverage. Of the three isPremature call sites (handleChanAnnouncement, handleChanUpdate, handleAnnSig), only handleChanAnnouncement carries a decoded feature map — ChannelUpdate1 and AnnounceSignatures1 have no RawFeatureVector — so restricting the per-entry charge to *lnwire.ChannelAnnouncement1 covers every dense object that can currently enter the cache.

One forward-looking note (not blocking): ChannelAnnouncement2 also carries a RawFeatureVector (tlv.TlvType2). It doesn't reach this cache today since v2 gossip isn't wired into isPremature, but if/when it is, futureMsgSize's type switch would charge it only futureMsgMinSize and reopen the same under-accounting. A short comment next to the ChannelAnnouncement1 assertion flagging that assumption would make the extension point explicit for whoever adds v2 gossip.

@saubyk

saubyk commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR, @moscowchill.

Our contribution guidelines note that PRs from new contributors aren't prioritized for review at the moment. With the backlog we have, that means closing this instead of leaving it in limbo.

If there is a genuine bug behind this, please open an issue instead with:

  • reproduction steps, or the logs / stack trace you saw
  • the lnd version and chain backend
  • an explanation of why the existing behavior is incorrect

That gives us something concrete to evaluate, and if it checks out we may fix it ourselves or invite you to reopen.

If you'd like to contribute going forward, reviewing open PRs and helping triage issues is the path we recommend. It's a stronger signal of understanding than a first patch, and it makes it a lot easier for us to prioritize your PRs later.

@saubyk saubyk closed this Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

severity-high Requires knowledgeable engineer review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants