# Consensus

Armchain uses the **Lachesis** consensus protocol, an **asynchronous Byzantine Fault Tolerant (aBFT)** consensus mechanism. In plain terms, this means transactions are finalized instantly with no waiting for multiple block confirmations like on Ethereum, and no risk of the chain reorganizing and reversing your transaction.

This page explains how Lachesis works and why it provides stronger guarantees than most other consensus mechanisms.

## What is aBFT?

Asynchronous Byzantine Fault Tolerance is the strongest class of consensus guarantees:

* **Asynchronous**: No timing assumptions. Consensus proceeds regardless of network delays, message reordering, or partitions.
* **Byzantine Fault Tolerant**: Tolerates up to **1/3 of validators** (by stake weight) being malicious or offline.
* **Deterministic Finality**: Confirmed transactions are **permanently final**: no rollbacks, no reorganizations.

### Async BFT vs. Synchronous BFT

At a high level, synchronous vs. asynchronous BFT differs in **trigger mechanism**.

#### Synchronous / Partially Synchronous BFT

**Examples**: Cosmos (Tendermint), Ethereum (Gasper/Casper).

These protocols operate on a fixed schedule. Each time slot has a designated leader. If the leader disconnects or responds too slowly, the protocol moves to the next leader.

| Aspect        | Details                                                                                                                                                                                                                                       |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Trigger**   | Time - "It is 12:00:00, time for the next block"                                                                                                                                                                                              |
| **Structure** | Linear chain - blocks are added one by one                                                                                                                                                                                                    |
| **Mechanism** | Rounds and leaders: time is divided into slots (e.g., Ethereum's 12-second slots). A leader proposes a block, validators vote.                                                                                                                |
| **Weakness**  | If the leader's block doesn't reach validators within the timeout (due to lag or DDoS), validators vote to skip that leader (View Change). An attacker can stall the network by delaying messages just enough to trigger timeouts repeatedly. |

#### Asynchronous BFT (Lachesis)

**Example**: Armchain / Fantom (Lachesis).

There is no clock, no leader, and no rounds. Nodes communicate with neighbors and process events as they arrive.

| Aspect        | Details                                                                                                                                                                                                                                                       |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Trigger**   | Events - "I just received new data, let's process it"                                                                                                                                                                                                         |
| **Structure** | DAG - events flow in parallel like a river, then are sorted into a total order                                                                                                                                                                                |
| **Mechanism** | Leaderless: no single node is in charge of a "round." Everyone produces events whenever they want. Gossip-about-gossip creates a web of causal connections, and virtual voting determines consensus by inspecting the DAG - no explicit vote messages needed. |
| **Strength**  | Because it doesn't wait for a clock, it can go as fast as the network allows. If the network is fast, consensus is instant. If the network lags, it just slows down - it doesn't "break" or require a complex view-change reset.                              |

#### Comparison Summary

| Feature             | Synchronous (Cosmos/ETH)                                                                                                                | Asynchronous (Lachesis)                                                |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| **Trigger**         | Time-based slots                                                                                                                        | Event-based                                                            |
| **Structure**       | Linear chain                                                                                                                            | DAG (parallel, sorted later)                                           |
| **Bottleneck**      | The timeout - the network can never be faster than the predefined slot time (e.g., Ethereum is always 12s, even if the network is idle) | Bandwidth - the network moves as fast as data can travel               |
| **DDoS Resistance** | Lower - attacker can target the leader or exploit timing                                                                                | Higher - no leader to DDoS, timing delays don't break the logic        |
| **Finality**        | Fast (Tendermint is instant, Ethereum \~15 min for full finality)                                                                       | Instant - usually < 1 second because no "round" coordination is needed |

> **Why isn't everyone using aBFT?** Asynchronous algorithms are mathematically harder to implement correctly. They require complex graph theory (finding roots and Atropos in the DAG) compared to "longest chain" or "majority vote" logic. aBFT nodes must also gossip more history (DAG structure) to prove event visibility, which is bandwidth-intensive.

### Comparison with Ethereum PoS

| Aspect             | Ethereum PoS                   | Lachesis aBFT                           |
| ------------------ | ------------------------------ | --------------------------------------- |
| Block Production   | Fixed 12-second slots          | Variable timing based on consensus      |
| Proposer Selection | Single validator per slot      | All validators participate continuously |
| Finality           | Multi-slot (2 epochs \~13 min) | Immediate upon Atropos selection        |
| Communication      | Broadcast blocks               | Gossip events asynchronously            |
| Consensus Type     | BFT (synchronous)              | aBFT (asynchronous)                     |
| Structure          | Linear chain                   | DAG with Atropos checkpoints            |
| Throughput         | Limited by single proposer     | Higher - parallel event creation        |
| Network Tolerance  | Sensitive to network delays    | Resilient to network delays             |

## How Lachesis Works

### High-Level Architecture

```
┌─────────────┐     ┌─────────────────┐     ┌──────────────────┐
│   Emitter   │────▶│ Lachesis Engine  │────▶│ Block Processing  │
│  (Mempool)  │     │  (Consensus)    │     │   (Callbacks)    │
└─────────────┘     └─────────────────┘     └──────────────────┘
       │                    │                        │
       ▼                    ▼                        ▼
┌─────────────┐     ┌─────────────────┐     ┌──────────────────┐
│   Events    │     │   DAG + Roots   │     │    EVM State     │
│ (with Txs)  │     │   + Atropos     │     │  + SFC Update    │
└─────────────┘     └─────────────────┘     └──────────────────┘
```

### Key Concepts

#### Events

Events are the fundamental units of the DAG. Each event is created by a validator and contains:

* **Creator**: Validator ID that created the event
* **Parents**: References to previous events (self-parent + other parents)
* **Transactions**: User transactions included in the event
* **Frame**: Consensus round the event belongs to
* **Lamport timestamp**: Logical clock for ordering
* **Epoch**: Time period the event belongs to
* **Gas power used**: Gas consumed by the event's transactions
* **Signature**: ML-DSA44 signature (3,732 bytes)

#### Frames

A **frame** is a consensus round that groups events for processing. Frames are the granularity at which Atropos events are elected.

```
┌───────────┐     ┌───────────┐     ┌───────────┐     ┌───────────┐
│  Frame 0  │────▶│  Frame 1  │────▶│  Frame 2  │────▶│  Frame 3  │
│(Candidates)│    │  (Voting) │     │  (Voting) │     │ (Decision)│
└───────────┘     └───────────┘     └───────────┘     └───────────┘
      │                 │                 │                 │
      ▼                 ▼                 ▼                 ▼
 Events created    Roots vote on    Votes aggregated  Atropos decided
 by validators     Frame 0 events   from Frame 1      for Frame 0
```

#### Roots

A **root** is a special event that qualifies to participate in the Atropos election:

* Must be **forkless-caused** by ≥ 2/3 of validator weight from the previous frame
* Each validator can have **at most one root per frame**
* Roots cast votes in the election process

> **Forkless cause**: An event E becomes a root if it is observed by roots from validators representing ≥ 2/3 of total stake weight from the previous frame. For example, with validators A(30), B(25), C(25), D(20) = 100 total weight, the quorum is `(100 × 2/3) + 1 = 67` weight.

#### Epochs

An **epoch** is a larger time period containing multiple frames:

* Each epoch has a **unique validator set**
* Epoch boundaries are determined by:
  * **Maximum epoch gas consumed** (`MaxEpochGas`: 1.5B gas)
  * **Maximum epoch duration** (`MaxEpochDuration`: 4 hours)
  * **Cheaters detected** (forces epoch seal)
  * **AdvanceEpochs > 0** (administrative trigger)

| Parameter          | Devnet        | Fakenet     |
| ------------------ | ------------- | ----------- |
| Max Epoch Duration | 4 hours       | 10 minutes  |
| Max Epoch Gas      | 1,500,000,000 | 300,000,000 |

At epoch boundaries:

1. Validator metrics (uptime, missed blocks, originated fees) are pushed on-chain to the SFC contract
2. Validator rewards become claimable
3. A new validator set may be activated
4. Network rules may be updated

### DAG-Based Event Ordering

Unlike traditional blockchains that produce a single linear chain of blocks, Lachesis builds a **Directed Acyclic Graph (DAG)** of events. Each validator independently creates events that reference previous events from other validators.

```
Time ──────────────────────────────────────────────────▶

Validator A: [E_a1]──────────▶[E_a2]──────────▶[E_a3]──────────▶[E_a4]
               │                 │╲                │
Validator B: [E_b1]────┬───▶[E_b2]──┬──▶[E_b3]──────────▶[E_b4]
               │        │╲         │       │╲
Validator C: [E_c1]────┘  ╲──[E_c2]───┘──▶[E_c3]──────────▶[E_c4]
               │             ╱         │
Validator D: [E_d1]──[E_d2]──────────[E_d3]──────────▶[E_d4]

             Frame 0       Frame 1       Frame 2       Frame 3
```

**Gossip about gossip**: When Node A communicates with Node B, Node B adds a reference to Node A's latest event, building a web of causal connections. **Virtual voting**: By inspecting the DAG structure, nodes determine which events have been observed by which validators. No explicit vote messages are sent.

### Atropos Election

The **Atropos** is a single finalized event selected through weighted voting that serves as a checkpoint and block boundary. There is exactly **one Atropos per frame**, and there is a **1:1 mapping between Atropos events and blocks**.

#### Election Algorithm

1. **Round 1** (Frame F+1 voting on Frame F): Each root R in Frame F+1 votes:
   * **YES** for candidate C if R observes C (has forkless causality)
   * **NO** for candidate C if R does NOT observe C
2. **Round 2+** (Frame F+2 and beyond): Each root R' aggregates votes from the previous round:
   * Count weighted YES votes and NO votes
   * Vote according to the majority
   * If `YES_weight ≥ Quorum` OR `NO_weight ≥ Quorum` → **DECIDED**
3. **Quorum**: `(TotalWeight × 2/3) + 1`
4. **Atropos selection**: Iterate through validators in deterministic order (by weight descending). The first validator whose candidate event has a decided "YES" vote becomes the Atropos.

#### Worked Example

Consider 4 validators with stakes: A(30), B(25), C(25), D(20) = 100 total weight, quorum = 67.

**Frame F: Candidates**: Each validator creates a candidate event:

* `a0_0` (5 txs), `b0_0` (3 txs), `c0_0` (7 txs), `d0_0` (2 txs)

**Frame F+1: Round 1 Voting**: Root events observe candidates:

| Root               | Observes                   |
| ------------------ | -------------------------- |
| `a1_1` (weight 30) | a0\_0, b0\_0, c0\_0        |
| `b1_1` (weight 25) | b0\_0, c0\_0, d0\_0        |
| `c1_1` (weight 25) | a0\_0, b0\_0, c0\_0, d0\_0 |
| `d1_1` (weight 20) | c0\_0, d0\_0               |

**Vote matrix** (YES = ✓, NO = ✗):

| Candidate | a1\_1 (30w) | b1\_1 (25w) | c1\_1 (25w) | d1\_1 (20w) | Total YES                    |
| --------- | ----------- | ----------- | ----------- | ----------- | ---------------------------- |
| `a0_0`    | ✓           | ✗           | ✓           | ✗           | 55 - NOT DECIDED             |
| `b0_0`    | ✓           | ✓           | ✓           | ✗           | 80 - **DECIDED YES** (≥ 67)  |
| `c0_0`    | ✓           | ✓           | ✓           | ✓           | 100 - **DECIDED YES** (≥ 67) |
| `d0_0`    | ✗           | ✓           | ✓           | ✓           | 70 - **DECIDED YES** (≥ 67)  |

**Atropos selection**: Iterate by weight descending, A(30), B(25), C(25), D(20):

1. A's candidate (`a0_0`): NOT decided → skip
2. B's candidate (`b0_0`): **DECIDED YES** → **`b0_0` is elected Atropos**

This event becomes the checkpoint for Frame F. A new block is created containing all confirmed events from this frame.

#### When No Atropos is Decided

If not enough events reach quorum in a frame (e.g., too many validators offline but still above 2/3 total weight), voting continues into subsequent rounds. If an Atropos cannot be elected for a frame, that frame's block is skipped.

## Block Processing Lifecycle

### Event Emission

The **Emitter** is responsible for creating events at regular intervals, packaging transactions from the mempool.

```
┌─────────────┐
│   Mempool   │
│  (Pending   │
│    Txs)     │
└──────┬──────┘
       │ getSortedTxs()
       ▼
┌─────────────┐
│   Emitter   │◄─── Tick every ~11ms
│             │
│  • Min interval: 110ms
│  • Max interval: 10min
│  • Max 32 txs/address
└──────┬──────┘
       │ createEvent()
       ▼
┌─────────────┐
│    Event    │
│  • Creator  │
│  • Parents  │
│  • Txs      │
│  • Lamport  │
│  • Signature│
└──────┬──────┘
       │
       ├─── world.Process(e) ──▶ Lachesis Engine
       │
       └─── world.Broadcast(e) ──▶ Network Peers
```

**Emitter configuration**:

| Parameter                    | Value  |
| ---------------------------- | ------ |
| Min Emit Interval            | 110 ms |
| Max Emit Interval            | 10 min |
| Confirming Interval          | 120 ms |
| Doublesign Protection        | 27 min |
| Parallel Instance Protection | 1 min  |

### Consensus Callbacks

When the Lachesis engine decides on an Atropos, it invokes application callbacks using a **three-phase model**:

```
Lachesis Engine
       │ onFrameDecided()
       ▼
┌──────────────┐
│  BeginBlock  │
│  • Load state│
│  • Merge     │
│    cheaters  │
│  • Open      │
│    StateDB   │
└──────┬───────┘
       │
  ┌────┼────┐
  ▼    ▼    ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ApplyEvent│ │ApplyEvent│ │ApplyEvent│  (for each confirmed
│• Collect │ │          │ │          │   event in order)
│  events  │ │          │ │          │
│• Process │ │          │ │          │
│  MBProofs│ │          │ │          │
└──────────┘ └──────────┘ └──────────┘
       │
       ▼
┌──────────────┐
│   EndBlock   │
│  • Execute   │
│    txs       │
│  • Seal epoch│
│    (if ready)│
│  • Persist   │
│    state     │
└──────────────┘
```

1. **BeginBlock**: Loads block and epoch state, triggers the events module to fetch the confirmed Atropos events.
2. **ApplyEvent** (called for each confirmed event): Collects events and processes misbehavior proofs (e.g., double signing). Cheaters are penalized after the current epoch ends.
3. **EndBlock**: Performs the three-phase transaction execution (see below), then persists state and updates the state root.

### Three-Phase Transaction Execution

The EndBlock callback executes transactions in three distinct phases:

#### Phase 1: Pre-Internal Transactions

* **Punish cheaters**: `DeactivateValidator(validatorID, DoublesignBit)` for any validators caught double-signing
* **If sealing epoch**: Push validator metrics to the SFC contract via `SealEpoch(metrics)`:
  * `offlineTimes[]`: duration each validator was offline
  * `offlineBlocks[]`: blocks missed by each validator
  * `uptimes[]`: active time for each validator
  * `originatedTxsFee[]`: total fees from transactions originated by each validator

#### Epoch Sealing (if triggered)

Epoch sealing is triggered when any of these conditions are met:

| Condition              | Threshold                      |
| ---------------------- | ------------------------------ |
| Epoch gas consumed     | ≥ `MaxEpochGas` (1.5B gas)     |
| Epoch duration elapsed | ≥ `MaxEpochDuration` (4 hours) |
| Cheaters detected      | Any double-signers found       |
| Administrative advance | `AdvanceEpochs > 0`            |

When an epoch is sealed:

* New validators are selected based on `NextValidatorProfiles`
* `ValidatorBlockStates` are reset
* Epoch number is incremented
* Network rules are updated if `DirtyRules` is present

#### Phase 2: Post-Internal Transactions

* **If sealing epoch**: `SealEpochValidators(nextValidatorIDs)`, which updates the active validator set on-chain

#### Phase 3: Event Transactions

* Sort confirmed events by Lamport time
* Apply `spillBlockEvents()` to exclude events exceeding `MaxBlockGas`
* Collect all transactions from events
* Execute through the EVM
* Track receipts and gas usage
* Update originated fees per validator

### State Persistence

After transaction execution, the block is persisted to storage:

```
EVM State Commit
  statedb.Commit(true) → New StateRoot
       │
       ▼
Block Construction
  block := {
    Time:       blockCtx.Time,
    Atropos:    cBlock.Atropos,
    Events:     confirmedEvents,
    Root:       stateRoot,
    GasUsed:    evmBlock.GasUsed,
  }
       │
       ▼
Storage Operations
  • SetTxPosition()       - map tx hash → event & block position
  • SetReceipts()         - store transaction receipts by block
  • IndexLogs()           - index logs for efficient filtering
  • SetBlock()            - store block data
  • SetBlockIndex()       - map Atropos hash → block number
  • SetBlockEpochState()  - update current block/epoch state
  • commitEVM()           - flush EVM state to disk
```

## Validators

Validators are the nodes that participate in consensus by creating and signing events.

### Validator Requirements

* **Staking**: Validators must stake ARM tokens (minimum self-stake varies by network)
* **Key Type**: Validators use **ML-DSA44** keys exclusively
* **Uptime**: Validators earn rewards proportional to their participation
* **Max Parents**: Each event can reference up to **10 parent events**

### Validator Key Generation

Validators generate ML-DSA44 key pairs:

```bash
# Generate a new validator key
armnode validator new
```

This creates a key file in the validator keystore with:

* Private key: 2,560 bytes (ML-DSA44)
* Public key: 1,312 bytes (ML-DSA44)

## Validator Rewards System

### SFC Contract

The **SFC (Special Fee Contract)** is the core on-chain governance mechanism deployed at genesis. It manages:

* Validator registration and management
* Stake delegation
* Reward distribution
* Epoch sealing
* Slashing and penalties

The SFC is owned by a single **owner address** set at genesis (the root of trust). No validator can change this owner address: only the owner itself can transfer ownership.

```
SFC Contract (Fixed Address at Genesis)
├── STATE:
│   ├── validators[]         - validator info (status, stake, times)
│   ├── delegations[]        - delegation info per delegator+validator
│   ├── epochSnapshots[]     - epoch state snapshots
│   ├── withdrawalRequests[] - pending withdrawals
│   └── parameters           - baseRewardPerSecond, penalties, etc.
│
├── USER FUNCTIONS:
│   ├── createValidator(pubkey) payable
│   ├── delegate(toValidatorID) payable
│   ├── undelegate(toValidatorID, wrID, amount)
│   ├── withdraw(toValidatorID, wrID)
│   ├── claimRewards(toValidatorID)
│   └── lockStake(toValidatorID, duration, amount)
│
├── NODE INTERFACE (via NodeDriver):
│   ├── sealEpoch(offlineTimes, offlineBlocks, uptimes, fees)
│   ├── sealEpochValidators(nextValidatorIDs)
│   └── deactivateValidator(validatorID, status)
│
└── GOVERNANCE (Owner Only):
    ├── updateBaseRewardPerSecond(value)
    ├── updateOfflinePenaltyThreshold(blocksNum, time)
    └── transferOwnership(newOwner)
```

### Reward Calculation

Validator rewards consist of two components:

```
Total Reward = Transaction Fees + Base Rewards

Where:
  Transaction Fees = Σ(GasUsed × GasPrice) for transactions originated by the validator

  Base Rewards = baseRewardPerSecond × epochDuration
                 × (validatorStakeWeight / totalBaseRewardWeight)
```

**The greater the stake, the greater the rewards.** After each epoch, rewards are recalculated and become claimable by validators and their delegators.

### Epoch Sealing

When an epoch ends, the node pushes validator metrics on-chain through the **NodeDriver** intermediary contract:

**Validator metrics collected per epoch**:

| Metric             | Description                                         |
| ------------------ | --------------------------------------------------- |
| `missed.BlocksNum` | Blocks missed by the validator                      |
| `missed.Period`    | Duration the validator was offline                  |
| `uptime`           | Active time during the epoch                        |
| `originatedTxFee`  | Total fees from transactions the validator included |

**Forgiveness rule** (`BlockMissedSlack = 50`): If a validator missed ≤ 50 blocks, the miss counts are reset to zero and no penalty applies. This prevents penalizing validators for brief, incidental downtime.

**Epoch snapshot** (persisted on-chain):

| Field                   | Description                                           |
| ----------------------- | ----------------------------------------------------- |
| `endTime`               | Epoch end timestamp                                   |
| `epochFee`              | Total transaction fees collected                      |
| `totalBaseRewardWeight` | Sum of validator weights for base reward distribution |
| `totalTxRewardWeight`   | Weighted transaction fee distribution                 |
| `baseRewardPerSecond`   | Governance-set reward rate                            |
| `totalStake`            | Total staked in the epoch                             |
| `totalSupply`           | Total token supply                                    |

### Governance Model

The SFC owner address (set at genesis, root of trust) controls key economic parameters:

| Parameter                 | Type       | Description                                   |
| ------------------------- | ---------- | --------------------------------------------- |
| `baseRewardPerSecond`     | Governance | Validator reward rate - set by owner          |
| `offlinePenaltyThreshold` | Governance | (blocksNum, time) tuple for offline penalties |
| `BlockMissedSlack`        | Network    | 50 blocks (forgiveness threshold)             |
| `MinGasPrice`             | Network    | 1 Gwei                                        |
| `MaxEpochGas`             | Network    | 1.5B gas                                      |
| `MaxEpochDuration`        | Network    | 4 hours                                       |
| `MinSelfStake`            | Network    | Varies by network                             |
| `WithdrawalPeriodTime`    | Network    | 7 days                                        |

> **Key point**: Node operators cannot modify governance parameters. Only the designated owner address can update `baseRewardPerSecond`, penalty thresholds, and transfer ownership.

## Finality Guarantees

### What "Instant Finality" Means

When you submit a transaction to Armchain:

1. Transaction enters the mempool
2. Emitter includes it in an event (\~110ms tick)
3. Event is gossiped to peers and enters the DAG
4. aBFT consensus elects an Atropos for the frame
5. Three-phase block processing executes the transaction
6. **Transaction is final**: it will never be reversed

No confirmation waits are needed. A single confirmation on Armchain is equivalent to thousands of confirmations on a proof-of-work chain.

### Safety Under Network Partition

Even if the network splits into partitions:

* No **conflicting** blocks can be finalized
* When the partition heals, consensus resumes from where it left off
* No fork choice rules are needed: there are no forks

### Liveness

Lachesis guarantees liveness (the network continues making progress) as long as more than 2/3 of validators (by stake weight) are honest and online.

## Gas and Event Economics

### Gas Power

Validators have **gas power** that replenishes over time:

| Parameter         | Value                                  |
| ----------------- | -------------------------------------- |
| Max Block Gas     | 20,500,000                             |
| Default Event Gas | 28,000                                 |
| Min Gas Price     | 1 Gwei                                 |
| Empty Block Skip  | 1 minute (devnet), 3 seconds (fakenet) |

### Max Parents

Events in the DAG can reference multiple parent events:

| Parameter        | Value |
| ---------------- | ----- |
| Max Parents      | 10    |
| Max Free Parents | 3     |

More parent references improve the DAG's connectivity and consensus speed, but consume more bandwidth.

## Further Reading

* [DAG Structure](/architecture/dag.md): Deep dive into the DAG data structure
* [EVM Integration](/architecture/evm.md): How the EVM runs on top of Lachesis
* [Transaction Lifecycle](/architecture/transaction-lifecycle.md): End-to-end transaction flow
* [Running a Validator](https://github.com/khizarbakhtiar1/dev-docs/blob/main/node-operators/running-a-validator.md): Participate in consensus
* [Node Types](/node-operators/node-types.md): GC modes, sync modes, and database configuration


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.armchain.org/architecture/consensus.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
