Consensus and finality
An app block is final when a threshold of members has signed COMMIT for it, after each of them re-executed the block and got the same state root. A final block is never rolled back. The rest of this page explains how Yano gets there.
You’ll learn: the two-phase round, what a member checks before it votes, how a failed leader is replaced, how to choose a threshold, and what catch-up and restart guarantee.
Before you start: read What is an app ledger?, especially “Life of a message”.
| Term | Meaning |
|---|---|
| Member | A node identified by an Ed25519 key that checks and votes on every block. A ledger has at most 32 members. |
Threshold t | How many member signatures each step of the round needs. |
| View | One attempt to finalize a height. Every height starts at view 0; a failed attempt moves to the next view. |
| Leader | The member that proposes the block for a height and view. |
| PREPARE, COMMIT | The two signed votes of a round. |
| PreparedQC | A threshold of PREPARE votes for one block. |
| Finality certificate | A threshold of COMMIT signatures. It makes the block final. |
The round
Section titled “The round”Yano runs a two-phase protocol, described in Yano’s ADR-036. Step through one round on four members with a threshold of 3, then try the “What if” scenarios.
What you're seeing: four members, any 3 of which make a block final. Members are numbered in their sorted key order. Pick a step, press Play, or try a “What if”.
- Propose Member 0 leads view 0 at height h. It selects pending messages, applies the block itself to compute the state root, and persists a prepare lock for (height, view, block hash). Then it sends the block and its own PREPARE vote to the other members.
- Check Each follower runs these checks, in this order, before it votes. The last one is the heart of the system: the follower applies the block itself and requires the same state root, byte for byte. State is never trusted, always recomputed.
- passes: The proposal fits the block byte budget and decodes as a block
- passes: Its height is one above the local tip (a later block waits)
- passes: The envelope is signed by the block’s proposer
- passes: The proposer is the leader for this height and view
- passes:
prevHashequals the local tip hash - passes:
messagesRootrecomputes from the messages - passes:
consensusContextDigestequals this member’s own context - passes: A higher view carries a valid new-view certificate and any prepared value
- passes: The L1 reference matches this member’s own L1 view
- passes: Every message is valid, unexpired, and signed by a member at this height
- passes: Effect results come from the designated signer; L1 observations re-derive
- passes: Per-sender sequence numbers increase
- passes: No conflicting prepare lock
- passes: Re-executing the block gives a byte-identical
stateRoot
- Prepare Each follower persists its own prepare lock, then signs and sends PREPARE. A member never prepares two different blocks in one view, and a lock survives a restart.
- PreparedQC Any member that holds 3 PREPAREs, the threshold, forms a PreparedQC: signed proof that a quorum accepted this exact block. It persists the QC with the block and sends it on. The QC must survive any later view change.
- Commit Holding a PreparedQC, each member signs COMMIT for the same block and sends it to the others.
- Final Any member that holds 3 COMMITs assembles the finality certificate, writes the block, its certificate, and the new state in one atomic batch, and sends the certificate on. Each receiver checks every signature itself. The block is APP_FINAL; there is no rollback below finality.
What if: The leader is offline
Signed timeouts move the height to view 1, and the next member in sorted order leads.
- No proposal Member 0, the leader for view 0, is offline. Nothing arrives, and the round clock runs. By default the round timeout is 5 block intervals or 10 seconds, whichever is longer, and it doubles with each higher view, up to a limit.
- Time out Members 1, 2, and 3 each sign a TIMEOUT for view 1. A TIMEOUT carries the highest PreparedQC its signer holds for this height, if any. A timeout never deletes a prepare lock.
- New view Three signed TIMEOUTs, the threshold, form a new-view certificate. Every member that holds it moves to view 1. The leader of view 1 is the next member in sorted order: Member 1.
- Propose in view 1 Member 1 proposes with the new-view certificate attached. If any TIMEOUT carried a PreparedQC, Member 1 must re-propose the block contents that the highest one certified; otherwise it builds a fresh block. Followers check this rule.
- Final without Member 0 Members 1, 2, and 3 are exactly the threshold. They prepare, form a PreparedQC, commit, and finalize block h. If one more member were offline, no block could become final. Each new height starts at view 0 again; with a fixed sequencer, Member 0 leads view 0, so every block waits for a timeout until it returns.
- Member 0 catches up When Member 0 returns, it asks a peer for the blocks it missed and verifies each one: hash links, messages root, leader, every message signature, the full finality certificate, and a re-executed state root. Then it applies them.
What if: A member computes a different root
One member’s state machine is not deterministic. It refuses to vote and stalls alone.
- Propose Member 0 leads view 0 at height h. It selects pending messages, applies the block itself to compute the state root, and persists a prepare lock for (height, view, block hash). Then it sends the block and its own PREPARE vote to the other members.
- Check fails on Member 3 Member 3 runs with a Turkish default locale, and the state machine lowercases text without
Locale.ROOT. Its re-executed root differs, so it does not vote. A member never signs a root it did not compute itself.- passes: The proposal fits the block byte budget and decodes as a block
- passes: Its height is one above the local tip (a later block waits)
- passes: The envelope is signed by the block’s proposer
- passes: The proposer is the leader for this height and view
- passes:
prevHashequals the local tip hash - passes:
messagesRootrecomputes from the messages - passes:
consensusContextDigestequals this member’s own context - passes: A higher view carries a valid new-view certificate and any prepared value
- passes: The L1 reference matches this member’s own L1 view
- passes: Every message is valid, unexpired, and signed by a member at this height
- passes: Effect results come from the designated signer; L1 observations re-derive
- passes: Per-sender sequence numbers increase
- passes: No conflicting prepare lock
- fails: Re-executing the block gives a byte-identical
stateRoot
- The quorum finalizes Members 0, 1, and 2 are the threshold. They prepare, commit, and finalize block h with root
7f3a…. - Member 3 stalls Member 3 tries to catch up, but re-executing certified block h still gives a different root, so it never applies it. After a minute without progress its status reports
stalled. The others continue, but one more failure would now stop finality.
- leader for the view
- member
- final
- offline, refused, or stalled
In short:
- Propose. The leader selects pending messages, applies the block itself, persists a prepare lock, and sends the block with its PREPARE vote.
- Check. Each follower checks the block and re-executes it. It votes only if its own state root matches byte for byte.
- Prepare. A threshold of PREPARE votes forms a PreparedQC. Any member that collects them can form it.
- Commit. Holding a PreparedQC, members sign COMMIT.
- Final. Any member that collects a threshold of COMMITs assembles the finality certificate, commits the block atomically, and shares the certificate. Every receiver verifies each signature itself.
The client does not sign anything in this round. The member that accepted a message signs its envelope with its member key; a person’s approval, when a workflow needs one, is a signature inside the message body.
Fail closed
Section titled “Fail closed”A member signs only a root it computed itself, and it never prepares two different blocks in one view. Every member checks, always:
| Check | If it fails |
|---|---|
| The block’s proposer signed the proposal and is the leader for this height and view | No vote. |
prevHash links to the local tip; messagesRoot recomputes; the consensus context matches | No vote. |
| Every message is unexpired and signed by a member at this height | No vote for the whole block. |
Re-executing the block gives the same stateRoot | No vote. |
| A vote or certificate signature is from a member at that height, over this block | The vote is ignored. |
| A finality certificate has a threshold of valid, distinct member signatures | The whole certificate is rejected. |
If too few members can vote, no block becomes final. The ledger stops rather than accept a block a quorum did not compute.
Inside a block
Section titled “Inside a block”The block hash covers the header. Messages are bound through messagesRoot,
and only the finality certificate sits outside the hash, so it can be attached
after finality.
- Header · covered by the block hash: Format version, Chain id, Height, Consensus context, View, Previous block, L1 reference, Timestamp, Messages root, State root, Proposer, Justification.
- Body: Messages.
- Outside the hash: Finality certificate.
- Messages → Messages root (Merkle root of ids).
- Finality certificate → Header · covered by the block hash (signs the hash).
Select a field to see what it binds and which check uses it.
- Format version
- The block format version, currently 3. A follower rejects any other version before it checks anything else.
- Chain id
- The ledger’s
chain-id. A block for another chain is rejected. The chain id is also part of the consensus context that every vote signs. - Height
- Starts at 1. A follower votes only for its tip height plus one. A block from further ahead waits and is retried; a height that is already final is ignored.
- Consensus context
- A digest of the chain id, genesis id, height, sorted member keys,
n,t,f, and the consensus, observer, and observation profile digests. A follower requires it to equal the context it computes itself, and every PREPARE, COMMIT, and TIMEOUT signs it, so a vote cannot be reused for another chain, height, or membership. With a rotating sequencer it also seeds the view-0 leader, together with the previous block hash. - View
- 0 in a normal round, higher after a certified view change. The proposer must be the leader for this height and view.
- Previous block
- The hash of the previous block, or 32 zero bytes at height 1. It must equal the follower’s own tip hash, which chains every block to the history before it.
- L1 reference
- A stable Cardano slot and block hash. They are 0 and empty unless
l1.stability-depthis above 0. Then each follower checks them against its own view of Cardano, and slots must not decrease. - Timestamp
- The proposer’s clock in milliseconds when it built the block, identical for every member because it is in the block. It is the only time a state machine may use. Followers do not compare it with their own clocks, so it is only as accurate as the proposer’s.
- Messages root
- A binary blake2b-256 Merkle root over the ordered message ids. A follower recomputes it from the messages. A message inclusion proof checks a message id against it.
- State root
- The root of the authenticated state after applying this block. A follower re-executes the block and requires the same root, byte for byte. State proofs and Cardano anchors refer to it. State and proofs →
- Proposer
- The leader’s Ed25519 public key. It must equal the key that signed the proposal envelope, and the leader for this height and view.
- Justification
- Empty in view 0. In a higher view it holds the new-view certificate, a threshold of signed TIMEOUTs. The block hash covers its digest.
- Messages
- The ordered message envelopes, each signed by the member that accepted it. The block hash covers
messagesRoot, not the messages themselves. Every message must be unexpired and signed by a member at this height. - Finality certificate
- Ed25519 COMMIT signatures from a threshold of members. Each signs a digest of the height, view, context, block hash, and value hash. The certificate sits outside the block hash so it can be attached after finality. A verifier requires every signer to be a member at that height, no duplicates, and every signature valid: one bad entry voids the whole certificate.
messagesRoot; only the finality certificate is outside the hash.Leaders and view changes
Section titled “Leaders and view changes”Every member computes the same leader from committed data, so no coordination is needed:
leader = sortedMembers[(initial + view) mod n]| Sequencer mode | initial for view 0 | Use it when |
|---|---|---|
fixed | The configured sequencer.proposer. | A clear operational owner exists. |
rotating | Derived from the height’s consensus context and the parent block hash. | No member should hold the ordering role permanently. |
Rotation does not follow Cardano slots, so members agree on the leader even when their views of Cardano differ. When a round times out, members sign TIMEOUT votes for the next view. A threshold of them forms a new-view certificate, and the next member in sorted order leads. If any TIMEOUT carried a PreparedQC, the new leader must re-propose the contents of the highest one.
The round timeout defaults to five block intervals or 10 seconds, whichever is longer, and doubles with each higher view up to a limit. Each new height starts again at view 0. With a fixed sequencer, a failed proposer therefore costs one timeout per block until it returns; with a rotating sequencer, only the heights it would lead pay that cost. The mode affects who orders and how much time a failure costs, not safety.
Choosing a threshold
Section titled “Choosing a threshold”Three numbers describe a ledger’s fault model:
n: the number of members, at most 32;t: the threshold; andf: how many members may be Byzantine, that is, sign conflicting votes. It is the chain settingconsensus.max-byzantine-members, default 0.
A node refuses to start a chain unless all three rules hold:
| Rule | Why |
|---|---|
1 ≤ t ≤ n | The threshold is within the membership. |
2t − n > f | Any two quorums share at least one honest member, so two conflicting blocks cannot both be certified. |
t ≤ n − f | A quorum is still possible with f faulty members. |
With f = 0, the second rule means the threshold must be more than half the
members. The ledger keeps finalizing with up to n − t members offline.
What you're seeing: one threshold configuration per step, checked against the three rules a node enforces before it starts a chain. n is the member count, t the threshold, and f the setting consensus.max-byzantine-members (default 0).
- 2 of 3 The launcher default for three members, a majority. Any two quorums of 2 share a member, and the ledger keeps finalizing with one member offline.
- passes:
1 ≤ t ≤ n: the threshold is within the membership - passes:
2t − n > f: any two quorums share an honest member - passes:
t ≤ n − f: a quorum remains withfmembers faulty
Members nThreshold tFault bound fMay be offline 3 2 0 1 - passes:
- 1 of 3 Two members could each finalize a different block on their own, because two quorums of 1 need not share a member. The node refuses this configuration.
- passes:
1 ≤ t ≤ n: the threshold is within the membership - fails:
2t − n > f: any two quorums share an honest memberconsensus quorums do not intersect in an honest member - not reached:
t ≤ n − f: a quorum remains withfmembers faulty
Members nThreshold tFault bound fMay be offline 3 1 0 — the chain does not start - passes:
- 2 of 4 Half is not enough: members A and B could finalize one block while C and D finalize another. With
f = 0, the threshold must be more than half the members.- passes:
1 ≤ t ≤ n: the threshold is within the membership - fails:
2t − n > f: any two quorums share an honest memberconsensus quorums do not intersect in an honest member - not reached:
t ≤ n − f: a quorum remains withfmembers faulty
Members nThreshold tFault bound fMay be offline 4 2 0 — the chain does not start - passes:
- 3 of 4 The launcher default for four members. One member may be offline.
- passes:
1 ≤ t ≤ n: the threshold is within the membership - passes:
2t − n > f: any two quorums share an honest member - passes:
t ≤ n − f: a quorum remains withfmembers faulty
Members nThreshold tFault bound fMay be offline 4 3 0 1 - passes:
- 3 of 5 The launcher default for five members. Two members may be offline.
- passes:
1 ≤ t ≤ n: the threshold is within the membership - passes:
2t − n > f: any two quorums share an honest member - passes:
t ≤ n − f: a quorum remains withfmembers faulty
Members nThreshold tFault bound fMay be offline 5 3 0 2 - passes:
- 1 of 1 A single member. The host’s default threshold is 1, which passes only for one member; for more members, set the threshold.
- passes:
1 ≤ t ≤ n: the threshold is within the membership - passes:
2t − n > f: any two quorums share an honest member - passes:
t ≤ n − f: a quorum remains withfmembers faulty
Members nThreshold tFault bound fMay be offline 1 1 0 0 - passes:
What if: One Byzantine member (f = 1)
With f = 1, safety must hold even if one member signs conflicting votes.
- 3 of 4, f = 1 The classic
n = 3f + 1,t = 2f + 1profile. Any two quorums of 3 share two members, so at least one honest member is in both. One member may be offline.- passes:
1 ≤ t ≤ n: the threshold is within the membership - passes:
2t − n > f: any two quorums share an honest member - passes:
t ≤ n − f: a quorum remains withfmembers faulty
Members nThreshold tFault bound fMay be offline 4 3 1 1 - passes:
- 2 of 3, f = 1 Two quorums of 2 share only one member, and that member could be the dishonest one. A 2-of-3 ledger is crash-safe, but it does not tolerate a Byzantine member.
- passes:
1 ≤ t ≤ n: the threshold is within the membership - fails:
2t − n > f: any two quorums share an honest memberconsensus quorums do not intersect in an honest member - not reached:
t ≤ n − f: a quorum remains withfmembers faulty
Members nThreshold tFault bound fMay be offline 3 2 1 — the chain does not start - passes:
- 4 of 4, f = 1 Safe, but not live: if the one faulty member stops voting, only 3 remain, which is below the threshold.
- passes:
1 ≤ t ≤ n: the threshold is within the membership - passes:
2t − n > f: any two quorums share an honest member - fails:
t ≤ n − f: a quorum remains withfmembers faultyconsensus threshold cannot remain live under the configured fault bound
Members nThreshold tFault bound fMay be offline 4 4 1 — the chain does not start - passes:
- 4 of 5, f = 1 A stricter profile that also satisfies both rules. One member may be offline.
- passes:
1 ≤ t ≤ n: the threshold is within the membership - passes:
2t − n > f: any two quorums share an honest member - passes:
t ≤ n − f: a quorum remains withfmembers faulty
Members nThreshold tFault bound fMay be offline 5 4 1 1 - passes:
Defaults to know:
- The host’s default threshold is 1, which passes only for a single member. Set the threshold for any multi-member ledger.
- The cluster launcher defaults to a majority,
⌊n / 2⌋ + 1: 2 of 3, 3 of 4, 3 of 5. - A 2-of-3 ledger is crash-safe but does not tolerate a Byzantine member. To
tolerate one, use for example 3 of 4 with
f = 1.
Choose t from the fault model you need, not from how many nodes are usually
online.
Membership
Section titled “Membership”Membership is selected per chain with membership.mode:
static(the default): the members and threshold come from configuration, identical on every member.governed: membership and threshold changes are commands on reserved~governance/topics. A change is accepted once a threshold of current members has submitted the identical command. It is recorded in the chain’s own history as a membership epoch with the height it applies from.
Every check uses the membership at the block’s height, so old blocks verify against the members of their time. The stock cluster configuration uses governed membership.
Every membership epoch must satisfy the quorum rules above. A governed add,
remove, or threshold change whose resulting t-of-n breaks them is refused
by the admin endpoints with MEMBERSHIP_QUORUM_INVALID and is void if it ever
reaches activation. An add keeps the current threshold, so with f = 0 a
2-of-3 ledger cannot add a fourth member directly: raise the threshold to 3
first, wait until that epoch is active, then add the member (3-of-4). The local
launcher’s cluster node join does this for you. The
Yano consensus guide
describes the procedure.
Catch-up and restart
Section titled “Catch-up and restart”A member that is behind asks one connected peer every 5 seconds for the next
50 blocks after its tip. It verifies each block with the checks of a live
round, except message expiry: hash links, messagesRoot, the leader for the
height and view, message signatures, the full finality certificate, and a
re-executed state root. A member that is behind but makes no progress for a
minute reports stalled in its status.
Blocks, certificates, state, prepare locks, PreparedQCs, and membership epochs are persisted. The pending message pool is held in memory and is lost on restart, so a message that no other member received must be submitted again. On startup, a member checks that its committed root matches the tip block and that the tip block carries a valid threshold certificate.
What “AGREED” means
Section titled “What “AGREED” means”./yano.sh appchain cluster statusConsistency (per chain, roots must match across nodes): orders-chain: AGREED (...)cluster status reads each running node’s current state root for each chain.
AGREED means the roots are equal; MISMATCH means at least one differs. It
compares roots only, not heights, and reads each node once, so a node that is
one block behind during traffic shows MISMATCH. Run it again when traffic
stops. A mismatch that persists usually means one of:
- a consensus-affecting configuration value differs between members;
- a different plugin bundle version is installed on one member; or
- a custom state machine is not deterministic. See Determinism rules.
Chain identity
Section titled “Chain identity”Three values pin the identity of a chain’s state:
(commitment-profile, format-fingerprint, genesis-id)A retained genesis-id is never regenerated. In addition, every vote signs a
per-height consensus context that binds the chain, genesis, membership,
threshold, and consensus profiles. Changing consensus semantics is therefore a
versioned upgrade, not a rolling code change. See
Consensus rules for plugins.
Deeper reading
Section titled “Deeper reading”- Yano consensus and internals guide: the round check by check, vote locks, rotation, catch-up, and restart.
- ADR-036: the safety and liveness model.
- Tutorial 1: watch agreement on a running three-member cluster.
Next: State and proofs.