discovery: avoid boxing channel update freshness - #11056
Conversation
🟠 PR Severity: HIGH
🟠 High (4 files)
🟢 Low (3 files)
AnalysisThe highest-severity files touched are in To override, add a |
Lrifton92
left a comment
There was a problem hiding this comment.
Trading a self-describing interface for a value plus a discriminator is the right call here, and the release-note arithmetic checks out: an interface value is two words, so dropping two of them to uint64 saves exactly the 16 bytes claimed, and the boxing allocations go with it. Two things before this is ready, one of which will fail CI as it stands.
Six added lines exceed the 80-column limit
.golangci.yml enables a custom ll linter with line-length: 80 and tab-width: 8 (lines 209-219). Measured with tabs expanded to 8, the new assignments come out over:
88 chanInfo.Node1Freshness = uint64(edge.LastUpdate.Unix()) graph/db/kv_store.go
88 chanInfo.Node2Freshness = uint64(edge.LastUpdate.Unix()) graph/db/kv_store.go
82 chanInfo.Node1Freshness = uint64(n1Update) graph/db/sql_store.go
82 chanInfo.Node1Freshness = uint64(n1Height) graph/db/sql_store.go
82 chanInfo.Node2Freshness = uint64(n2Update) graph/db/sql_store.go
82 chanInfo.Node2Freshness = uint64(n2Height) graph/db/sql_store.go
The two kv_store.go ones are the telling case: the code you replaced was already split across three lines for exactly this reason, and collapsing it to one call put it back over. All six are at deep nesting, so they need wrapping rather than shortening.
freshnessTimestamp has an unchecked default
func (c ChannelUpdateInfo) freshnessTimestamp(value uint64) lnwire.Timestamp {
if c.Version == lnwire.GossipVersion1 {
return lnwire.UnixTimestamp(value)
}
return lnwire.BlockHeightTimestamp(value)
}GossipVersion1 is 1 and GossipVersion2 is 2 (lnwire/interfaces.go:17-22), so zero is not a valid version — but it is the zero value of the field. Anything that is not exactly V1 is reported as a block height: a zero-valued ChannelUpdateInfo, and any GossipVersion3 added later.
This is not reachable today — both constructors set Version, and they are the only two places that build the struct literal — so I am raising it as a property of the refactor rather than a live bug. But it is the specific thing the interface used to make impossible: a lnwire.Timestamp either held a UnixTimestamp, held a BlockHeightTimestamp, or was nil and detectably absent. It could not quietly claim to be the wrong one. An explicit switch with a GossipVersion2 case makes the discriminator carry that same guarantee, and gives a future version a compile-time or panic-time signal instead of silently wrong data.
Worth noting the sibling method already treats unknown versions conservatively:
func (c ChannelUpdateInfo) Node1FreshnessTime() time.Time {
if c.Version == lnwire.GossipVersion1 {
return time.Unix(int64(c.Node1Freshness), 0)
}
return time.Time{}
}Same condition, opposite default — one falls back to "unknown", the other to "block height". Whichever is intended, they should agree.
The default branch is untested
TestChannelUpdateInfoFreshness covers V1 and V2, which is the useful half. Nothing pins the behaviour for a version that is neither, so whatever you decide above would not be protected by a test. Given the point of the change is that Version now carries the type information, that branch seems worth an assertion.
Address review feedback on the freshness refactor: wrap the six assignments that exceeded the 80-column limit, convert freshnessTimestamp to an explicit switch over the gossip version so an unknown version reads as a nil (detectably absent) timestamp instead of a block height, and pin that default branch with a test. The nil default agrees with Node1FreshnessTime's zero-time fallback, and isTimestampStale already treats a nil freshness as stale, so unknown versions stay conservatively prunable.
|
Thanks for the careful read — all three points addressed in 692e35f. Line lengths: all six assignments wrapped. Re-measured every added line in the diff with tabs expanded to 8; nothing exceeds 80 columns now. The
Default-branch test: Verified locally: |
Lrifton92
left a comment
There was a problem hiding this comment.
All three points addressed in 692e35f — verified each one:
- Line lengths: the six assignments are wrapped; the
kv_store.gopair went back to the multi-line call shape the replaced code used, and thesql_store.gocases break after=. Nothing in the new diff exceeds 80 columns with tabs at 8. freshnessTimestampdefault: the explicitswitchwith aGossipVersion2case andnildefault restores the property the interface used to carry — an unknown version is now detectably absent instead of silently claiming to be a block height. I checked the consumer side too:isTimestampStaleguards withts, ok := freshness.(...)on both arms, so a nil freshness reads as stale rather than dereferencing, and the nil default now agrees withNode1FreshnessTime's zero-time fallback instead of contradicting it.- Default branch test: the new
unknowncase inTestChannelUpdateInfoFreshnesspins both accessors (nil timestamp, zero time) for a version that is neither v1 nor v2, including the zero value.
LGTM.
Change Description
Fixes #11006.
Store
ChannelUpdateInfofreshness values as concreteuint64fields, using the existing gossip version as the discriminator. Version-specificlnwire.Timestampvalues are reconstructed only at the API boundary. This removes the two interface-boxing allocations from each timestamped channel-range entry without changing wire or database formats.A release-note entry and focused v1/v2 representation tests are included.
Steps to Test
go test ./graph/db -run '^TestChannelUpdateInfoFreshness$' -count=1.go test ./graph/db ./discovery -run '^$' -count=1to compile both affected packages.Pull Request Checklist
Testing
Code Style and Documentation