Upgrades and readiness
A rule that enters a state_root cannot be rolled out node by node. It arrives at a height everybody agreed on beforehand — and this is how a running chain agrees on one.
Why it cannot just be deployed
A node still running the old rule computes a different state_root, rejects every block the upgraded majority is producing, and stops — with logs full of state root does not match, which is also what a corrupt database and a hostile proposer look like. So the change is not something to deploy. It is something to schedule.
A chain has a rule version: one number, 1 at genesis, moved by a schedule of {version, height} entries. The version is derived from the height and the schedule and is never carried in a header — a proposer that could state it could state a different one. A binary that does not implement the version a height calls for refuses to execute the block and says so.
Publishing what your binary implements
The schedule starts in the genesis file, and it grows. What makes it grow is not a vote: a ballot cannot make code exist, and is this change good is a judgement the chain has no way to hold. What it can hold is the fact underneath — can everybody run it — so a member publishes one number, the highest rule version their node implements.
./baklava ready -keystore node.jsonOr /ready from the console. There is no version to type, and there will not be one: what is published is what the binary can actually execute, because a number somebody typed would be a claim about a machine rather than a fact about it.
Say it from the binary that is really running.
Nothing checks the claim, and nothing can — whether a node can execute version 7 is a fact about a machine on the other side of the world. It is answered by the activation height instead: a member who published a version they do not have stops there along with everybody else who cannot run it. What they said stays in the registry for anybody to read back.
A number is not an implementation
There is a second hole underneath that one, and it is worse. A member who publishes a version they do not have stops at the activation height — handled. But what if two people each write “version 5” and mean different things? Both binaries publish 5. Both are counted. The threshold is crossed, the activation lands, and at that height they compute different state roots: a fork, produced by the machinery built to prevent one.
So from rule version 4 a signal carries a digest beside the version — what that version actually does, in this binary, reduced to one hash. It is not typed in by whoever writes the release; it is computed by running the rules: a fixed genesis and eight fixed blocks through the whole block path — transitions, the fee pool, the reward split, the reputation update, the epoch close — hashed into a transcript of every block hash and state root.
A hand-written constant would not have worked. Somebody who forks the repository, changes a rule and forgets to change the constant publishes the same number while running different code, and that honest mistake is exactly what this is for. A computed digest cannot be forgotten.
It does not stop a liar. It stops a fork.
The digest is public and anybody can copy it into a signal from a binary that does something else. Nothing here could prevent that — a chain cannot inspect a machine.
What changes is the shape of the failure. Two camps publishing different digests are two claims, not one. Neither is ninety percent of anything, so the threshold is not crossed and the schedule does not grow. A disagreement nobody noticed becomes no upgrade instead of a fork at a height everybody agreed on. It fails by not moving.
One consequence is worth stating because it changed an existing rule. Support used to accumulate downwards — a member who published 6 was counted as supporting 5, since anybody who can run 6 can run 5. That is the same unverified guess the digest exists to close: they never said what their version 5 behaviour would be. So a claim now counts toward the version it names and nowhere else, and a set split across two releases waits rather than activating the lower one.
How a height gets fixed
At every epoch close the chain counts the frozen set — the members who actually produce blocks for that epoch. The highest version that ninety percent of the set is behind gets appended to the schedule, activating four epochs later.
| Ninety, not two thirds | Everybody who did not signal goes dark at the activation height — that is the mechanism working. But those members were part of the committee, and the ones still running have to reach ⌊2·C/3⌋+1 without them. A threshold at the quorum leaves the chain exactly at the edge on the day it upgrades. |
|---|---|
| Of two things | Ninety percent of the set's weight and of its members. A committee is filled by two draws and only one is weighted: the apprenticeship quota is flat, so somebody who registered moments ago holds a seat while their weight is effectively nothing. Counted by weight alone, a set could be nine tenths newcomers who have said nothing and read as unanimous. |
| Four epochs, not two weeks | There are no empty blocks, so a height is not a clock — four epochs is hours on a busy chain and weeks on a quiet one. It errs in the safe direction, and it is not the only warning: the count is public and climbs in view. |
| Append only | Nothing edits the schedule and nothing removes from it. That a version arrives at a height agreed in advance is the whole mechanism, and an activation that could be withdrawn would not be an agreement. |
Where the chain stands
Read from the node as you look at it, because the schedule is not a constant this site can compile in.
Asking the node…
./baklava upgradesbaklava-testnet-5 has moved itself.
Its genesis schedules version 5 at height 1, which is the earliest an activation can sit — height 0 is refused, because the genesis block is what version 1 means. From that height the readiness signal exists, so the schedule can grow.
And it has. The set signalled version 6, the threshold was crossed at the close of epoch 0, and the chain appended {version: 6, height: 2500} to its own schedule — a height every node knew four epochs in advance. It arrived there and activated the admission cap, the reliability-weighted draw, the treasury spend and the lock. No new genesis file, no restart, nothing to coordinate on the day.
What that cost is worth knowing, because it is the one thing about this mechanism that surprises people: there are no empty blocks, so four epochs is a distance in blocks and an idle chain never covers it. Two thousand five hundred transactions had to be sent — about two and a half hours of somebody driving the chain — between the signal and the rule.
baklava-testnet-2 could not do this and never will: it started with an empty schedule, and the only thing that can append to one is the mechanism described above — which is itself a version 2 rule. A chain with no way to reach version 2 has no way to reach anything. That is the whole reason this network replaced it.
The reasoning, in full, is under Upgrades in the protocol document — including what this deliberately is not, which is governance.