Skip to content

Consensus engines

myelin-consensus is the crate that finalises Myelin blocks. It exposes one trait, two implementations, and one config-driven selector. This page covers what each engine does, when to pick which, and how the choice stays behind the same trait so the rest of the runtime doesn't care.

The trait

pub trait ConsensusEngine {
    fn kind(&self) -> ConsensusKind;
    fn verify_certificate(
        &self,
        block_hash: [u8; 32],
        cert: &CommitteeCertificate,
    ) -> Result<()>;
    fn finalise_block(
        &self,
        block: MyelinBlock,
        cert: CommitteeCertificate,
    ) -> Result<FinalisedBlock>;
}

Two engines implement this trait:

  • StaticClosedCommittee — a configured set of validators, any quorum-weight subset can finalise.
  • Tendermint — a configured set of validators, a quorum-power precommit certificate finalises.

Both are constructed through SelectedConsensus::from_config, which reads a TOML file. The rest of the runtime never knows which engine is active.

%%{init: {
  "theme": "base",
  "themeVariables": {
    "primaryColor": "#A5B4FC",
    "primaryTextColor": "#1E293B",
    "primaryBorderColor": "#4F46E5",
    "lineColor": "#6366F1",
    "secondaryColor": "#C7D2FE",
    "tertiaryColor": "#C7D2FE"
  }
}}%%
flowchart TB
    subgraph TR["ConsensusEngine trait"]
        K["kind()"]:::trait
        V["verify_certificate(block_hash, cert)"]:::trait
        F["finalise_block(block, cert)"]:::trait
    end
    subgraph ENGS["Implementations"]
        S["StaticClosedCommittee"]:::impl1
        T["Tendermint"]:::impl2
    end
    subgraph SEL["Selector"]
        SC["SelectedConsensus::from_config(toml)"]:::sel
    end
    subgraph CFG["TOML config"]
        C1["kind = static-closed-committee"]:::cfg
        C2["kind = tendermint"]:::cfg
    end

    SC --> S
    SC --> T
    C1 --> SC
    C2 --> SC

    S -->|implements| TR
    T -->|implements| TR

    classDef trait fill:#A5B4FC,stroke:#4F46E5,color:#1E293B;
    classDef impl1 fill:#C7D2FE,stroke:#7C3AED,color:#1E293B;
    classDef impl2 fill:#C7D2FE,stroke:#7C3AED,color:#1E293B;
    classDef sel  fill:#C7D2FE,stroke:#7C3AED,color:#1E293B;
    classDef cfg  fill:#A5B4FC,stroke:#D97706,color:#1E293B;

The MyelinBlock shape

Both engines operate on the same MyelinBlock:

pub struct MyelinBlock {
    pub version: u32,
    pub parent_hash: [u8; 32],
    pub number: u64,
    pub timestamp_ms: u64,
    pub consensus_kind: ConsensusKind,
    pub state_root_before: [u8; 32],
    pub state_root_after:  [u8; 32],
    pub ordered_cell_tx_commitments: Vec<[u8; 32]>,
    pub data_commitments:            Vec<[u8; 32]>,
    pub scheduler_commitment:        [u8; 32],
}

The block hash is a canonical hash over the Molecule-shaped serialised header plus all commitments. Two properties:

  • Stability. Same inputs → same hash, on every validator, every time.
  • Sensitivity. Any field mutation → a different hash.

Tests must cover both. The production gate runs the canonical-hash tests for both engines.

Static closed committee

A static closed committee is configured from TOML:

kind = "static-closed-committee"

[static_committee]
quorum_weight = 2

[[static_committee.validators]]
id = "validator-0"
public_key = "0101010101010101010101010101010101010101010101010101010101010101"
weight = 1

[[static_committee.validators]]
id = "validator-1"
public_key = "0202020202020202020202020202020202020202020202020202020202020202"
weight = 1

A certificate is valid when the sum of signer weights is at least quorum_weight and each signature verifies against the validator's public key.

This is the simplest possible finality model: the committee is known, the quorum is fixed, the cert is a list of signatures.

[!NOTE] The static committee engine is the default for sessions and pressure testing. It is not a permissionless security claim.

Tendermint-style weighted precommit

A Tendermint-style engine is configured from TOML:

kind = "tendermint"

[tendermint]
quorum_power = 2

[[tendermint.validators]]
id = "validator-0"
public_key = "0101010101010101010101010101010101010101010101010101010101010101"
weight = 1

[[tendermint.validators]]
id = "validator-1"
public_key = "0202020202020202020202020202020202020202020202020202020202020202"
weight = 1

[[tendermint.validators]]
id = "validator-2"
public_key = "0303030303030303030303030303030303030303030303030303030303030303"
weight = 1

A certificate is a list of Precommit { validator_id, signature, block_hash } records. The certificate is valid when the sum of signer weights is strictly more than quorum_power and every signature verifies.

This model gives you the same property as static committee (a quorum of weight/power signs the same block_hash), with the Tendermint-style strict "more than ⅔" semantics — which is what makes it BFT-equivalent under partial synchrony assumptions.

How the two engines interact with the rest of the runtime

%%{init: {
  "theme": "base",
  "themeVariables": {
    "primaryColor": "#A5B4FC",
    "primaryTextColor": "#1E293B",
    "primaryBorderColor": "#4F46E5",
    "lineColor": "#6366F1",
    "secondaryColor": "#C7D2FE",
    "tertiaryColor": "#C7D2FE"
  }
}}%%
sequenceDiagram
    participant Exec as Executor
    participant State as myelin-state
    participant Cons as myelin-consensus
    participant CLI as myelin-cli

    Exec->>State: CellTx applied, state_root_after
    State->>Cons: MyelinBlock candidate
    Cons->>Cons: validate quorum / precommit
    Cons-->>CLI: FinalisedBlock
    CLI->>Cons: verify_certificate (on import)
    Cons-->>CLI: Ok(()) or Err

The block candidate is the same regardless of which engine is configured. Only the certificate shape changes:

Field Static Tendermint
Certificate kind static-closed-committee tendermint
Signer set Quorum weight or more Strict majority of total power
Evidence shape Signatures over block_hash Precommits with block_hash
Trust model Quorum of configured validators Quorum of configured validators

Both engines produce the same block_hash for the same inputs. The session ID, CellTx commitments, scheduler commitment, and state roots are identical between the two — only the certificate is different. That's the point: the choice of finality stays behind the trait.

When to pick which

Use case Pick Why
Session benchmarking Static closed committee Simplest cert shape, fastest to set up.
Pressure testing Static closed committee Lets you iterate on the protocol without BFT concerns.
Multi-validator demo Tendermint More realistic BFT semantics; rotates validator responsibility.
Pre-permissionless research Tendermint Closer to the eventual "open committee" shape, while still being a known-set engine.
Live public L2 Neither yet Both engines assume a known validator set. The court path is what would extend this to a permissionless claim.

What neither engine is

  • Neither is a Nakamoto PoW consensus. Block finality is immediate on quorum signature; there is no probabilistic confirmation.
  • Neither is a permissionless entry path. A new validator can only be added through configuration change, not through an on-chain action.
  • Neither is a slashing engine. There is no stake to slash in the current design; the trust model is direct ("you trust the configured validators").

These are deliberate scope choices. See Claim ladder for what would have to be true to climb higher.

Where to look next