Skip to content

fix: harden runtime authorized signer refresh - #7

Closed
dghelm wants to merge 6 commits into
feat/tsuki-hardfork-2from
fix/runtime-authorized-signer-refresh-6
Closed

fix: harden runtime authorized signer refresh#7
dghelm wants to merge 6 commits into
feat/tsuki-hardfork-2from
fix/runtime-authorized-signer-refresh-6

Conversation

@dghelm

@dghelm dghelm commented Jul 23, 2026

Copy link
Copy Markdown

Summary

  • add a head-qualified, two-phase authorization barrier for runtime L1 signer refresh
  • pin signer storage reads to the observed L1 block hash using EIP-1898
  • keep runtime refresh behind --consensus.runtime-authorized-signer-refresh, default off
  • preserve explicit --consensus.authorized-signer and Noop consensus as static modes
  • make reset and reorg transitions staged, fail-closed, retryable, and generation-aware
  • invalidate pre-unwind payload/signing work before it can be persisted or announced
  • keep authorization phase two reachable while optimistic L2 synchronization is active
  • defer trusted imports while authorization or reset transitions are pending
  • acknowledge manual block-build admission with typed outcomes
  • reject manual block builds unless the orchestrator is fully synchronized
  • cancel and resolve active builds before optimistic-sync and reset transitions enter Syncing
  • give every admitted manual build a correlated terminal completion ticket
  • report success, cancellation/skip, and post-admission payload failures to the exact waiting caller
  • keep remote-source completion waits shutdown-aware without an unsafe timeout
  • surface typed reset failures through the handle, RPC, and debug REPL

Root 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:

  1. AuthorizationPending(head) on a priority control channel before the structural transition.
  2. Reorg / NewBlock on the ordinary FIFO L1 channel.
  3. A signer read pinned to head.hash.
  4. AuthorizedSigner { head, signer } on the ordinary FIFO channel before Processed.

Consensus stays fail-closed until phase two matches the exact pending head. Read failures retain the barrier, use bounded retry backoff, and emit neither Processed nor Synced. 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.

BuildBlock now 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 as Failed; empty, invalidated, and cancelled jobs resolve it as Skipped; successful signer enqueue resolves it as Sequenced. 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 Syncing cancels 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:

--consensus.runtime-authorized-signer-refresh

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-2 at f0b480d, 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

  • orchestrator unit suite, including correlated-ticket and injected post-admission failure regressions
  • signer and watcher suites
  • optimistic-sync authorization-overlap regression
  • typed BuildBlock rejection regression, including unsynchronized admission
  • exact-once cancellation regression, including correlated ticket completion
  • optimistic-sync transition cancellation regression
  • reset-transition cancellation regression
  • remote-source suite, 3/3 serially
  • strict Clippy for the orchestrator and node across all targets
  • cargo check --workspace --all-features --locked
  • RUSTUP_TOOLCHAIN=nightly-2026-01-18 cargo fmt --all -- --check
  • git diff --check

The full sequencer suite still has inherited failures in the rebased DogeOS execution stack (missing exec scalar in spec_id=TSUKI, followed by ValidationServiceUnreachable); the cancellation changes are not the source of those failures.

Required before enabling dynamic refresh in production

  • deploy and integration-test DogeOS L1 EIP-1898 support
  • add the deterministic watcher/orchestrator interleaving harness
  • close or formally specify the residual in-flight external engine-call linearization window
  • decide runtime behavior for the static --network.valid_signer eth-wire compatibility signer

These items do not block merging the default-off implementation.

Fixes #6

@dghelm
dghelm marked this pull request as ready for review July 23, 2026 22:40
@dghelm dghelm changed the title fix: refresh authorized signer at runtime fix: harden runtime authorized signer refresh Aug 12, 2026
dghelm added a commit that referenced this pull request Aug 13, 2026
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.
dghelm added 6 commits August 12, 2026 22:22
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.
@dghelm
dghelm force-pushed the fix/runtime-authorized-signer-refresh-6 branch from d1d669f to 45e3663 Compare August 13, 2026 13:24
@codspeed-hq

codspeed-hq Bot commented Aug 13, 2026

Copy link
Copy Markdown

Congrats! CodSpeed is installed 🎉

🆕 2 new benchmarks were detected.

You will start to see performance impacts in the reports once the benchmarks are run from your default branch.

Detected benchmarks


Open in CodSpeed

@dghelm

dghelm commented Aug 14, 2026

Copy link
Copy Markdown
Author

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:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant