18. Releases and hardforks
Makechain changes many times over its life, from a price-table edit to a signing-scheme switch. This chapter defines the one artifact that carries such a change, the schedule that decides when it takes effect, and the single place from which a node selects the rules of a height. Nothing is ever migrated and an activated release is never reverted. The practice around a release, its rehearsals, its compatibility procedures, and its assurance list, is in the release process and imposes no requirement here.
18.1 Vocabulary
Ruleset. The complete set of validity, execution, pricing, encoding, and proof rules a node
applies to a block. Rulesets are numbered by a small monotone ruleset_id starting at the genesis
ruleset, and ruleset_id is the value rule dispatch selects on (section 18.5).
Release. A governance artifact that names a ruleset and says when it starts:
ReleaseDescriptor {
name, # CalVer generation of the client that first ships the ruleset, e.g. 2027.3
manifest,
predecessor_digest,
activation_height,
parameter_deltas, # governed parameters this release changes, section 18.4
installer?, # optional one-time activation work with bounded inputs, section 18.6
notes_digest # binds the MIP text
}
Manifest {
ruleset_id,
class, # parameter | format-preserving | versioned-state | reset
consensus_critical: bool,
surfaces: [SurfaceId]
}
release_digest = BLAKE3("makechain.release" || canonical(ReleaseDescriptor))
The manifest declares what the ruleset changes, using surface identifiers drawn from a fixed
enumeration in the chainspec format: command validity, execution, pricing, encoding, proof format,
and each consensus-critical surface of section 18.2. Because the manifest is structural, a binary
that cannot run a ruleset is still able to validate a descriptor that names it. The header field
active_release_digest names the release in force at its height (section 11). Field order, tags,
and the closed governance witness and payload forms are fixed by encoding.
Hardfork. Any release whose ruleset differs from its predecessor's. Every release except a pure parameter change is a hardfork, and the word carries no extra requirement. There is no soft fork: a Makechain rule change is flag-day for every validator (section 18.8).
Identity replacement. Establishing a new threshold group key through a fresh genesis, using
glue::dkg::bootstrap for DynamicReshareV1 or a new trusted fixture for FixedEpochZeroV1
(section 13). It is a reset-class ingredient and never a synonym for a hardfork.
Node-local storage upgrade. A change to how a node stores data on disk with roots, encodings, and rules unchanged. It is not a release (section 18.10).
18.2 Release classes and surfaces
The class is part of the descriptor and is checked, not advisory.
| Class | May change | Never changes | Activation |
|---|---|---|---|
| parameter | governed chainspec parameters (price table, limits, work weights, admission rates, settlement pause flags) | code, roots, encodings | any height |
| format-preserving | validity and execution rules, command semantics, new commands | roots of existing data, encodings, storage formats | any height, or the first block of an epoch if consensus_critical |
| versioned-state | adds record families or value versions written only from activation; old values read as before | existing records, existing root computations | any height, or the first block of an epoch if consensus_critical |
| reset | anything, including root computations, canonical encodings, the group key, and chain_id |
(a reset is a new chain) | genesis of the new chain |
A consensus-critical surface is one that all validators must switch at one instant for consensus
to keep working, as opposed to block validity alone. Each is a named surface in the manifest
enumeration: signing or handshake namespaces, RandomVersion, TermLength, timeouts, DKG sharing
or reveal modes, Transcript version, committee bounds, marshal variant, and the p2p wire codec.
R-18.1. A descriptor MUST set consensus_critical when its release touches any consensus-critical
surface.
R-18.2. A consensus-critical surface is schedulable on a continuing chain only once the engine handoff for that surface is qualified (section 18.7); until then a change to it is reset-class.
R-18.3. SCHEDULE_RELEASE (section 8) is invalid unless its manifest satisfies all of:
a. the declared class admits every declared surface;
b. consensus_critical is set exactly when a consensus-critical surface is declared;
c. the ruleset identity conditions of section 18.3 hold;
d. a ruleset_id already assigned is not redefined with a different manifest.
R-18.4. A change that would need a state-size-dependent migration is reset-class; where existing records must take a new shape, the versioned-state class covers it, with owners rewriting their own records through ordinary commands under the new rules and no protocol-triggered bulk rewrite.
Every validator runs R-18.3 under the current rules, and that structural check is the whole of the
class check inside SCHEDULE_RELEASE validity. Two further obligations on a release are
qualification gates rather than rules a validator applies, because a binary that lacks the ruleset
can check neither: that the shipped code matches its manifest, and that a release stays within its
class, meaning a versioned-state release that changes no existing root and a format-preserving
release that alters no encoding. Understanding a schedule entry and executing its ruleset are
distinct, and only the second requires the new binary. Both gates are recorded in
the release process and validation.
18.3 The release schedule
R-18.5. Governance uses dedicated Ed25519 keys, never reused for any other role.
R-18.6. The governance key set and its threshold are chain state, and a release MAY rotate them.
R-18.7. A schedule entry names the key-set revision that authorized it and carries a predecessor digest and a governance nonce.
R-18.8. A pending entry is invalidated by a rotation that removes its signers.
R-18.9. Governance signs raw Ed25519 over BLAKE3("makechain.governance" || intent_digest),
over the canonical governance intent and its revision and nonce.
R-18.10. Approval indices strictly increase in the current key set; an ordinary operation carries exactly the threshold count of approvals and no trailing approvals, and an emergency activation carries every current key exactly once.
SCHEDULE_RELEASE appends to an authenticated schedule with four entry kinds. h is the height at
which the entry executes. An entry is effective if it has been neither cancelled nor activated.
| Entry kind | Effect | Valid if |
|---|---|---|
ACTIVATE(descriptor) |
appends a pending release | at most one effective unactivated entry results; activation_height >= h + min_activation_delay_blocks; the descriptor's predecessor is the current tail; a parameter release keeps the predecessor's ruleset_id and every other class uses the predecessor's plus one; if consensus_critical, activation_height is the first block of an epoch at least two epochs (2 * blocks_per_epoch heights) after h |
DEFER(new_activation_height) |
moves the pending entry later | the new height is later than the current one, under no new minimum delay; the entry is not locked; a deferred consensus_critical entry again names the first block of an epoch and is revalidated against the schedule at its new height |
CANCEL |
vacates the pending slot immediately | h is before the pending entry's activation height; the entry is not locked |
EMERGENCY_ACTIVATE(descriptor) |
appends a pending release under the reduced delay | no effective unactivated entry is pending; the descriptor's predecessor is the current tail; the descriptor's class is format-preserving or parameter; consensus_critical is unset; every key in the governance set has signed; activation_height >= h + min_emergency_delay_blocks |
R-18.11. A deferral changes the digest of the pending release and is recorded as an entry.
R-18.12. A cancelled entry never becomes the active release.
R-18.13. CANCEL vacates the pending slot immediately, so an ACTIVATE later in the same
block, in flat command order, satisfies the one-effective-entry condition.
R-18.14. EMERGENCY_ACTIVATE is the only path with a reduced delay.
R-18.15. An EMERGENCY_ACTIVATE is invalid while a locked consensus_critical entry is
pending, because that entry is effective and unactivated and can be neither cancelled nor deferred.
R-18.16. A consensus_critical entry with activation at the first block of epoch E is
locked once the final block of E − 2 has embedded it, which is the same block that locks
next_players for E (section 13); entries of the other classes have no lock and remain
cancellable until activation.
R-18.17. A consensus_critical entry that changes committee bounds is valid only if every
locked validator-set entry and the current effective set satisfy the new bounds, and a validator-set
entry scheduled after it is validated against the pending bounds.
R-18.18. The schedule is strictly increasing in activation height, and every validator performs this check on apply rather than as a debug-only assertion.
R-18.19. The rules at a height are derived from the authenticated parent state's schedule and
never from local configuration, where active_release(h) is the latest entry with
activation_height <= h.
R-18.20. A fresh node verifies the schedule from the latest release checkpoint it trusts rather than from every historical release, and a paged release checkpoint carries the complete schedule state, including the tail release digest and the pending entry, so that a verifier can enforce predecessor linkage across pages and verification stays bounded regardless of chain age.
INV-GOV-01. A release activates only through the finalized schedule, never by proposer or operator choice.
INV-GOV-08. A
consensus_criticalrelease activates only at the first block of an epoch scheduled at least two epochs ahead; an emergency release is neverconsensus_critical.
INV-GOV-10. An activated release is never reverted; a pending one is only ever cancelled or deferred later, and a locked
consensus_criticalentry is neither.
18.4 Governed parameters
R-18.21. parameter_deltas names only governed parameters, which are:
a. the price table; b. limits and ceilings; c. work weights; d. admission rates; e. pause flags; f. the root WebAuthn origin allowlist (section 7); g. the set-once configured-genesis factory address (section 7); h. the settlement values marked governed in section 16; i. the exporter roster, the non-voting mesh peers that publish history (section 17).
R-18.22. The genesis chainspec bytes are immutable, because they feed chain_id (section 5); a
governed parameter's genesis value is its initial effective value, and its effective value at a
height is the genesis value folded with every activated delta.
R-18.23. The bounded effective snapshot is held in ProtocolScheduleState, which holds the
username-claim and reclaim pause bits and references the sole authoritative imports_paused bit in
SettlementAnchor (section 16).
R-18.24. Values with no governed path, namely epoch length, the genesis committee, the genesis release, the format identifiers of the canonical artifacts, and the merkle families and chunk sizes, change only by reset.
R-18.25. A parameter delta takes effect at the activation height and is part of the descriptor's digest.
18.5 Rule dispatch
R-18.26. At the start of proposing, verifying, or applying a block at height h, the
application computes rules = rules_for(active_release(h).ruleset_id, effective_parameters(h)) from
the executing parent's state, once.
R-18.27. Resolution selects the due release, reads the bounded parameter snapshot and its authenticated settlement anchor, then applies due deltas and marker-guarded installer effects to a speculative overlay using the selected ruleset; those overlay effects and the installer marker join the same validity-state batch.
R-18.28. The resulting Rules combines the activated snapshot and anchor, so that replacement
anchors and all pause flags govern commands in the first activation block, and two parameter
releases on one ruleset are distinguishable.
R-18.29. A handler receives these resolved values and does not read configuration independently:
every validity check, handler, price lookup, limit, encoding choice, proof format, and event
projection is a method on rules or takes rules as an argument.
R-18.30. No handler tests a height, a release name, or a digest, and the only comparison that
selects a release is active_release(h); other height guards, such as schedule delays and witness
anchors, are ordinary rules.
R-18.31. Old and new behaviour live side by side as branches selected by ruleset_id, in the
same function, so that replaying a historical block reproduces its historical rules exactly.
R-18.32. A command that a ruleset does not know is invalid in that ruleset and is not an error class of its own.
R-18.33. Every ruleset the chain has ever activated stays in the binary for the life of the chain, because the replay job replays stored blocks from genesis and validators and readers replay from their floor; dropping a ruleset is a reset.
R-18.34. The consensus and storage layers, namely Simplex, marshal, glue, and the database set,
order, finalize, and persist opaque blocks and never see ruleset_id, except through the
consensus-critical surfaces of section 18.7.
INV-GOV-07. The rules applied at height h are a pure function of
chain_id, the schedule, and the effective parameters in the executing parent's state; no rule consults local configuration, wall-clock time, or the chain tip, and the only comparison that selects a release isactive_release(h).
18.6 Activation installers
An activation installer is one-time work named in a descriptor with its bounded inputs, such as seeding a new record family, writing a settlement anchor, or marking a reserved key. A release MAY carry one.
R-18.35. An installer belongs to its release and never to the ruleset, so a later parameter release on the same ruleset inherits nothing.
R-18.36. The installer runs as the first step of executing every block at or after
activation_height until the marker record release_applied/{release_digest} exists, and never
again after that.
R-18.37. The marker and the installer's writes are one validity-state batch.
R-18.38. An installer is bounded by a constant independent of state size, its writes are ordinary state operations under the new rules that produce a new current root, and it is exactly-once in finalized history.
R-18.39. An installer MUST be deterministic and free of side effects outside its batch, because it may execute more than once across discarded speculative proposals.
R-18.40. Installers use a closed release-qualified handler registry with an exact input schema, declared destination families, and maximum reads and writes; arbitrary family or value writes are forbidden, and a new-family initializer requires a newly reviewed handler and a versioned-state release.
R-18.41. No launch installer creates or modifies a monetary ledger, a deposited user record, or a replay mark.
Because the marker and the writes commit together, a block that carries the effects carries the marker, a sync target produced after activation contains both, and a restart inside the activation block replays or reproduces both. Installer work, including marker creation, is reserved before commands and counted in the same block limits (section 10).
INV-GOV-09. Activation work is deterministic, marker-guarded, bounded by a constant independent of state size, and committed with its marker in one batch; no release of any class other than reset reinterprets historical bytes, changes an existing root computation, or performs a state-size-dependent migration. Bounded installer writes are ordinary transitions and produce a new current root like any block.
18.7 Consensus-critical surfaces and the engine handoff
The library offers no schedule seam for these switches: the orchestrator clones one elector and timeout configuration into every epoch's engine, the reshare actor takes its namespace, modes, and participant bounds once at construction, and marshal requires a scheme provider covering every epoch it may still sync.
R-18.42. Makechain applies a consensus-critical switch by an engine handoff at the epoch
boundary, the final block of E − 1, so that the release governs from the first block of E: the
node reads the locked entry from state, stops the engine at the final block of E − 1, and
reassembles it for epoch E from the public assembly API with the new configuration.
R-18.43. A handoff preserves the vote journals, the DKG custody material, and a scheme provider that keeps serving the pre-boundary scheme for every retained epoch.
R-18.44. Each consensus-critical surface has its own handoff, and a surface whose handoff is not qualified MUST NOT be scheduled on a continuing chain (R-18.2).
Section 19 records which surfaces have a qualified handoff.
18.8 Compatibility and UnsupportedRelease
R-18.45. There is no compatibility window: at activation_height every node runs the new rules
or has stopped.
R-18.46. A binary supports a contiguous range of rulesets and refuses to start on a chain whose finalized schedule already has an activated ruleset outside that range.
R-18.47. A node whose binary does not support active_release(h).ruleset_id stops before
proposing or voting at h.
R-18.48. The p2p handshake carries chain_id and the node's supported ruleset range.
R-18.49. A node refuses a peer whose supported range excludes the currently active ruleset, and warns about a peer whose range excludes a pending one.
An un-upgraded validator is therefore visible to its peers before activation rather than silent after it.
R-18.50. Consensus, DKG, and handshake namespaces are chain-bound and do not change across
releases; a release that must change one is consensus_critical.
UnsupportedRelease is the answer given when the ruleset needed to serve a request lies outside the
range the answering node or verifier supports. It is a service outcome and never an authenticated
absence, an invalidity verdict, or a decoded value.
R-18.51. Admission SHOULD answer UnsupportedRelease to a command whose ruleset_id will not
be active at the earliest height the command can be included, which is local policy and never
changes validity (sections 10 and 12).
R-18.52. The proof service serving a historical proof, an exporter serving a current or
within-window proof, and the reference verifier each answer UnsupportedRelease, never a decoded
absence, when asked for work under a ruleset outside the range they support (section 15).
Client and gateway introspection, query-tier and event versioning, and the operator-facing report of a pending unsupported release are practice, not validity, and are in the release process and operations.
INV-GOV-11. Every node role supports every ruleset from the chain's genesis release to the active one in a single binary; a binary that cannot support the active ruleset does not propose or vote.
18.9 Profile restrictions
The four entry kinds of section 18.3 describe the continuing-chain schedule under
DynamicReshareV1. The engine handoff of section 18.7 applies only there, because
FixedEpochZeroV1 rejects the schedule operation that would require one. Under FixedEpochZeroV1
the following outcomes are fixed, each with the scope stated:
Operation under FixedEpochZeroV1 |
Outcome |
|---|---|
ACTIVATE whose descriptor sets consensus_critical or declares any consensus-critical surface |
deterministically invalid |
DEFER or CANCEL naming a consensus-critical pending entry |
deterministically invalid; such an entry is unreachable from valid fixed-profile genesis state |
EMERGENCY_ACTIVATE carrying consensus_critical |
deterministically invalid, under the structural ban that holds on every profile |
ACTIVATE, DEFER, or CANCEL of a non-critical parameter, format-preserving, or versioned-state release |
ordinary height and cancellation rules of section 18.3 |
The validator-schedule outcomes under this profile are stated in section 13.
R-18.53. A reset-class change, including every change that section 5 makes reset-class and a change to canonical encoding or chain identity (section 18.2), cannot be represented as a non-critical manifest.
R-18.54. Admission and execution return the ordinary deterministic invalidity result for every disabled variant; there is no successful no-op and no profile-specific validity class.
INV-GOV-12.
FixedEpochZeroV1rejects every validator-schedule command and every release-schedule operation that would create, move, or cancel a consensus-critical pending entry; replacing its committee, threshold output, or profile requires a reset.
18.10 Storage and dependency classification
| Change | Classification |
|---|---|
| a client version's on-disk layout, with roots and encodings unchanged | node-local storage upgrade, not a release |
| a dependency bump that changes any root computation or canonical encoding | reset |
| a dependency bump that changes crash recovery or an internal format only | node-local storage upgrade |
| a dependency bump that changes a consensus-critical switch | consensus_critical format-preserving release |
| a dependency bump confined to non-consensus crates | ordinary dependency change |
R-18.55. A dependency bump is classified by what it changes, not by which crate it touches.
R-18.56. No storage upgrade rewrites a live database in place.
R-18.57. The versioned consensus switches RandomVersion, the DKG Reveal mode, and the
Transcript version are always release-activated and never silent defaults, while BlobLayout and
Makechain's own BlobVersion are node-local container layouts that follow the storage-upgrade
procedure rather than a release.
The upgrade procedure itself, and the presumption list that decides which bumps are read as releases, are in the release process.
18.11 Emergency posture
Before activation, a pending entry can be cancelled or deferred under the rules of section 18.3,
which exempt a locked consensus_critical entry (R-18.16).
R-18.58. There is no rollback after activation: blocks finalized under the new rules are final,
history under them is replayed under them forever, and remediation is a successor release, through
EMERGENCY_ACTIVATE where it qualifies and otherwise through the ordinary path.
The governed parameters include pause flags for the command families whose failure is survivable.
| Governed flag | Effect when set |
|---|---|
imports_paused |
rejects both IMPORT_CREDITS variants |
username_claims_paused |
rejects all USERNAME_CLAIM variants, including extension and restoration |
reclaims_paused |
rejects RECLAIM |
R-18.59. username_claims_paused and reclaims_paused both default to false at genesis, no
command outside the table is disabled by these flags, and consensus itself is never pausable by
parameter.
The genesis value of imports_paused is the pause bit supplied with the genesis settlement anchor
(section 16).
R-18.60. If validators disagree on a block's validity because a ruleset has a bug, the chain halts when fewer than a quorum agree, and the block finalizes while the dissenting nodes stop when a quorum agrees, because Simplex checks quorum and not unanimity.
R-18.61. Neither a halt nor automatic detection of such a divergence is guaranteed.
R-18.62. Recovery from such a divergence is a reset with an identity replacement (section 13) under the settlement anchor rules of section 16, and never a coordinated re-execution of finalized history.
18.12 Rationale
This subsection is informative.
Activation is keyed by height rather than by timestamp because heights are the authenticated axis and chain time is parent-relative. Rules are dispatched from one place and every ruleset is retained so that history replays under the rules it was finalized under and no scattered height check can drift. Installers are marker-guarded and run on every block until applied rather than keyed to a first-block flag, because a first-block flag breaks under replay, state sync, and discarded speculative proposals. No class migrates state because a state-size-dependent migration has no bounded cost; versioned defaults at creation and owner-driven per-record rewrites cover the observed need. Consensus-critical switches are locked two epochs early and applied by an engine handoff because the library configures each epoch's engine from one fixed configuration and offers no schedule seam. The descriptor carries a structural manifest so that scheduling does not split consensus before activation. There is no rollback because blocks finalized under new rules are final, and governed pause flags give a code-free response while a successor is built.
Lookup material.