Skip to content

feat: store repeater-reported node names (/names) and resolve by them - #17

Merged
efiten merged 4 commits into
masterfrom
feat/client-node-names
Oct 16, 2026
Merged

efiten merged 4 commits into
masterfrom
feat/client-node-names

Conversation

@efiten

@efiten efiten commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Summary

CoreDrive RX v1.20.0 asks a repeater for its own name (ANON_REQ_TYPE_OWNER) when this instance has none for it, and publishes the answer as NODE_NAME on meshcore/client/{pk}/names. This stores those answers and uses them where a name is resolved.

  • 494583b9 Ingestor: new names subtopic, gated on clientNodeNames.enabled (default off). It stores into a new table client_node_names (observations, UNIQUE(target, rx_pubkey, observed_at)).
    • target must be 64 hex.
    • The name is trimmed and rejected when empty, over 31 runes (firmware node_name[32], src/helpers/CommonCLI.h:27), invalid UTF-8, or holding a control character.
    • Retention retention.clientNodeNamesDays (0 = keep).
  • ef0a77b7 Server: lookupPrefixes counts candidates in nodes and client_node_names. /api/nodes/resolve and batchResolveHeardKeys (coverage tooltips) both use it.
    • The advert name wins; otherwise the newest client-reported name by observed_at.
    • A prefix matching different pubkeys across the two tables stays ambiguous.
    • Blacklist and hidden name prefixes apply to both sources.
    • A database without the table resolves from nodes alone, and the table is re-probed until the ingestor creates it.
  • fab6dc78 Docs: the /regions section named clientRxCoverage.enabled as its switch. The code uses clientRegions.enabled.

Compatibility

  • An older CoreDrive never publishes /names: nothing changes for it.
  • An older ingestor drops /names silently (no case), so CoreDrive v1.20.0 works against either.
  • /api/nodes/resolve keeps its response shape {prefix, pubkey?, name?, ambiguous}.

Performance

lookupPrefixes adds one LIKE prefix% term per key on client_node_names, served by idx_cnn_target(target, observed_at). It is still one query per chunk of 200 keys, which is 400 compound terms, under SQLite's default 500. Covered by TestLookupPrefixesManyKeys (250 keys over two chunks). Not benchmarked: the table holds one row per answer, and the expected size is in the hundreds.

Trust

Any companion the broker lets publish can report a name for any repeater, and the newest report wins. That is the same model as /regions. clientRxCoverage.requireLinkedCompanion limits it to linked companions.

Test plan

  • go test ./... in cmd/ingestor and cmd/server: pass
  • New tests: handler, validation cases, idempotent redelivery, newest observed_at wins, prune, gate on/off without observer-path fall-through, client-only node, nameless nodes row, advert name wins, cross-source ambiguity, hidden, blacklisted, absent-then-created table
  • Staging: clientNodeNames.enabled, a real /names message, /api/nodes/resolve and a coverage tooltip in the browser
  • Not run: frontend unit tests and E2E (no frontend change)

Upstream: fork-only for now. A clean PR against upstream/master comes later.

efiten and others added 4 commits October 16, 2026 11:19
…/names

CoreDrive RX v1.20.0 asks a repeater for its own name (ANON_REQ_TYPE_OWNER)
when this instance has none for it, and publishes the answer as NODE_NAME on
/names. Until now the ingestor dropped that subtopic.

- New case "names" in the client topic switch (cmd/ingestor/main.go), gated
  on clientNodeNames.enabled, default off.
- handleClientNodeNames (cmd/ingestor/client_names.go): target must be 64 hex
  (targetPubkeyRe, as on /regions). The name is trimmed of spaces and NUL
  padding and dropped when empty, over 31 runes (firmware node_name[32],
  CommonCLI.h:27), invalid UTF-8, or holding a control character. GPS optional.
- Table client_node_names: observations, not state, UNIQUE(target, rx_pubkey,
  observed_at), indexed on (target, observed_at) and observed_at.
- Retention retention.clientNodeNamesDays (0 = keep), startup prune plus the
  existing daily ticker.
- Contract in docs/client-rx-coverage.md, keys in config.example.json.

Tests: handler, validation cases, idempotent redelivery, newest observed_at
wins, prune, gate on/off (off does not fall through to the observer path).
Full ingestor suite passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A repeater asked for its name over /names usually has no nodes row, so
/api/nodes/resolve and the coverage tooltips could not name it.

lookupPrefixes (cmd/server/client_names.go) now counts candidates in nodes and
in client_node_names, one LIMIT 2 term per source per key, still one query per
chunk of 200 keys (400 compound terms, under SQLite's 500):

- unique pubkey across both sources: name from nodes when non-empty, else the
  newest client-reported name (by observed_at)
- different pubkeys across the sources: ambiguous, as before
- blacklist and hidden name prefixes apply whichever source named it

handleResolvePrefix and batchResolveHeardKeys both go through it, so the
tooltips need no frontend change. The table is probed with a latch and
re-probed while absent: the ingestor creates it, and a database without it
resolves from nodes alone, as before.

Tests: client-only node, nameless nodes row, advert name wins, cross-source
ambiguity, hidden, blacklisted, absent-then-created table, 250 keys over two
chunks. Full server suite passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The region-answers section said /regions is accepted under
clientRxCoverage.enabled. The ingestor dispatches it on
cfg.ClientRegionsEnabled() (cmd/ingestor/main.go, case "regions"), which is
also what docs/client-regions.md tells operators to set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ncy test

nodeNameMsg reads the clock, so building the message twice gave two
observations a millisecond apart on the race-detector runner (rows = 2,
run 38880444861). Build it once and hand it to the handler twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@efiten
efiten merged commit 01db820 into master Oct 16, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant