Consensus
How XE reaches consensus across the lattice
Overview
XE does not globally order transactions. Its block lattice architecture means each account maintains its own chain, so blocks on independent accounts never have to be ordered against one another. Every block is validated locally using cryptographic signatures, balance checks, and proof-of-work.
What the network does have to agree on is finality — whether a block is irreversible. Representatives run a two-phase election at every chain position (a position is an account plus the Previous hash its blocks extend). A block is finalized once representatives holding at least 67% of delegated XE weight have cast an irrevocable final vote for it.
[!INFO] Every block is voted on Finalization voting is not reserved for forks. Every non-genesis block runs an election at its position. An uncontested position has a single candidate and finalizes quickly; a fork just means the election has more than one candidate competing for the same slot.
Finality matters because it is what makes money real: spendable balance counts only finalized inflows, and a finalized block can never be reorged — the ledger refuses to replace any block at or below an account's final-height watermark.
The Contested Case: Equivocation
An equivocation (also called a double-publish or fork) occurs when an account signs two different blocks that both reference the same Previous hash. This is the block lattice equivalent of a double-spend: the account is trying to create two conflicting histories.
┌──────────┐
┌────▶│ Block A │ (send 100 XE to Alice)
│ └──────────┘
┌──────────┐ │
│ Previous │──┤ ← same Previous hash = CONFLICT
└──────────┘ │
│ ┌──────────┐
└────▶│ Block B │ (send 100 XE to Bob)
└──────────┘Only one of these blocks can be accepted. The network must decide which one wins and which one is rejected.
How It Works
XE uses delegated voting in two phases. Both phases run at every position; a fork changes only how many candidates the election has.
| Phase | Vote kind | Behaviour |
|---|---|---|
| 1. Converge | Final = false | Mutable. A representative votes for its preferred candidate at the position and may re-vote as its preference shifts. Converge votes establish a leader; they never finalize anything. |
| 2. Final | Final = true | Irrevocable. Once a candidate has a visible supermajority, the representative writes a write-once commit-lock for the position and casts a final vote for the locked hash. Only final votes finalize. |
The supporting components:
| Component | Role |
|---|---|
| Delegation | Representatives derive voting power from delegated XE balances |
| Voting | Casting, validating and encoding converge and final votes |
| Quorum | Tallying final votes, finalizing the winner, rejecting losers |
| Conflict Detection | Recording equivocations and staging the competing block |
Quorum Threshold
A block is finalized when its accumulated final-vote weight meets 67% of the total delegated weight:
blockWeight * 100 >= totalWeight * 67All weight calculations use big.Int to prevent overflow. On a contested position the denominator is the total weight snapshotted when the fork was detected; on an uncontested position it is the current total delegated weight.
Deterministic Preference
A representative's converge preference is the candidate with the lexicographically lowest hash among the candidates whose block bodies it actually holds (on chain or in staging) or that appear in the conflict record. Because the rule is deterministic, honest representatives converge on the same block regardless of network propagation order, so an attacker cannot pick the winner by controlling message ordering.
The same determinism is applied on the tally side: only the lowest-hash final-voted candidate may finalize. A node that sees it below threshold withholds rather than finalizing a higher-hash sibling, so two honest nodes can never finalize different siblings even if their local weight snapshots drift.
The Commit-Lock
Before signing a final vote, a representative records the position → hash pair in a write-once store that is never deleted — not on conflict resolution, not on rollback, not on restart. That record is what makes a final vote irrevocable: a representative is structurally unable to final-vote two different hashes at one position, so adversarial timing or a restart can delay finalization but never split it.
Both the commit-lock and the final vote are flushed to stable storage before the final vote is tallied or gossiped. If the flush fails, emission is deferred and retried — the lock persists, so the retry is idempotent.
Design Principles
Dependency gating. A representative withholds its own vote for a block whose dependencies are not finalized locally — its Previous, plus the cross-account Source block for receives and lease lifecycle blocks. It still tallies incoming votes, so a lagging node never deadlocks the election. The dependency switch is exhaustive and fails closed: an unknown block type yields an unresolvable dependency and the vote is withheld.
Weight snapshots. When a fork is detected, the current delegation weights are snapshotted and frozen for that conflict. This prevents an attacker from manipulating delegation between detection and resolution.
Fallback resolution. If non-voting representatives inflate the total weight such that 67% is unreachable, a fallback finalizes the lowest-hash candidate once the election has been open long enough — but only if it carries a strict majority (>50%) of total delegated weight in final votes. Because each representative is commit-locked to at most one hash per position, at most one candidate can ever clear that bar.
Liveness stalls, not forks. Wherever the safety bar is not met, the position stays open. An unresolved position is a recoverable stall that later votes can clear; finalizing on a local view would be an unrecoverable fork behind the rollback wall. The code consistently prefers the stall.
[!WARNING] Only XE confers voting weight XUSD balances do not contribute to voting weight — XUSD is mintable by an authorized minter, and minting must not mint consensus weight. Only delegated XE counts. See Delegation for details.
Position Lifecycle
Block B added at position (account, previous)
│
├── uncontested → VoteManager.OnBlockAdded → CastVote
└── a sibling already occupies the slot
│
▼
conflict recorded (after B passes staging validation)
├── B stored in staging (not the main chain)
├── weight snapshot taken
└── ConflictCallback fires → VoteManager.OnConflict()
│
▼
Phase 1 — converge
├── gather candidates (seed, voted hashes, conflict record)
├── prefer the lowest hash whose body we hold
├── withhold if the preferred block's dependencies aren't finalized
└── sign + store + broadcast a converge vote (re-emitted, throttled)
│
▼
Phase 2 — final
├── preferred candidate at ≥67% converge weight (after the stability window)
│ or, on an aged election, the plurality leader with >50%
├── PutFinalVoteIfAbsent writes the write-once commit-lock
├── durability sync, then sign + store + broadcast the final vote
│
▼
QuorumManager.OnVote() re-tallies FINAL votes after every stored vote
│
├── lowest-hash final-voted candidate at ≥67% → finalize
└── aged + strict majority (>50%) → fallback finalize
│
▼
confirmConflict()
├── promote a staged winner (swap out the on-chain loser, or append)
├── advance the account's final-height watermark
├── Winner: StatusFinalized Losers: StatusRejected
└── clean up votes, staged blocks, and the conflict recordPromoting a winner can require unwinding blocks that were built on the loser — including on other accounts, if the loser's funds were already received and spent onward. See cascading rollback.
Related Pages
- Delegation — how voting weight is derived from delegated XE balances
- Conflict Detection — equivocation detection and staging
- Voting — vote casting, validation, and encoding
- Quorum — tallying final votes and finalizing the winner
- Accounts and Keys — ed25519 key pairs used for signing
- Block Types — the
Representativefield on blocks