feat: gate unstake behind a fork and discover activations in the worker - #188
Closed
prajwolrg wants to merge 9 commits into
Closed
feat: gate unstake behind a fork and discover activations in the worker#188prajwolrg wants to merge 9 commits into
prajwolrg wants to merge 9 commits into
Conversation
|
Commit: 57e4fb2
|
prajwolrg
force-pushed
the
upgradability-fork-activation
branch
2 times, most recently
from
July 7, 2026 09:36
e24be6d to
4c08b69
Compare
prajwolrg
force-pushed
the
upgradability-params-spec
branch
from
July 7, 2026 09:58
d2d1a3f to
23a773a
Compare
prajwolrg
force-pushed
the
upgradability-fork-activation
branch
2 times, most recently
from
July 8, 2026 06:00
1f2c3f1 to
f0371ba
Compare
prajwolrg
force-pushed
the
upgradability-params-spec
branch
from
July 8, 2026 06:00
23a773a to
f39db77
Compare
prajwolrg
force-pushed
the
upgradability-fork-activation
branch
from
July 8, 2026 06:31
f0371ba to
8d64daf
Compare
prajwolrg
force-pushed
the
upgradability-params-spec
branch
from
July 8, 2026 06:31
f39db77 to
3b5a0f0
Compare
The bridge gates unstake txs on ForkId::Fork1 in BOTH phases: pre-fork the aux request is never made and the handler rejects before touching aux data, keeping aux request/consumption in lockstep. Behavior is unchanged for now — every executor still runs with the fork active since genesis (StfParams::all_forks_enabled, replacing the all-disabled Default at executor call sites).
Persistence for the fork activations the worker will discover from ASM VK upgrade logs; the worker-side consumer lands in a follow-up commit. Each record carries the predicate the upgrade enacted: enactment only surfaces the new VK in the emitted log, so these records are the worker's durable copy of the key each boundary switched to.
ForkActivationStore joins WorkerContext as its fifth concern, backed by the sled store in the runner and an in-memory impl in tests. Plumbing only; the discovery logic that populates it lands next.
Name the fork a VK upgrade activates in the signed action itself: the chain is the single authority on what an upgrade activates, and the fork id renders into the signing message so the multisig authorizes it alongside the predicate hash. The emitted AsmStfUpdate log forwards it to observers. The admin does not validate the id: each update is expected to name the fork it newly activates, and upholding that is on the multisig when authoring the action. The action carries a raw u16 rather than the ForkId enum because the upgrade that activates a fork is enacted by the artifact that predates it, so the wire format cannot require knowing the fork — and the id's representation may still change.
When a processed block's manifest carries an AsmStfUpdate, the worker activates the fork the log names from the next block on and persists the activation — with the predicate the upgrade enacted — keyed by (enacting height, fork). Ordering carries the guarantees: the record is written before the enacting block's anchor commit so a committed anchor can never lack its activation (crash-replay rewrites the same record), and every sync rebase prunes activations above the base before re-processing so a reorged-out enactment cannot leak into the new branch — its blocks re-discover any activation that survives. An upgrade naming an already-active fork is an operational flaw on the authoring side; it is skipped loudly and leaves the schedule untouched so it cannot retro-raise an activation height. One naming a fork id unknown to this binary is skipped too, since a binary that cannot apply the fork's rules cannot meaningfully activate it either.
Unit level: the bridge gate rejects valid unstakes pre-fork (without touching aux data — the lockstep property that keeps the aux panic unreachable), flips exactly at the activation height, and the worker's discovery activates the fork an upgrade names (recording the enacted VK alongside it), skips upgrades naming an already-active fork (an authoring flaw that must not retro-raise the activation), skips unknown fork ids, resumes persisted activations on restart, and rolls back on rebase. Integration level: the full choreography against regtest — genuine musig2-signed unstake ignored pre-fork, admin VK upgrade enactment activates the fork at H+1, unstake then removes the operator; plus the reorg path where abandoning the submission block rolls the activation back until the re-mined update re-enacts on the new branch. The harness gains a fork schedule knob, a genuine-unstake builder (real N/N key, full validation passes — only the fork gate decides), and a reorg helper.
prajwolrg
force-pushed
the
upgradability-params-spec
branch
from
July 8, 2026 10:14
3b5a0f0 to
32bd1d3
Compare
prajwolrg
force-pushed
the
upgradability-fork-activation
branch
from
July 8, 2026 10:14
8d64daf to
d867438
Compare
prajwolrg
requested review from
bewakes and
evgenyzdanovich
and removed request for
evgenyzdanovich
July 8, 2026 10:29
Scanning a block's logs for enacted upgrades and persisting the resulting activations are different concerns. Keep discovery a pure read that returns the activations to enact, and move the write side (activation record + in-memory schedule update) behind apply_fork_activations, separating validation from persistence.
Warn-and-skip left the situation ambiguous: an enacted update naming an already-active fork is a flawed upgrade with no safe reading — applying it would retro-raise the activation height, ignoring it silently diverges from the predicate the chain enacted. Refuse to commit the enacting block instead, via a dedicated RedundantForkActivation error. The check also catches two updates naming one fork within a single block, which the schedule alone cannot see: the first takes effect at H+1, above the enacting height H, and checking at H+1 instead would false-fire on crash-replay. Drop discovery-local error logs along the way: both variants carry the full context in their Display, and the sync shutdown handler already logs every fatal sync error.
14 tasks
prajwolrg
force-pushed
the
upgradability-params-spec
branch
from
July 17, 2026 12:12
0e5ad8a to
0683d28
Compare
14 tasks
14 tasks
Collaborator
Author
|
Superseded by #204, which reworks this on top of the spec-versioning model from #202 (base: |
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.
Description
Stacked on #187, which defined the fork schedule/params types but left them unconsumed. This PR is the first consumer: it puts the worker through a real fork boundary, using unstake as the example feature. The fork keeps its
ForkId::Fork1placeholder name — the bridge gates unstake txs on it, the adminAsmStfVkUpdateaction carries the raw id of the fork the new proving artifact implements (rendered into the signing message, so the multisig authorizes the fork id alongside the predicate hash), the emittedAsmStfUpdatelog forwards it, and the worker activates the named fork from the next block on, persisting the activation. The chain is the single authority on which fork an upgrade activates: there is no per-operator config to distribute, so a worker cannot silently diverge from the network by missing or misconfiguring one.The fork id travels as a raw u16 rather than the
ForkIdenum because the upgrade that activates a fork is enacted by the artifact that predates it — the wire format cannot require knowing the fork. The worker maps ids it knows; an update naming an already-active fork is a logic-neutral VK rotation, and one naming an unknown id is skipped loudly (a binary that cannot apply the fork's rules cannot meaningfully activate it either).The subtle part is crash/reorg safety, which is carried entirely by ordering: the activation record is written before the enacting block's anchor commit (a committed anchor can never lack its activation), and every sync rebase prunes activations above the base before re-processing (a reorged-out enactment cannot leak into the new branch — its blocks re-discover whatever survives). The activation height is derived, not stored: it is always the block after the enacting one, so
ForkActivationrecords only(enacting_height, fork).Deliberately out of scope: the prover side (switching proving artifacts across VK upgrades) stays in #183. At the worker level nothing verifies proofs; the new predicate is carried opaquely.
Default behavior is unchanged: unstake remains active since genesis in production params, so a VK update naming
Fork1there acts as a rotation.Type of Change
Notes to Reviewers
Suggested entry point: the fork id on the action in
crates/subprotocols/admin/txs/src/actions/updates/asm_stf_vk.rs(and what it renders for signers), then the activation discovery and rollback incrates/worker/src/state.rs, then the bridge gate incrates/subprotocols/bridge-v1/subprotocol/src/handler.rs— note the gate decides identically in both tx-processing phases, so pre-fork the aux request is never made and aux request/consumption stay in lockstep.Breaking surface:
AsmStfVkUpdateand theAsmStfUpdatelog gain a mandatory raw fork id, changing their SSZ layout and the rendered signing message (nothing deployed consumes the old layout).Tested at two levels, no prover involved: unit tests for the gate boundary and the worker's discovery (activation of the named fork, already-active forks treated as logic-neutral rotations, unknown fork ids skipped, restart resume, rebase rollback), and a worker-level regtest integration test driving the full choreography — genuine musig2-signed unstake ignored pre-fork, admin VK upgrade enactment activates the fork at H+1, the same unstake then removes the operator — plus the reorg path where abandoning the submission block rolls the activation back until the new branch re-enacts it.
Checklist
Related Issues
Part of #183 (fork-based ASM upgradeability), stacked on #187.