Skip to content

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”.

TermMeaning
MemberA node identified by an Ed25519 key that checks and votes on every block. A ledger has at most 32 members.
Threshold tHow many member signatures each step of the round needs.
ViewOne attempt to finalize a height. Every height starts at view 0; a failed attempt moves to the next view.
LeaderThe member that proposes the block for a height and view.
PREPARE, COMMITThe two signed votes of a round.
PreparedQCA threshold of PREPARE votes for one block.
Finality certificateA threshold of COMMIT signatures. It makes the block final.

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.

Inside a roundIllustrative data

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”.

  1. 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.
  2. 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: prevHash equals the local tip hash
    • passes: messagesRoot recomputes from the messages
    • passes: consensusContextDigest equals 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
  3. 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.
  4. 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.
  5. Commit Holding a PreparedQC, each member signs COMMIT for the same block and sends it to the others.
  6. 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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.

  1. 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.
  2. 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: prevHash equals the local tip hash
    • passes: messagesRoot recomputes from the messages
    • passes: consensusContextDigest equals 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
  3. The quorum finalizes Members 0, 1, and 2 are the threshold. They prepare, commit, and finalize block h with root 7f3a….
  4. 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:

  1. Propose. The leader selects pending messages, applies the block itself, persists a prepare lock, and sends the block with its PREPARE vote.
  2. Check. Each follower checks the block and re-executes it. It votes only if its own state root matches byte for byte.
  3. Prepare. A threshold of PREPARE votes forms a PreparedQC. Any member that collects them can form it.
  4. Commit. Holding a PreparedQC, members sign COMMIT.
  5. 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.

A member signs only a root it computed itself, and it never prepares two different blocks in one view. Every member checks, always:

CheckIf it fails
The block’s proposer signed the proposal and is the leader for this height and viewNo vote.
prevHash links to the local tip; messagesRoot recomputes; the consensus context matchesNo vote.
Every message is unexpired and signed by a member at this heightNo vote for the whole block.
Re-executing the block gives the same stateRootNo vote.
A vote or certificate signature is from a member at that height, over this blockThe vote is ignored.
A finality certificate has a threshold of valid, distinct member signaturesThe 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.

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.

Anatomy of an app blockConcept
HEADER · COVERED BY THE BLOCK HASHBODYOUTSIDE THE HASHFormat versionversion = 3Chain idchainIdHeightheightConsensus contextconsensusContextDigestViewviewPrevious blockprevHashL1 referencel1Slot, l1BlockHashTimestamptimestamp (ms)Messages rootmessagesRootState rootstateRootProposerproposerJustificationjustification digestMessagesbound by messagesRootFinality certificatecertMerkle root of idssigns the hashHEADER · COVERED BY THE BLOCK HASHBODYOUTSIDE THE HASHFormat versionversion = 3Chain idchainIdHeightheightConsensus contextconsensusContextDigestViewviewPrevious blockprevHashL1 referencel1Slot, l1BlockHashTimestamptimestamp (ms)Messages rootmessagesRootState rootstateRootProposerproposerJustificationjustification digestMessagesvia messagesRootCertificatecertMerkle root of idssigns the hash
  • 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).
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-depth is 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.
A version-3 app block. The block hash is blake2b-256 over the header fields, the proposer, and a digest of the justification. Messages are bound through messagesRoot; only the finality certificate is outside the hash.

Every member computes the same leader from committed data, so no coordination is needed:

leader = sortedMembers[(initial + view) mod n]
Sequencer modeinitial for view 0Use it when
fixedThe configured sequencer.proposer.A clear operational owner exists.
rotatingDerived 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.

Three numbers describe a ledger’s fault model:

  • n: the number of members, at most 32;
  • t: the threshold; and
  • f: how many members may be Byzantine, that is, sign conflicting votes. It is the chain setting consensus.max-byzantine-members, default 0.

A node refuses to start a chain unless all three rules hold:

RuleWhy
1 ≤ t ≤ nThe threshold is within the membership.
2t − n > fAny two quorums share at least one honest member, so two conflicting blocks cannot both be certified.
t ≤ n − fA 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.

Threshold rulesRule check

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).

  1. 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 with f members faulty
    Members nThreshold tFault bound fMay be offline
    3201
  2. 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 member consensus quorums do not intersect in an honest member
    • not reached: t ≤ n − f: a quorum remains with f members faulty
    Members nThreshold tFault bound fMay be offline
    310— the chain does not start
  3. 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 member consensus quorums do not intersect in an honest member
    • not reached: t ≤ n − f: a quorum remains with f members faulty
    Members nThreshold tFault bound fMay be offline
    420— the chain does not start
  4. 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 with f members faulty
    Members nThreshold tFault bound fMay be offline
    4301
  5. 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 with f members faulty
    Members nThreshold tFault bound fMay be offline
    5302
  6. 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 with f members faulty
    Members nThreshold tFault bound fMay be offline
    1100

What if: One Byzantine member (f = 1)

With f = 1, safety must hold even if one member signs conflicting votes.

  1. 3 of 4, f = 1 The classic n = 3f + 1, t = 2f + 1 profile. 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 with f members faulty
    Members nThreshold tFault bound fMay be offline
    4311
  2. 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 member consensus quorums do not intersect in an honest member
    • not reached: t ≤ n − f: a quorum remains with f members faulty
    Members nThreshold tFault bound fMay be offline
    321— the chain does not start
  3. 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 with f members faulty consensus threshold cannot remain live under the configured fault bound
    Members nThreshold tFault bound fMay be offline
    441— the chain does not start
  4. 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 with f members faulty
    Members nThreshold tFault bound fMay be offline
    5411

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 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.

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.

Terminal window
./yano.sh appchain cluster status
Consistency (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.

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.

Next: State and proofs.