Skip to content

WarmNodeStore: carry the XEdDSA signer flag through the warm tier#11020

Merged
caveman99 merged 3 commits into
developfrom
warm-signer-bit
Jul 16, 2026
Merged

WarmNodeStore: carry the XEdDSA signer flag through the warm tier#11020
caveman99 merged 3 commits into
developfrom
warm-signer-bit

Conversation

@caveman99

@caveman99 caveman99 commented Jul 16, 2026

Copy link
Copy Markdown
Member

The warm tier already caches an evicted node's role and protected category, but not
whether we ever verified an XEdDSA signature from it. That flag is learned from
verified traffic rather than from NodeInfo, so a node that was evicted and later
re-admitted came back with it clear until the next signed packet arrived.

Cache it in the same packed word: last_heard now carries 7 metadata bits (role 4,
protected 2, signer 1) and getOrCreateMeshNode() restores the flag on re-admission
alongside the role and key. WarmNodeEntry stays 40 B.

Bit 6 was part of the quantised timestamp under the v2 record format, so a v2 record
read with the new layout would report a signer about half the time. The record magic
moves to WRN3/WRM3 and load() clears that bit on v2 records on both backends
(raw-flash ring and warm.dat), rewriting them in the current format. v2 keeps its
role/protected bits; v1 handling is unchanged (identity + key kept, last_heard
discarded). The ring forces a rotation when the head page predates v3 so new records
never land in an older-format page.

Tests: signer round trip through absorb/take with a cleared-flag control; a v2
snapshot migration asserting a stale timestamp bit does not read as a signer; and an
eviction/re-admission round trip through NodeDB with a non-signer control.

Note for #10967, which touches the same metadata: it adds WarmProtected::XeddsaSigner
as a fourth category value for this purpose. Since WarmProtected holds one value,
that makes the flag exclusive with the existing categories, and warmProtectedCategory()
returns it ahead of the favourite/ignored/manually-verified check, so a favourite or a
TRACKER that has signed comes back without its protected category. A dedicated bit keeps
the two independent. hasSeenXeddsaSigner() from that PR works unchanged on top of this
via lookupMeta; only the category value needs dropping on rebase.

Summary by CodeRabbit

  • Bug Fixes
    • Preserved XEdDSA signer status across hot-to-warm and warm-to-hot node transitions.
    • Ensured warm-node persistence/reload correctly retains or clears the signer bit depending on the stored format.
    • Enhanced warm storage migration for older on-disk layouts to prevent signer-bit misreads from timestamp metadata.
  • Tests
    • Added warm-tier migration coverage to verify signer-bit survival without affecting non-signer nodes.
    • Added warm store round-trip and legacy-format migration tests for signer-bit behavior.

The signer flag is learned from verified traffic rather than from NodeInfo, so a
node evicted to the warm tier and re-admitted came back with it clear until the
next signed packet arrived. Cache it alongside the role and protected category.

last_heard now packs 7 metadata bits (role 4, protected 2, signer 1). Bit 6 was
part of the timestamp in the v2 format, so the record magic moves to WRN3/WRM3 and
load() clears that bit on v2 records rather than reading it as a signer. v1
handling is unchanged. Entry size stays 40 B.
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

Warm-node persistence now stores the XEDDSA signer bit, supports v3 warm storage metadata, migrates v1/v2 records, restores the bit during NodeDB re-admission, and adds round-trip and migration tests.

Changes

Warm signer-bit persistence

Layer / File(s) Summary
Signer metadata and format contracts
src/mesh/WarmNodeStore.h, src/mesh/WarmNodeStore.cpp
Warm metadata packing and WarmNodeStore::absorb now include the signer bit, with explicit v1/v2/current format identifiers.
Warm storage migration
src/mesh/WarmNodeStore.cpp
Ring and filesystem loaders normalize v1/v2 records and rotate subsequent writes onto the current format.
NodeDB round-trip integration and tests
src/mesh/NodeDB.cpp, test/test_nodedb_blocked/test_main.cpp, test/test_warm_store/test_main.cpp
Hot-to-warm paths persist signer status, warm re-admission restores it, and tests cover round trips, migration, and metadata preservation.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant NodeDB
  participant WarmNodeStore
  participant WarmStorage
  NodeDB->>WarmNodeStore: absorb node metadata and signer bit
  WarmNodeStore->>WarmStorage: persist v3 warm record
  WarmStorage-->>WarmNodeStore: load and migrate v1/v2 records
  WarmNodeStore-->>NodeDB: return warm entry and signer bit
  NodeDB->>NodeDB: restore XEDDSA signer status
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: carrying the XEdDSA signer flag through the warm tier.
Description check ✅ Passed The description is substantive and covers the change, migration, tests, and caveats, though it omits the template's checklist attestations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch warm-signer-bit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/mesh/WarmNodeStore.h (1)

46-53: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Condense the new explanatory comments to at most two lines.

  • src/mesh/WarmNodeStore.h#L46-L53: summarize the metadata layout and timestamp quantization.
  • src/mesh/WarmNodeStore.cpp#L19-L23: reduce the legacy-format explanation.
  • src/mesh/WarmNodeStore.cpp#L406-L408: shorten corrupt-header handling rationale.
  • src/mesh/WarmNodeStore.cpp#L457-L460: condense V1/V2 normalization rationale.
  • src/mesh/WarmNodeStore.cpp#L478-L480: shorten the forced-rotation explanation.
  • src/mesh/WarmNodeStore.cpp#L582-L584: condense the V1 filesystem migration note.
  • test/test_warm_store/test_main.cpp#L285-L287: shorten the V2 test description.
  • test/test_nodedb_blocked/test_main.cpp#L135-L138: shorten the signer round-trip description.

As per coding guidelines, keep comments to one or two lines maximum and only explain non-obvious reasons.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mesh/WarmNodeStore.h` around lines 46 - 53, Condense the explanatory
comments to one or two lines at each listed site: summarize metadata layout and
timestamp quantization in WarmNodeStore.h; reduce the legacy-format,
corrupt-header, V1/V2 normalization, forced-rotation, and V1 filesystem
migration explanations in WarmNodeStore.cpp; and shorten the V2 test and signer
round-trip descriptions in the two test_main.cpp files. Preserve only the
non-obvious rationale and do not change code behavior.

Source: Coding guidelines

test/test_warm_store/test_main.cpp (1)

329-334: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that V2 migration preserves the timestamp.

The test promises time preservation but only verifies signer, role, and protection. Assert warmTimeOf(e) so corruption of the upper timestamp bits cannot pass unnoticed.

Proposed test assertion
     TEST_ASSERT_FALSE_MESSAGE(warmSignerOf(e), "a v2 timestamp bit must not read as a signer");
+    TEST_ASSERT_EQUAL(123456u & WARM_TIME_MASK, warmTimeOf(e));
     TEST_ASSERT_EQUAL(5, warmRoleOf(e));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/test_warm_store/test_main.cpp` around lines 329 - 334, Extend the V2
migration assertions in the test around warmSignerOf, warmRoleOf, and warmProtOf
to also verify warmTimeOf(e) preserves the expected timestamp value. Use the
timestamp established by the test fixture or migration input, ensuring upper
timestamp-bit corruption is detected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/mesh/WarmNodeStore.h`:
- Around line 46-53: Condense the explanatory comments to one or two lines at
each listed site: summarize metadata layout and timestamp quantization in
WarmNodeStore.h; reduce the legacy-format, corrupt-header, V1/V2 normalization,
forced-rotation, and V1 filesystem migration explanations in WarmNodeStore.cpp;
and shorten the V2 test and signer round-trip descriptions in the two
test_main.cpp files. Preserve only the non-obvious rationale and do not change
code behavior.

In `@test/test_warm_store/test_main.cpp`:
- Around line 329-334: Extend the V2 migration assertions in the test around
warmSignerOf, warmRoleOf, and warmProtOf to also verify warmTimeOf(e) preserves
the expected timestamp value. Use the timestamp established by the test fixture
or migration input, ensuring upper timestamp-bit corruption is detected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 865aead0-f9b1-4861-a5bd-b2f4133ffe93

📥 Commits

Reviewing files that changed from the base of the PR and between 53a6b5e and 8225d35.

📒 Files selected for processing (5)
  • src/mesh/NodeDB.cpp
  • src/mesh/WarmNodeStore.cpp
  • src/mesh/WarmNodeStore.h
  • test/test_nodedb_blocked/test_main.cpp
  • test/test_warm_store/test_main.cpp

@caveman99

Copy link
Copy Markdown
Member Author

Both addressed in 990297a.

Comment length: condensed every comment this PR adds or touches to two lines, per the one-or-two-line guideline. Skipped one site from the list: the corrupt-header rationale in WarmNodeStore.cpp load() is pre-existing and unchanged here (only the adjacent bool legacy -> WarmFormat fmt line moved), so shortening it would be unrelated churn.

Timestamp assertion: added, good catch. The test claimed time carries over but only checked the flag bits, so a mask error in the upper bits would have gone unnoticed. Note the fixture happens to make this sharper than it looks: 123456 has bit 6 set in its own timestamp, so the assertion also pins that warmPackLastHeard() masks the time before OR-ing the signer flag in.

@caveman99
caveman99 requested a review from RCGV1 July 16, 2026 14:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

This PR updates WarmNodeStore persistence to retain whether a node has ever produced a verified XEdDSA signature across eviction/re-admission, including safe migration for older warm-store formats.

Changes:

  • Packs an additional “XEdDSA signer seen” bit into last_heard metadata and restores it on node re-admission.
  • Updates warm-store page/file magic (WRN3/WRM3) and migrates v1/v2 records safely (including clearing v2’s former timestamp bit 6).
  • Adds tests covering signer-bit round trips, v2 snapshot migration, and NodeDB eviction/re-admission behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test/test_warm_store/test_main.cpp Adds signer-bit and v2-migration tests; updates quantization commentary.
test/test_nodedb_blocked/test_main.cpp Adds NodeDB test ensuring signer bit survives warm-tier round trip.
src/mesh/WarmNodeStore.h Expands packed metadata to include signer bit; adds format enum and accessor.
src/mesh/WarmNodeStore.cpp Implements v3 magic + v1/v2 replay normalization for both ring and file backends.
src/mesh/NodeDB.cpp Persists and restores signer bit when demoting/evicting/re-admitting nodes.

Comment thread test/test_warm_store/test_main.cpp Outdated
Comment thread test/test_warm_store/test_main.cpp
Comment thread src/mesh/WarmNodeStore.h
Comment thread src/mesh/WarmNodeStore.cpp Outdated
@caveman99

Copy link
Copy Markdown
Member Author

Copilot findings addressed in 87a94c6:

  • warmSignerOf implicit bool→bool (WarmNodeStore.h): now returns an explicit != 0 comparison.
  • legacy duplicating fmt (WarmNodeStore.cpp load()): legacy is now derived as fmt != WarmFormat::Current, and a separate known flag drives the reject, so the format decision lives in one place.
  • Hard-coded << 6 (test_warm_store): the LRU-spacing test now shifts by WARM_META_BITS, so it tracks the quantum instead of a stale literal.
  • Unchecked short read/write (v2 migration test): the read and write byte counts are now asserted against buf.size().

Left the identical read/write pattern in the pre-existing v1-migration and oversized-snapshot tests as-is to keep this diff to the signer change; happy to tighten those in a follow-up if you'd like them consistent.

@github-actions

Copy link
Copy Markdown
Contributor

⚡ Try this PR in the Web Flasher

Note

Building this pull request… the flash button, badges and supported-board
list will appear here automatically once CI finishes.

@NomDeTom NomDeTom left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm

@thebentern thebentern added the bugfix Pull request that fixes bugs label Jul 16, 2026
@caveman99
caveman99 merged commit e219e24 into develop Jul 16, 2026
86 of 87 checks passed
@caveman99
caveman99 deleted the warm-signer-bit branch July 16, 2026 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Pull request that fixes bugs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants