fix(validator): fail closed on signing-protection persistence gaps (A-1315, A-1317, A-1318)#24565
Merged
Merged
Conversation
…-1315, A-1317, A-1318)
Protection stores (signing protection, and for the ordering fix world-state) must never be
silently emptied or bypassed by the store-creation path. Three related fixes:
- A-1315: DatabaseVersionManager now opens the database before writing the version marker, so
the marker is a post-commit record. A crash between reset and a durable open leaves no marker,
and the next start re-runs the reset instead of trusting a marker over empty data. writeVersion
is now an atomic durable write (temp file, fsync, rename, best-effort dir fsync).
- A-1318: a version file that exists but cannot be read (permissions, IO error, truncation) no
longer defaults to an empty version — which would reset the data directory. A new
versionFileReadFailurePolicy ('reset' default, 'throw' for signing protection) makes the local
signing-protection store refuse to open and leave data untouched on a transient read failure.
This closes the gap left by the A-1029 schema-mismatch 'throw' policy.
- A-1317: createLocalSignerWithProtection now throws when no data directory is configured, since
an ephemeral store drops all double-signing protection across restarts. Dev/test networks can
opt in via VALIDATOR_ALLOW_EPHEMERAL_SIGNING_PROTECTION; the local network sets it by default.
e19a807 to
1a477f2
Compare
alexghr
approved these changes
Jul 7, 2026
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.
Context
A store that must never silently become empty — the single-node signing-protection LMDB, and (for the ordering fix) world-state — could be wiped or bypassed by the
DatabaseVersionManager/ store-creation path.writeVersion()ran beforeonOpen(), and was a plain non-atomicwriteFile. A crash in between left a "valid" marker over an empty/partial data dir; on restart the self-healing reset was skipped forever (a stable wedge for world-state, which opens several native stores inonOpen).DatabaseVersion.empty(), which unconditionally triggered a reset — so a transient permissions/disk error at startup rm-rf'd the signing-protection DB. TheschemaVersionMismatchPolicy: 'throw'added by A-1029 only guarded the numeric-mismatch branches, not this one.dataDirectorysilently selected a fresh ephemeral tmp store on every start, giving no double-signing protection across restarts, with only adebuglog.Approach
DatabaseVersionManager.open()now opens the database before writing the version marker, making the marker a post-commit record — a crash before a durable open leaves no marker, so the next start re-runs the reset.writeVersionis now an atomic durable write (temp file → fsync → rename → best-effort directory fsync). The marker is only (re)written when it would actually change (first boot, reset, upgrade), which also avoids leaking a freshly opened DB if the write fails and drops the per-boot fsync.versionFileReadFailurePolicy('reset'default, preserving existing behavior for archiver/p2p/world-state;'throw'for signing protection). On'throw', an unreadable version file fails startup with an operator-actionable error and leaves data untouched. Threaded through the kv-storecreateStoreoptions.createLocalSignerWithProtectionnow throws when no data directory is configured, unlessallowEphemeralSigningProtection(envVALIDATOR_ALLOW_EPHEMERAL_SIGNING_PROTECTION, default false) is set, in which case it warns loudly. The local network sets the flag by default; the production default is strict fail-fast.The rollup-address-change reset is intentionally left as-is: the LMDB slashing DB relies on it (see the
cleanupOutdatedRollupDutiesno-op).Reviewed by Codex and a second model; both confirmed the atomic-write mechanics, ENOENT branching, migration idempotency, and the schema-composition decision (a cross-field Zod refine was avoided because it would break the downstream
.merge()/.extend()chain, so the invariant is enforced at the factory).Operator-facing changes are documented in the v5 changelog.
Fixes A-1315
Fixes A-1317
Fixes A-1318