You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
traffic_share_score (the "Traffic share" axis, and through it 20 % of usefulness_score) grows steadily with server uptime and ends up far above reality. A repeater went from ~10 % to 50 %+ overnight with no change on the air. Cause: the same transmission is appended to a repeater's byPathHop bucket once per late-arriving observation, while the denominator counts it once.
Evidence (public /api/nodes, 2026-10-01)
Instance
Version
Uptime
Sum of all repeaters' traffic_share_score
Repeaters clamped at 1.0
prod (meshview.dk)
1d19cd5
98 h
235.8
many (e.g. DK_0999_DRBYEN, DK_5700_Svendborg_N)
staging
d264716c
47 h
147.8
many
The sum should be roughly the average number of relay hops per non-advert packet (a small number). Right after a restart the index is rebuilt cleanly and values look plausible; they then drift upwards as live observations arrive.
Cause (master be35eefb)
computeRepeaterUsefulnessScoreMap() (cmd/server/repeater_enrich_bulk.go, around line 222): share = |non-advert txs in byPathHop[pubkey]| / |non-advert txs in byPayloadType|, clamped to 1. It counts list entries, not distinct transmissions.
IngestNewObservations() (cmd/server/store.go, around line 3393) calls indexResolvedPathHops(tx, pks, …) for each new observation of an existing transmission.
addResolvedPubkeysToPathHopIndex() (around line 4570) only de-duplicates within one call (hopsSeen is cleared on entry); it never checks whether tx is already in byPathHop[pk]. Every further observation through the same relays appends tx again.
Load and background chunk load index the union of resolved pubkeys once per transmission, which is why a restart temporarily "fixes" the numbers.
addToByNode() does not have this problem (it de-duplicates via nodeHashes[pubkey][tx.Hash]), and retainResolvedPathHops() already de-duplicates by *StoreTx when it walks the index.
Proposed fix
Make the byPathHop insert idempotent per (key, transmission): e.g. keep a small per-transmission set of resolved keys already indexed (or reuse an existing per-tx structure), and skip the append when present. Bounded: at most one entry per relay key per transmission. Keep eviction (fix(store): remove evicted transmissions from resolved byPathHop entries #144) consistent with it.
Defensive: count distinct transmissions in computeRepeaterUsefulnessScoreMap() (and GetRepeaterUsefulnessScore).
Audit the other byPathHop consumers for the same inflation: GetRepeaterNodeStatsBatch (repeater_usefulness.go), GetNodeHopAnalytics, computeMultiByteCapability (store.go), and fix any that count entries.
Acceptance criteria
A transmission ingested once and then observed by N more observers through the same relays appears once in each relay's byPathHop bucket (test with N = 10), via both the live ingest and the late-observation paths.
traffic_share_score for a fixed set of transmissions is identical right after load and after any number of extra observations (test).
Property test: the sum of all repeaters' traffic_share_score is bounded by the maximum path length; no value is clamped at 1.0 in a realistic fixture.
Audited consumers either count distinct transmissions or are shown not to be affected (tests).
Memory/perf: no unbounded growth; benchmark the observation-ingest path before/after (AGENTS.md rule 0). Full cmd/server suite under -race.
Not in scope
No change to the score definition or weights; no deploy in this issue (a staging round should confirm the sum stays bounded over a day of uptime).
Summary
traffic_share_score(the "Traffic share" axis, and through it 20 % ofusefulness_score) grows steadily with server uptime and ends up far above reality. A repeater went from ~10 % to 50 %+ overnight with no change on the air. Cause: the same transmission is appended to a repeater'sbyPathHopbucket once per late-arriving observation, while the denominator counts it once.Evidence (public
/api/nodes, 2026-10-01)traffic_share_score1d19cd5DK_0999_DRBYEN,DK_5700_Svendborg_N)d264716cThe sum should be roughly the average number of relay hops per non-advert packet (a small number). Right after a restart the index is rebuilt cleanly and values look plausible; they then drift upwards as live observations arrive.
Cause (master
be35eefb)computeRepeaterUsefulnessScoreMap()(cmd/server/repeater_enrich_bulk.go, around line 222):share = |non-advert txs in byPathHop[pubkey]| / |non-advert txs in byPayloadType|, clamped to 1. It counts list entries, not distinct transmissions.IngestNewObservations()(cmd/server/store.go, around line 3393) callsindexResolvedPathHops(tx, pks, …)for each new observation of an existing transmission.addResolvedPubkeysToPathHopIndex()(around line 4570) only de-duplicates within one call (hopsSeenis cleared on entry); it never checks whethertxis already inbyPathHop[pk]. Every further observation through the same relays appendstxagain.addToByNode()does not have this problem (it de-duplicates vianodeHashes[pubkey][tx.Hash]), andretainResolvedPathHops()already de-duplicates by*StoreTxwhen it walks the index.Proposed fix
byPathHopinsert idempotent per (key, transmission): e.g. keep a small per-transmission set of resolved keys already indexed (or reuse an existing per-tx structure), and skip the append when present. Bounded: at most one entry per relay key per transmission. Keep eviction (fix(store): remove evicted transmissions from resolved byPathHop entries #144) consistent with it.computeRepeaterUsefulnessScoreMap()(andGetRepeaterUsefulnessScore).byPathHopconsumers for the same inflation:GetRepeaterNodeStatsBatch(repeater_usefulness.go),GetNodeHopAnalytics,computeMultiByteCapability(store.go), and fix any that count entries.Acceptance criteria
byPathHopbucket (test with N = 10), via both the live ingest and the late-observation paths.traffic_share_scorefor a fixed set of transmissions is identical right after load and after any number of extra observations (test).traffic_share_scoreis bounded by the maximum path length; no value is clamped at 1.0 in a realistic fixture.cmd/serversuite under-race.Not in scope
No change to the score definition or weights; no deploy in this issue (a staging round should confirm the sum stays bounded over a day of uptime).