Skip to content

fix(store): traffic_share_score inflates with uptime — late observations re-append the same tx to byPathHop #158

Description

@dborup

Summary

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.
  • The eviction leak in fix(store): remove evicted transmissions from resolved byPathHop entries #115 (resolved keys not removed on eviction; fixed on master by fix(store): remove evicted transmissions from resolved byPathHop entries #144, not yet deployed) inflated the numerator further while the denominator shrank.

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

  1. 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.
  2. Defensive: count distinct transmissions in computeRepeaterUsefulnessScoreMap() (and GetRepeaterUsefulnessScore).
  3. 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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions