Skip to main content

Consensus Mechanism

Mersennet uses Delegated Proof-of-Stake (DPoS) with round-robin proposer selection and priority-based weighting. This document provides a deep dive into how consensus works, from validator selection to block finalization and slashing.

Overview​

ParameterValue
ConsensusDelegated Proof-of-Stake (DPoS)
Block Time~1 second
FinalityBFT (Byzantine Fault Tolerant)
ImplementationRust

Validator Selection​

Validators are nodes that have staked PRIM tokens and registered in the validator set. Voting power is proportional to stake:

voting_power(validator) ∝ staked_amount

Token holders can delegate their PRIM to validators, increasing that validator's voting power. The validator set is dynamicβ€”new validators can join by staking, and existing validators can leave by unbonding.

Proposer Rotation​

Block production uses a round-robin algorithm with priority-based weighting:

  1. Each validator has a priority value that accumulates over time.
  2. The validator with the highest priority is selected as the proposer for the current block.
  3. After selection, the proposer's priority is reduced by the total weight of all validators.
  4. All validators' priorities are incremented by their normalized stake weight each round.

This ensures:

  • Fair rotation β€” No single validator dominates block production
  • Stake-weighted frequency β€” Validators with more stake are chosen more often
  • Determinism β€” Given the same validator set and heights, proposer selection is reproducible

Priority Algorithm (Conceptual)​

priority[i] += normalized_weight[i]   (each round)
priority[proposer] -= total_weight (after selection)
proposer = argmax(priority)

Weights are normalized from stake to prevent overflow with 18-decimal PRIM values.

Block Production Cycle​

The block production cycle proceeds as follows:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ BLOCK PRODUCTION CYCLE β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Height N
β”‚
β”‚ 1. PROPOSER SELECTION
β”‚ └─ Select validator with highest priority
β”‚
β”‚ 2. PROPOSAL PHASE
β”‚ └─ Proposer builds block (txs, orders, state)
β”‚ └─ Broadcasts block to all validators
β”‚
β”‚ 3. PREVOTE PHASE
β”‚ └─ Validators receive block, validate
β”‚ └─ Broadcast prevote for block hash
β”‚ └─ Wait for 2/3+ prevotes
β”‚
β”‚ 4. PRECOMMIT PHASE
β”‚ └─ Validators broadcast precommit
β”‚ └─ Wait for 2/3+ precommits
β”‚
β”‚ 5. FINALIZATION
β”‚ └─ Block committed to chain
β”‚ └─ State root updated
β”‚ └─ Rewards distributed
β”‚
β–Ό
Height N+1

Diagram: Block Production Flow​

     Validator A          Validator B          Validator C          Validator D
(Proposer) (Voter) (Voter) (Voter)
β”‚ β”‚ β”‚ β”‚
β”‚ Create Block β”‚ β”‚ β”‚
│────────────────────── β”‚ β”‚
β”‚ β”‚ β”‚ β”‚
β”‚ Broadcast Block β”‚ β”‚ β”‚
│─────────────────────┼────────────────────┼──────────────────────
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ Prevote β”‚ Prevote β”‚ Prevote
β”‚ │◄───────────────────┼──────────────────────
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ Precommit (2/3+) β”‚ Precommit β”‚ Precommit
β”‚ │◄───────────────────┼──────────────────────
β”‚ β”‚ β”‚ β”‚
β”‚ FINALIZE β”‚ β”‚ β”‚
│─────────────────────┼────────────────────┼──────────────────────
β”‚ β”‚ β”‚ β”‚
β–Ό β–Ό β–Ό β–Ό

Block Finalization​

A block is finalized when:

  1. Prevote threshold: More than 2/3 of total stake has broadcast a prevote for the block hash
  2. Precommit threshold: More than 2/3 of total stake has broadcast a precommit
T = 2/3 Γ— total_stake + 1
finalized ⟺ prevotes β‰₯ T AND precommits β‰₯ T

Finalized blocks are irreversibleβ€”there are no chain reorganizations. This provides fast, deterministic finality for applications.

Epoch Transitions​

Mersennet may use epochs for validator set updates (e.g. applying pending stake changes, unbonding completions). At epoch boundaries:

  • Pending validator additions/removals are applied
  • Unbonding queues are processed
  • Validator set is updated for the next epoch

The exact epoch length is configurable. Validator set changes take effect at the start of the next epoch to ensure consensus continuity.

Slashing Mechanism​

Slashing Types​

TypeTriggerBase PenaltyConsequence
Double-signSigning two different blocks at same height5% of stakeTombstoned (permanent ban)
TimeoutFailing to precommit after prevoting1% of stakeJailed (temporary exclusion)

Escalation​

Penalties escalate with repeated offenses:

actual_penalty = base_bps + (escalation_step Γ— offense_count) + (escalation_step Γ— rounds_missed)
actual_penalty = min(actual_penalty, max_escalation_bps)

With default parameters:

  • escalation_step = 25 bps (0.25%)
  • max_escalation = 1000 bps (10%)

First timeout: 1%. Second: 1.25%. Third: 1.5%. And so on, up to 10%.

Slashing Source Priority​

When slashing is executed, tokens are taken from:

  1. Unbonding queue first β€” tokens being withdrawn
  2. Active stake second β€” if unbonding doesn't cover the penalty
  3. Remainder burned β€” if the validator doesn't have enough to cover

Tombstone vs. Jail​

  • Tombstoned: Permanently banned. Cannot rejoin.
  • Jailed: Temporarily excluded. Can unjail() after the jail period.

Block Structure​

Every finalized block contains the following fields:

FieldTypeDescription
numberu64Sequential block height starting from 0 (genesis)
hashB256Keccak-256 hash uniquely identifying this block
chain_idu64Network identifier (7919 for testnet)
state_rootB256Merkle root of the post-execution state trie
transactionsVec<Transaction>Ordered list of transactions included in the block
receiptsVec<Receipt>Execution receipts corresponding 1:1 with transactions
proposerAddressValidator address that proposed this block
coinbaseAddressAddress receiving block rewards (same as proposer)
gas_limitu64Maximum gas allowed in this block (default 30,000,000)
gas_usedu64Total gas consumed by all transactions
base_feeU256EIP-1559 base fee for this block (adjusts per block)
finalizedboolWhether 2/3+ stake committed to this block
consensusFinalizationBFT finalization data (prevotes, precommits, round info)
rewardsVec<Reward>Per-validator block reward distributions
total_rewardU256Sum of all rewards paid this block
burned_rewardU256Portion of rewards burned (e.g. slashed stake)
slashesVec<Slashing>Slashing events applied in this block
unbondedVec<Unbonding>Completed unbonding operations
domain_eventsVec<DomainEvent>PrimeOrders and bridge events emitted during execution

Receipt Format​

Each transaction produces a Receipt:

FieldTypeDescription
successboolWhether the transaction executed without revert
gas_usedu64Gas consumed by this transaction
outputBytesReturn data (ABI-encoded for contract calls)
created_addressOption<Address>Contract address if this was a deployment
errorOption<String>Human-readable error message on failure
logsVec<LogEntry>Emitted EVM logs (events)

Transaction Lifecycle​

A transaction moves through the following stages from submission to finalization:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ TRANSACTION LIFECYCLE β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

1. SUBMISSION
β”‚ Client sends signed transaction via JSON-RPC
β”‚ (eth_sendRawTransaction or prime_sendTransaction)
β”‚
2. VALIDATION
β”‚ β”œβ”€ Verify ECDSA signature (recover signer from r, s, v)
β”‚ β”œβ”€ Check chain_id matches (7919 for testnet)
β”‚ β”œβ”€ Verify nonce == account.nonce (no gaps, no replays)
β”‚ β”œβ”€ Verify sender balance β‰₯ value + gas_limit Γ— gas_price
β”‚ └─ Check gas_price β‰₯ base_fee (EIP-1559)
β”‚
3. MEMPOOL
β”‚ β”œβ”€ Insert into pending pool (max 10,000 total txs)
β”‚ β”œβ”€ Per-sender limit: 1,000 pending txs
β”‚ β”œβ”€ Replacement: new tx must bump gas_price by β‰₯10% (1000 bps)
β”‚ └─ Gossip transaction hash to connected peers
β”‚
4. BLOCK INCLUSION
β”‚ β”œβ”€ Proposer gathers txs from mempool ordered by gas_price
β”‚ β”œβ”€ Txs included up to block gas_limit (30M gas)
β”‚ └─ Proposer builds candidate block
β”‚
5. EVM EXECUTION
β”‚ β”œβ”€ Execute each tx sequentially in revm
β”‚ β”œβ”€ Apply state transitions (balance changes, storage writes)
β”‚ β”œβ”€ Process PrimeOrders precompile calls (if any)
β”‚ β”œβ”€ Generate receipt with logs, gas_used, status
β”‚ └─ Compute post-execution state_root
β”‚
6. CONSENSUS & FINALIZATION
β”‚ β”œβ”€ Proposer broadcasts block to validators
β”‚ β”œβ”€ Validators verify block, broadcast prevote
β”‚ β”œβ”€ On 2/3+ prevotes, broadcast precommit
β”‚ └─ On 2/3+ precommits, block is finalized (irreversible)
β”‚
7. RECEIPT
└─ Client queries receipt via eth_getTransactionReceipt
Receipt includes: status, gas_used, logs, created_address

Nonce Management​

Nonces enforce strict transaction ordering per account:

  • Each account has a monotonically increasing nonce starting at 0
  • Transaction with nonce N can only execute when the account's current nonce is N
  • If nonce N+1 arrives before N, it stays in the mempool queue until N executes
  • Sending a new transaction with the same nonce replaces the pending one (if gas price is bumped by β‰₯10%)

State Management​

Mersennet maintains EVM-compatible world state using a Merkle trie structure.

State Trie​

The world state is a mapping from addresses to account objects:

State Root (B256)
β”‚
β”œβ”€β”€ Account 0x1234...
β”‚ β”œβ”€β”€ nonce: u64
β”‚ β”œβ”€β”€ balance: U256
β”‚ β”œβ”€β”€ code_hash: B256 (keccak256 of contract bytecode)
β”‚ └── storage_root: B256 (root of account's storage trie)
β”‚
β”œβ”€β”€ Account 0x5678...
β”‚ └── ...
β”‚
└── ...

Each block produces a new state_root β€” the Merkle root computed over all account state after executing every transaction. This provides:

  • Integrity verification β€” Any node can verify state correctness by recomputing the root
  • Light client proofs β€” Merkle proofs can prove account balances without full state
  • Determinism β€” Same transactions on same pre-state always produce the same state root

Storage Model​

Account storage follows the EVM model: each contract has a 256-bit key β†’ 256-bit value mapping. Storage slots are accessed via SLOAD and SSTORE opcodes.

The current storage backend options are:

BackendDescriptionUse Case
sledEmbedded key-value store (default)Production nodes
redbRust-native embedded databaseAlternative production backend
memoryIn-memory (volatile)Testing and development

State Root Computation​

After each block, the state root is computed using a sorted Merkle tree:

  1. Collect all (address, account_data) pairs
  2. Sort by address (deterministic ordering)
  3. Hash each pair: keccak256(address || rlp(account))
  4. Build a binary Merkle tree from the leaf hashes
  5. The root hash becomes the block's state_root

Network Protocol​

Mersennet nodes communicate using a custom peer-to-peer protocol built on TCP and UDP.

Message Types​

Nodes exchange three categories of messages:

MessageTransportPurpose
TxTCP + UDP gossipPropagate new transactions to peers
BlockTCP syncBroadcast finalized blocks
VoteTCP + UDP gossipExchange prevote/precommit messages

Peer Discovery​

Nodes discover peers through:

  1. Seed nodes β€” Configured in p2p.peers (bootstrap addresses)
  2. Peer exchange β€” Connected nodes share their known peer lists
  3. Persistent peer store β€” Known peers are saved to peers.json for reconnection on restart

Block Propagation​

  Proposer Node                    Validator Node A              Validator Node B
β”‚ β”‚ β”‚
β”‚ 1. Produce block β”‚ β”‚
β”‚ 2. TCP broadcast ────────────── β”‚
β”‚ │──── TCP forward ──────────────
β”‚ β”‚ β”‚
β”‚ 3. Collect prevotes ◄────────── β”‚
β”‚ ◄──────────┼───────────────────────────────
β”‚ β”‚ β”‚
β”‚ 4. Collect precommits ◄──────── β”‚
β”‚ ◄───────┼───────────────────────────────
β”‚ β”‚ β”‚
β”‚ 5. Finalize & commit β”‚ Finalize & commit β”‚ Finalize & commit
β–Ό β–Ό β–Ό

Transport Layers​

LayerProtocolPurpose
TCP SyncTCPReliable block and state synchronization
UDP GossipUDPLow-latency transaction and vote propagation
Noise ProtocolOptionalEncrypted P2P communication (enable with noise_enabled: true)

Gossip Configuration​

Transaction and vote gossip uses configurable parameters:

  • Messages are forwarded to all connected peers
  • Duplicate message detection prevents re-broadcasting
  • Connection health is monitored with periodic pings

Configuration Reference​

Complete reference of all configuration parameters with their default values.

engine β€” Core Engine Settings​

ParameterTypeDefaultDescription
chain_idu647919Chain identifier (must match genesis)
state_pathstring"state"Directory for state storage
gas_limit_per_blocku6430000000Maximum gas per block (30M)
fee_elasticity_multiplieru642EIP-1559 elasticity multiplier
fee_max_change_denominatoru648Max base fee change per block (12.5%)
storage_backendstring"sled"Storage backend: "sled", "redb", or "memory"

mempool β€” Transaction Pool​

ParameterTypeDefaultDescription
max_totalusize10000Maximum transactions in the mempool
max_per_senderusize1000Maximum pending txs per sender address
bump_bpsu641000Minimum gas price bump for tx replacement (10%)

p2p β€” Peer-to-Peer Networking​

ParameterTypeDefaultDescription
node_key_pathstring"state/node_key.json"Path to the node identity key
peer_store_pathstring"state/peers.json"Path to persistent peer list
listenstring"0.0.0.0:30303"P2P listen address
peersstring[][]Seed peer addresses for bootstrap
block_time_msu641000Target block production interval in ms
noise_enabledboolfalseEnable Noise protocol encryption

rpc β€” JSON-RPC Server​

ParameterTypeDefaultDescription
enabledboolfalseEnable the HTTP JSON-RPC server
addrstring"127.0.0.1:8545"RPC listen address and port

ws β€” WebSocket Server​

ParameterTypeDefaultDescription
enabledboolfalseEnable the WebSocket server
addrstring"127.0.0.1:9945"WebSocket listen address and port

slashing β€” Slashing Parameters​

ParameterTypeDefaultDescription
double_sign_bpsu64500Base penalty for double-signing (5%)
timeout_bpsu64100Base penalty for timeout (1%)
escalation_step_bpsu6425Penalty increase per repeated offense (0.25%)
escalation_max_bpsu641000Maximum escalated penalty (10%)
round_timeout_msu64500Consensus round timeout in milliseconds
unbonding_periodu642Epochs before unbonded stake is withdrawable

token_economics β€” Reward & Supply​

ParameterTypeDefaultDescription
max_supplystring"1000000000000000000000000000"Maximum PRIM supply (1B Γ— 10¹⁸)
initial_reward_per_blockstring"10000000000000000000"Block reward (10 PRIM Γ— 10¹⁸)
halving_intervalu6435000000Blocks between reward halvings

prime_orders β€” PrimeOrders Precompile​

ParameterTypeDefaultDescription
initial_margin_bpsu640Initial margin requirement (basis points)
maintenance_margin_bpsu640Maintenance margin requirement (basis points)

bridge β€” Cross-Domain Bridge​

ParameterTypeDefaultDescription
max_queue_lenusize10000Maximum pending bridge messages

zk β€” Zero-Knowledge Proofs​

ParameterTypeDefaultDescription
enabledboolfalseEnable ZK proof generation
checkpoint_intervalu64100Blocks between ZK checkpoints

Example Full Configuration​

{
"engine": {
"chain_id": 7919,
"state_path": "state",
"gas_limit_per_block": 30000000,
"fee_elasticity_multiplier": 2,
"fee_max_change_denominator": 8,
"storage_backend": "sled"
},
"mempool": {
"max_total": 10000,
"max_per_sender": 1000,
"bump_bps": 1000
},
"p2p": {
"node_key_path": "state/node_key.json",
"peer_store_path": "state/peers.json",
"listen": "0.0.0.0:30303",
"peers": [
"46.225.30.187:30303"
],
"block_time_ms": 1000,
"noise_enabled": false
},
"rpc": {
"enabled": true,
"addr": "0.0.0.0:8545"
},
"ws": {
"enabled": true,
"addr": "0.0.0.0:9945"
},
"slashing": {
"double_sign_bps": 500,
"timeout_bps": 100,
"escalation_step_bps": 25,
"escalation_max_bps": 1000,
"round_timeout_ms": 500,
"unbonding_period": 2
},
"token_economics": {
"max_supply": "1000000000000000000000000000",
"initial_reward_per_block": "10000000000000000000",
"halving_interval": 35000000
},
"prime_orders": {
"initial_margin_bps": 0,
"maintenance_margin_bps": 0
},
"bridge": {
"max_queue_len": 10000
},
"zk": {
"enabled": false,
"checkpoint_interval": 100
}
}

Summary​

Mersennet's DPoS consensus provides:

  • Fast finality (~1s block time)
  • Fair proposer rotation (stake-weighted round-robin)
  • BFT security (2/3+ stake required)
  • Economic security (slashing for misbehavior)
  • No reversals (finalized blocks are final)