Safety Gate Task#271
Open
GCdePaula wants to merge 3 commits into
Open
Conversation
- This commit bumps Foundry on the CI and Dockefile. This is to align with the version of Foundry used on rollups-contracts, so that the node team can also bump Foundry (given that it uses the devnet image built in the dave repo still).
- Honeypot 3.0.1 fixes a bug in the rootfs Dockerfile that would lead to its build to fail. The resulting image is the same as 3.0.0 but we can now build the custom Honeypot rootfs image with devnet config.
Introduce a minimal task abstraction (ITask/ITaskSpawner) that decouples DaveConsensus from the proof system: - ITournament now extends ITask; result() projects arbitrationResult without the winner commitment, and cleanup() recovers bonds. - ITournamentFactory is replaced by ITaskSpawner. - DaveConsensus spawns tasks and settles on result(); canSettle reports the final machine state instead of the winner commitment. Add the Safety Gate, a delay-only middleware task: - SafetyGateTask gates an inner task behind unanimous sentry votes, with a permissionless fallback timer for liveness. Voting state is exposed as a tri-state SentryStatus (VOTING/AGREED/DISAGREED). - SafetyGateTaskSpawner wraps an inner spawner; the sentry manager can rotate the sentry set, effective from the next spawned task. - Starting the fallback timer is a manual, monitored operation; the node only casts sentry votes (see prt/docs/safety-gate.md). Deploy gates per app, not as shared infrastructure: - DaveAppFactory.newGatedDaveApp atomically deploys an app-specific SafetyGateTaskSpawner (wrapping the factory's bound proof system) and a DaveConsensus wired to it. Gate governance (sentry manager, sentries, disagreement window) is app-declared, like the template hash; deployment scripts carry no app-specific configuration. - Factory events are the canonical provenance check: DaveAppCreated and GatedDaveAppCreated certify the settlement mechanism and distinguish gated from bare apps. Both index the app and consensus addresses. Update the node accordingly: - epoch-manager detects a safety gate behind the EpochSealed task via ERC-165 (pinned ISafetyGateTask id, guarded by a Solidity test), casts a sentry vote when the signer is a sentry, and points the PRT player at INNER_TASK. - state-manager stores the epoch's final machine state alongside the computation hash, since canSettle now reports the final state. - the blockchain-reader e2e harness deploys a gated app through the factory, keeping the safety gate exercised in the node test pipeline.
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.
Summary
This PR introduces a minimal task abstraction (
ITask/ITaskSpawner) that decouplesDaveConsensusfrom the underlying proof system, and builds on it to add the Safety Gate — a delay-only middleware that gives a permissioned Sentry layer time to react to proof-system or application bugs before an epoch settles, without ever letting it change an outcome. Gated applications are deployed per-app through the existing factory. The reference node changes needed to consume this are described under Downstream impact.Targets
next/3.0. This is a breaking change to theIDaveConsensus/ PRT contract surface (see Breaking changes).Motivation
For early-stage (L2Beat Stage 1) deployments we want to trade a bit of decentralization for the ability to pause and recover from catastrophic bugs. The Safety Gate provides a disagreement buffer that only ever delays settlement — it can never alter the result — buying time for a higher-level authority to intervene. Realizing it cleanly required first separating "the thing that produces a final machine state" (a task) from "the PRT tournament" specifically, so the consensus contract and the application stay agnostic to what settles an epoch.
The
ITaskabstractionITask(result(),cleanup(), ERC-165) andITaskSpawner(spawn(initial, provider)). A task computes the final machine state for a fixed input range; consumers that only need the settled result depend onITaskrather than on a concrete proof system.ITournamentnow extendsITask:result()is the projection ofarbitrationResult()without the winner commitment, andcleanup()performs bond recovery.arbitrationResult()is unchanged.ITournamentFactoryis replaced byITaskSpawner;MultiLevelTournamentFactoryimplementsspawn.DaveConsensusnow spawns and settles tasks viaITask.result();canSettle()reports the final machine state instead of the winner commitment.The Safety Gate (middleware)
SafetyGateTaskwraps an inner task (a PRT root tournament) and gates its result behind a set of sentries:It is delay-only by construction:
result()only ever surfaces the inner task's state — sentries decide when it becomes visible, never what it is. It requires all sentries to be byzantine to bypass the window; a single disagreement or no-vote is enough to trigger the window. Moreover, if any sentry gets compromised, anyone can still drive settlement via the fallback timer (making the design walkaway-safe).SafetyGateTaskSpawnerlets aSENTRY_MANAGERrotate the sentry set for future epochs only. Voting state is exposed as a tri-statesentryStatus()(VOTING/AGREED/DISAGREED); tasks implement ERC-165 so off-chain actors can discover a gate (ISafetyGateTaskid0xf77c3559, pinned by a guard test).Per-app deployment
Gate governance (sentry manager, sentry set, disagreement window) is app-specific with no sensible deployment-time default, so gates are deployed per app, not as shared infrastructure.
DaveAppFactory.newGatedDaveApp(...)atomically deploys an app-specificSafetyGateTaskSpawner(wrapping the factory's bound proof system) and aDaveConsensuswired to it, emitting a distinctGatedDaveAppCreatedevent. Factory events are the canonical provenance check — they certify the settlement mechanism and distinguish gated from bare apps (this cannot be told from the factory address alone). BothDaveAppCreatedandGatedDaveAppCreatednow index the app and consensus addresses.Node changes
EpochSealedtask via ERC-165, casts a sentry vote for the locally-computed final state when its signer is a sentry, and resolvesINNER_TASK()to point the PRT player at the actual tournament. Starting the fallback timer is left as a manual, monitored operation.canSettlenow reports the final state).EpochSealedfield rename; the e2e harness deploys a gated app through the factory so the gate stays exercised in the node test pipeline.Breaking changes & downstream impact
For the reference node (consumes the published contract ABIs). Mostly mechanical after regenerating bindings:
canSettle()3rd returnwinnerCommitment (Tree.Node)→finalState (Machine.Hash)getTournamentFactory()→getTaskSpawner()EpochSealed.tournament→task;getCurrentSealedEpoch4th returnITournament→ITaskConsensusCreation3rd paramITournamentFactory→ITaskSpawnerDaveAppCreatedapp/consensus moved to indexed topicsSafetyGateTaskwrapperINNER_TASK()before binding as a tournamentITournamentFactory.soldeleted →ITaskSpawner.solresult/cleanup/supportsInterfaceadded;TournamentNotFinishedYetname keptSecurity
The gate's guarantee is delay, never change or freeze. The threat model is documented in
prt/docs/safety-gate.md. A max-effort review pass hardened several edges: the sentry set is length-bounded (MAX_SENTRIES = 16) — the sole anti-brick guarantee, enforced at both the spawner and the task, so a careless/compromised manager cannot freeze settlement with an oversized list; the zero address is rejected and duplicates are collapsed; the fallback-timer elapsed check is computed asnow - startso a pathological window cannot overflow and brickresult(); and the node's ERC-165 probe propagates transport errors instead of silently treating a gated epoch as bare.Testing
result()branch, vote reverts, tri-state consensus, timer semantics, sentry-set bounds, the interface-id guard).DaveConsensus → SafetyGateTask → real tournamentthrough all three settlement paths and proves sentries can only delay.newGatedDaveApp.forge fmtandcargo fmtclean.Out of scope / follow-ups
root_tournament→root_taskrename, per-tick RPC caching) are intentionally deferred, since that node is being rewritten on another branch.Chores (cherry-picked)
Includes two dependency bumps carried over from
feature/staging: Foundry 1.4.3 → 1.5.1 (with corrected sha256 pins — the upstream bump left them stale) and Honeypot 3.0.0 → 3.0.1.