fix: harden runtime authorized signer refresh - #7
Conversation
Merge the current feat/tsuki-hardfork-2 base into PR #7. Keep the base's DogeosBlock, DogeosNetworkPrimitives, RPC, and shutdown APIs while preserving the head-qualified authorization barrier, trusted-import deferral, and unwind-generation tagging.
Gate dynamic signer refresh behind an explicit opt-in and reconcile signer state with a head-qualified two-phase authorization barrier. Make reset and reorg transitions commit-aware, invalidate asynchronous block work across committed unwinds, and surface typed deferral/reset outcomes to callers. Add focused watcher, consensus, signer, reset, and configuration regressions.
d1d669f to
45e3663
Compare
Congrats! CodSpeed is installed 🎉
You will start to see performance impacts in the reports once the benchmarks are run from your default branch.
|
|
Closing without merge — a deliberate scope decision. The root-cause diagnosis stands and is preserved on #6, and the review rounds surfaced real hazards. But the remediation grew into ~2,800 lines of ordered-stream hardening (two-phase authorization barrier, dedicated priority control channel, unwind-generation tagging, staged commit-aware resets) for a feature that ships default-off and cannot be enabled until #8 (hash-pinned reads) and #31 (eth-wire signer gate) land. That is a deep cut into consensus-critical code we are still getting the hang of as a fork, guarding a flag nobody can flip. The next attempt will be a fresh, smaller cut built on a current-value abstraction for the signer (see the design note on #6) rather than more armor on the notification stream. What is preserved:
|
Summary
--consensus.runtime-authorized-signer-refresh, default off--consensus.authorized-signerand Noop consensus as static modesSyncingRoot cause
The original runtime refresh compared the latest L1 head against state that structural handling had already advanced, so the signer read was skipped. Enabling the corrected path exposed broader lifecycle assumptions: an explicit signer could be overwritten, storage reads were not tied to the observed head, refresh failures could stall derivation or report a false synced state, and asynchronous block work could cross signer-rotation, reorg, or reset boundaries.
Review also identified liveness gaps in the hardened state machine. Optimistic sync could mark L2 as syncing while an authorization barrier prevented network import, but the phase-two signer update was gated on L2 already being synced. Rejected, cancelled, post-admission failed, or sync-suspended manual builds could also leave the remote source waiting forever on unrelated global events.
Design
Dynamic mode emits:
AuthorizationPending(head)on a priority control channel before the structural transition.Reorg/NewBlockon the ordinary FIFO L1 channel.head.hash.AuthorizedSigner { head, signer }on the ordinary FIFO channel beforeProcessed.Consensus stays fail-closed until phase two matches the exact pending head. Read failures retain the barrier, use bounded retry backoff, and emit neither
ProcessednorSynced. The ordinary L1 branch is reachable when L2 is synced or authorization is pending, while retaining the derivation-empty and reset-tail gates, so phase two cannot deadlock optimistic sync or overtake structural work.Administrative resets and ordinary reorgs advance an unwind generation exactly once per committed unwind. In-flight payloads are cancelled, and signer results tagged with an older generation are discarded before database or network effects. Reset tails retain their committed unwind result, reject conflicting targets, and resume without double-unwinding.
BuildBlocknow returns a typed admission result for authorization/reset policy rejection, lack of full synchronization, an active payload, missing signer or sequencer configuration, and payload-start failure. Every command path answers its admission oneshot. Successful admission returns a per-request completion ticket. Payload finalization, L1-message persistence, and signer-enqueue errors resolve that ticket asFailed; empty, invalidated, and cancelled jobs resolve it asSkipped; successful signer enqueue resolves it asSequenced. Only one manual payload is admitted at a time, so a later request cannot replace the active job.Every orchestrator transition into L1 or L2
Syncingcancels an accepted payload before changing the sync state and resolves its ticket exactly once. Reset initiation performs this cancellation before the fallible database unwind, while optimistic sync performs it immediately after the engine accepts the optimistic head. A build cannot be admitted while either layer is already syncing.The remote source waits on this correlated ticket instead of a global event stream. It skips expected admission-policy rejections, reports operational admission and terminal failures, and retains shutdown awareness without an uncorrelated timeout.
Operator impact
Runtime refresh is opt-in:
Contradictory configurations are rejected. Explicit signer overrides remain static. The opt-in startup probe requires the configured L1 endpoint to accept hash-qualified EIP-1898 storage reads.
Zero remains a valid halt signer and produces a distinct warning on transition.
Base reconciliation
Rebased onto
feat/tsuki-hardfork-2atf0b480d, which includes PR #22 and removes the Rust Anvil/Tempo dependency conflict from the test graph. No separate test-infrastructure fix was cherry-picked.Validation on the rebased tip
cargo check --workspace --all-features --lockedRUSTUP_TOOLCHAIN=nightly-2026-01-18 cargo fmt --all -- --checkgit diff --checkThe full sequencer suite still has inherited failures in the rebased DogeOS execution stack (
missing exec scalar in spec_id=TSUKI, followed byValidationServiceUnreachable); the cancellation changes are not the source of those failures.Required before enabling dynamic refresh in production
--network.valid_signereth-wire compatibility signerThese items do not block merging the default-off implementation.
Fixes #6