fix(genesis): write the canonical authority root on the existing-head branch and finish first-run identity publication in-session - #869
Merged
Conversation
… branch and finish first-run identity publication in-session Every freshly created wallet persisted a device head whose genesis authority root was [0u8; 32] while AppState held the real seed-rooted v3.g; the ERA faucet's authority evidence, which re-derives the true G, then failed closed on every first-run device. Root cause, reproduced at its ordering by the new tests: install_v2_genesis calls StateMachine::set_state BEFORE CoreSDK::write_genesis_device_head; set_state materialised a zero-genesis head when none existed, and write_genesis_device_head only wrote `genesis` on its construct-new branch, so the existing-head branch — the one genesis install always takes — left the persisted head claiming a zero root. AppRouterImpl::new's synthetic genesis path would then have overwritten a restored canonical root on every router build. - DeviceState::set_genesis_digest installs G on an already-constructed head. - StateMachine::set_state re-seeds an EXISTING head only; it does not know G and no longer manufactures one. - write_genesis_device_head sets `genesis` on both branches and installs the head in memory; the synthetic path refuses to run over an identity that already has a canonical head. - kyber_identity: a cold LOCAL_KYBER_PUBKEY slot is recovered from the canonical derivation instead of refused, so first-run devices no longer park in PublicationPending until an app restart. - system.createGenesisV2 writes the publication row and drives publish_identity_now in-session (spawned; genesis never blocks on the network; the startup retry stays the backstop). - identity_publication posts dsm-wallet-refresh after the Published row so the host recomputes the session phase. Hardware (4-phone beta fleet, 2026-09-13): identity PUBLISHED in-session (5/5 verified, quorum 3) on all four; the faucet admits with no restart.
… genesis they declare StateMachine::set_state no longer manufactures a head — it does not know the genesis authority root and must not invent one — so the three traces that built a machine from a bare State (transfer chain, signature rejection, fork divergence) had no head to advance and CI's implementation-trace replay went 2/16 FAIL. The traces build their state with State::new_genesis(seed, ..): the seed IS the root they declare, so the head is now installed with it explicitly through the real API (set_device_head), then set_state re-seeds the legacy root exactly as before. A declared root in a validation trace is honest where a fabricated one in production was not. `cargo run --locked -p dsm_vertical_validation -- implementation-traces`: 16/16 PASS; the crate's tests: green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every freshly created wallet persisted a device head whose genesis authority root was
[0u8; 32]whileAppStateheld the real seed-rootedv3.g; the ERA faucet's authority evidence (which re-derives the trueG) then failed closed on every first-run device. This PR fixes the root at its ordering and makes first-run identity publication complete in-session.Root cause (reproduced by the new tests)
install_v2_genesiscallsStateMachine::set_stateBEFORECoreSDK::write_genesis_device_head.set_statematerialised a head with a zero genesis when none existed, andwrite_genesis_device_headonly wrotegenesison its construct-new branch — so the existing-head branch (the one genesis install always takes) left the persisted head claiming a zero root. On top of that,AppRouterImpl::newran the synthetic-genesis path on every router build, which once the write became authoritative would have overwritten a restored canonical root.What changes
dsm/types/device_state.rs:DeviceState::set_genesis_digest— installGon an already-constructed head.dsm/core/state_machine/mod.rs:set_statere-seeds an EXISTING head only; it does not knowGand no longer manufactures a zero-root head.dsm_sdk/sdk/core_sdk.rs:write_genesis_device_headsetsgenesisunconditionally on both branches and installs the head in memory; the synthetic-genesis path refuses to run over an identity that already has a canonical head. Three tests reproduce the defect at its ordering: the existing-head branch,set_statenot fabricating, and the router build not overwriting a restored root.dsm_sdk/sdk/kyber_identity.rs: a coldLOCAL_KYBER_PUBKEYslot is recovered from the canonical derivation (current_smaster()+DSM/kyber\0) instead of refused — first-run devices no longer park inPublicationPendinguntil an app restart.dsm_sdk/handlers/system_routes.rs:system.createGenesisV2writes the publication row and drivespublish_identity_nowin-session (spawned; genesis never blocks on the network; the startup retry stays the backstop, not the only driver).dsm_sdk/sdk/identity_publication.rs: after thePublishedrow is written, postdsm-wallet-refreshso the Android host recomputes the session phase instead of showing "PUBLISHING IDENTITY…" until restart.Hardware
On the 4-phone beta fleet (2026-09-13, wiped devices):
system.createGenesisV2: identity PUBLISHED … (5/5 verified, quorum 3)in-session on all four; the ERA faucet claim then admits (100 ERA each) with no restart.Verification
cargo test --release -p dsm --lib core::state_machine types::device_statecargo test --release -p dsm_sdk --lib sdk::core_sdk sdk::kyber_identity sdk::identity_publication handlers::system_routes init::(--test-threads=1)-D warnings/ci/production_safety_checks.shon the branch