fix(hash-size): key the zero-hop advert skip on the path byte, not the route type - #1913
Conversation
… type A zero-hop direct advert carries no path, so its hop count is 0. Whether the two size bits next to it mean anything depends on the sender: - Firmware that predates meshcore-dev/MeshCore#3293 does `path_len = 0` in `Mesh::sendZeroHop()`, wiping the whole byte. 0x00 says nothing about the node's path.hash.mode — skipping it is correct and Kpa-clawbot#649 was right. - A sender that writes the size through `setPathHashSizeAndCount()` emits 0x40 or 0x80 with a zero hop count. Nothing else can set those bits on a zero-hop packet, so they are a deliberate declaration, not noise. Kpa-clawbot#653 landed the skip as `pathByte & 0x3F == 0`, which also swallows the second case; the diagnosis in Kpa-clawbot#649 had proposed `pathByte == 0x00`. That mattered little in April because no firmware wrote the bits. It matters now: on the Czech mesh (869.4 MHz), a 24h window of 10k packets holds 54 zero-hop direct adverts — 39 at 0x00 and 15 carrying a declared size (14× 0x40, 1× 0x80). What the current behaviour costs, measured on one node across a 7-day window: a companion was reconfigured from a 2-byte to a 3-byte path hash. Its first advert under the new setting was a zero-hop direct one, 24 Aug 15:36 UTC, declaring 0x80. That packet was dropped, so the node kept reading as 2-byte until its next flood advert arrived on 25 Aug 10:18 UTC — 18h42m serving a configuration the analyzer had already been told was stale. With local adverts every 2h and flood adverts every 25h, that gap is the normal case rather than a corner one. Measured A/B over a real 181k-transmission database (973 nodes), same commit either side: two nodes changed, none regressed, and hash_size_inconsistent stayed at 6 in both runs. A node that changes its mode mid-window is handled by the recency decay from Kpa-clawbot#1788 — reading these packets does not resurrect false "varies" flags. Same rule applied in computeAnalyticsHashSizes so the two views agree. TestHashSizeTransportDirectZeroHopSkipped used 0x40 as its "should be skipped" fixture; it now uses 0x00, which is the case it was actually written to cover (Kpa-clawbot#747's missing RouteTransportDirect skip). The 0x40 case is covered by the new tests, with the opposite expectation. Not touched: the decoders in cmd/server/decoder.go and cmd/ingestor/decoder.go still report HashSize 0 for these packets. That changes per-packet display rather than node attribution and is a separate call. Tests: 5 new cases in cmd/server/zerohop_hashsize_test.go, two of them built from real off-air packets. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Review from the queue triage (#1922). This is the one PR in the queue I had not reviewed yet, so this is written fresh today rather than posted from the backlog. Verdict: approve. The reasoning is right and I could not fault it. Two things worth putting on the record, both of which make it safer than it looks. The firmware premise is correct, but the PR it depends on has not shippedThe change trusts a non-zero path byte with a zero hop count as a deliberate size declaration. That behaviour comes from That firmware PR is still open, not merged. So essentially no node in the field emits such a byte yet. This is forward-looking preparation, which is fine, but it is worth stating in the PR body so nobody reads it as fixing something visible today. Measured on a live network, the change moves exactly one packetThe only bytes where old and new behaviour differ are those whose low six bits (the hop count) are zero while the byte itself is not: Across 54,452 direct and transport-direct ADVERTs on a live instance:
Only Also goodMaking One interaction to be aware of, not a change requestThree open PRs now all move hash-size semantics: this one, #1912 (render an unobserved size as unknown rather than 1 byte) and the #1784 pathTrust chain (stop counting 1-byte prefixes as mapping evidence by default). They do not conflict textually, but merged together they change what the hash-size views report in three different ways at once. Worth a single line in the release notes covering all three rather than three separate surprises. |
|
Recycling this again, and the reason is my mistake rather than anything about your PR. Earlier today I approved the pending workflow run on this PR. That was the wrong order: approving an Closing and reopening now gets a fresh merge commit against current master, which is what the run should have been all along. No action needed from you, and apologies for the second round of noise. |
|
Merging in the #1922 queue run. My review above stands: approve, and the two things I put on the record there are worth keeping in view rather than acting on now. Short version for anyone reading later: the firmware behaviour this relies on ( Making |
Summary
computeNodeHashSizeInfoskips zero-hop direct adverts by route type. It should skip them by the content of the path byte, because the two cases are no longer the same thing.A zero-hop direct advert carries no path, so its hop count is 0. Whether the two size bits next to it mean anything depends on the sender:
packet->path_len = 0inMesh::sendZeroHop(), wiping the whole byte including the size bits.0x00genuinely says nothing about the node'spath.hash.mode— skipping it is right, and Direct/zero-hop adverts make node always appear as using 1 byte hash size #649 was right.setPathHashSizeAndCount()emits0x40(2 bytes) or0x80(3 bytes) with a zero hop count. On a zero-hop packet nothing else can set those bits, so they are a deliberate declaration.#653 landed the skip as
pathByte & 0x3F == 0, which swallows the second case too. The diagnosis in #649 had actually proposedpathByte == 0x00; the review widened it on the reasoning that a zero hop count always implies zeroed size bits. That was true in April, when no firmware wrote them.It is not true now. On the Czech mesh (869.4 MHz), a 24h window of 10k packets holds 54 zero-hop direct adverts: 39 at
0x00and 15 carrying a declared size (14×0x40, 1×0x80).Why it matters for display, not just tidiness
Measured on one node over a 7-day window. A companion was reconfigured from a 2-byte to a 3-byte path hash. Its first advert under the new setting was a zero-hop direct one on 24 Aug 15:36 UTC declaring
0x80. That packet was dropped, so the node kept reading as 2-byte until its next flood advert arrived on 25 Aug 10:18 UTC — 18h42m serving a configuration the analyzer had already been told was stale, confirmed against both an unpatched and a patched instance.With local adverts typically every 2h and flood adverts every 25h, that gap is the normal case rather than a corner one. It bites hardest on an instance whose retention window is shorter than a flood advert interval: there the node has no countable advert at all and falls out of
hash_sizeentirely (which is what #1912 is about on the rendering side).Change
(pathByte & 0x3F) == 0→pathByte == 0x00, incomputeNodeHashSizeInfoand incomputeAnalyticsHashSizesso the two views agree.isZeroHoprenamed toisUndeclaredZeroHopin the latter, since that is now what it means. No complexity change — same single byte comparison inside the existing scan.Measured A/B
Two builds of the same commit, one with the change, both run read-only against the same copy of a real 181k-transmission / 973-node database:
hash_size_inconsistentmulti_byte_statussplitThe flip-flop flag not moving is the point worth checking: a node that legitimately changes its mode mid-window is still handled by the recency decay from #1788, so reading these packets does not resurrect false "varies".
Tests
cd cmd/server && go test ./...→ ok, 0 failures. Coverage 83.5%, unchanged from master.5 new cases in
cmd/server/zerohop_hashsize_test.go, two built from real off-air packets:0x40→HashSize 2(was: dropped)0x80→HashSize 30x00→ still absent from the map, i.e. Direct/zero-hop adverts make node always appear as using 1 byte hash size #649's behaviour preservedcomputeMultiByteCapabilityasconfirmed, which is what the map's multi-byte overlay readsOne existing test changed, flagging it explicitly:
TestHashSizeTransportDirectZeroHopSkippedused0x40as its "should be skipped" fixture. It now uses0x00— the case it was written to cover, since #747 was about the missingRouteTransportDirectskip rather than about the size bits. The0x40case is covered by the new tests with the opposite expectation.Deliberately not touched
The decoders (
cmd/server/decoder.go:648,cmd/ingestor/decoder.go:1045) still reportHashSize 0for these packets, so per-packet views keep showing the size as unknown. Arguably they should follow the same rule, but that changes packet display rather than node attribution and felt like a separate call for you to make.Caveat worth stating
This attributes a declared size to the pubkey inside the advert. That holds as long as the advert was transmitted by the node that owns it — the same assumption the existing zero-hop flood path already makes, so this change does not widen it.