Consensus
NeuroChain runs Malachite (Informal Systems) — Tendermint BFT as a Rust library, embedded in the node. A block is final as soon as validators holding more than 2/3 of the stake have precommitted it: there are no forks to wait out and no confirmations to count.
| Property | Value |
|---|---|
| Algorithm | Tendermint BFT: propose → prevote → precommit |
| Voting power | Stake of the validator, in uNRO |
| Commit threshold | More than 2/3 of total voting power |
| Fault tolerance | Up to f faulty validators out of 3f + 1 |
| Vote and proposal signatures | ML-DSA-65 (post-quantum) |
| Finality | Immediate on commit |
One height, step by step
- Propose. The proposer for
(height, round)builds a block from the mempool: the transaction list, its Merkle roottx_root, andprev_state_root— the state root it reached after the previous block. - Validate. Every other validator checks the transactions against
tx_rootand comparesprev_state_rootwith its own root for the previous height. A mismatch means one side's state has diverged; the validator refuses to prevote and logsSTATE DIVERGENCE. - Prevote, precommit. Validators vote for the block, or for nil if it is invalid or did not arrive in time.
- Commit. With precommits from more than 2/3 of voting power, the block is decided. Each validator applies its transactions, splits the fees, pays staking rewards on reward heights, and computes the new state root — all deterministically, so every honest node ends at the same root.
- No quorum in time moves the height to the next round, with a new proposer and longer timeouts.
Round timeouts are Malachite's defaults. The pace between blocks is set per node:
| Variable | Default | Meaning |
|---|---|---|
NEUROCHAIN_BLOCK_MS | 200 | Minimum wait before proposing a block that carries transactions |
NEUROCHAIN_EMPTY_BLOCK_MS | 2000 | Minimum wait before proposing an empty block, so an idle chain stays quiet |
Who proposes
The proposer is chosen by stake alone, deterministically, so every node computes the same answer:
- Take the active validators and their stakes; divide all stakes by their greatest common divisor, so only proportions matter.
- Hash
SHA3-256("neuro-proposer|" ‖ height ‖ round)to get a number spread evenly over the total weight. - Walk the validators in order; the one whose weight range contains that number proposes.
A validator with twice the stake proposes twice as often. A round change picks a fresh number, so a validator that is down is skipped. If no validator has any stake left, selection falls back to round-robin so the chain keeps moving.
Validator set
A validator takes part in consensus when all of these hold:
- Its address ends in
.node, and its ML-DSA-65 consensus public key is on chain. - Its stake is at least
min_stake(default 10 000 NRO). - It is not slashed and not jailed.
- In a sharded network, it belongs to this shard.
The set is re-derived from chain state, so stake changes, slashes and new registrations take effect through transactions every validator has applied.
The protocol needs at least 7 validators (f = 2) for real Byzantine fault tolerance. A node with fewer logs a warning at start; GET /health reports validator_count, bft_quorum (the 2f + 1 count for the current set) and bft_ready. Override the minimum for test networks with NEUROCHAIN_MIN_VALIDATORS.
Where AI may and may not act
The safety proof of Tendermint rests on voting power being stake. AI must never change it.
| Allowed? | Status | |
|---|---|---|
| Changing voting power or the 2/3 threshold | Never | Enforced: votes are counted by Malachite from stake |
| Removing a validator (jail, slash → voting power 0) | Yes — removal only, never extra weight | Live, triggered by double-sign evidence |
| Biasing proposer selection by trust | Yes in principle | Not active: proposer choice is stake-only |
ConsensusAI computes a trust score for each validator from its recent participation. Today that score is reported, not used: you see it in GET /api/v1/validators and in the oracle decision log for every block. It was taken out of proposer selection because each node counted participation from its own view; after an outage those views drifted apart, nodes disagreed on who may propose, and never re-converged. Bringing trust back into proposer choice needs participation data that is itself agreed in consensus (the previous block's commit carried inside the next block).
Slashing and jail
Signing two different votes for the same height and round is provable misbehaviour. When such evidence is submitted and verified in consensus:
| Effect | Default | Governance parameter |
|---|---|---|
| Stake slashed | 5% of min_stake (capped at the validator's stake) | slash_fraction_double_sign_bps = 500 |
| Reporter reward | 1% of the slashed amount | slash_reward_fraction_bps = 100 |
| Remainder | burned | — |
| Jail (voting power 0) | 10 000 blocks | jail_blocks (env NEUROCHAIN_JAIL_BLOCKS) |
Each piece of evidence is applied once. After the jail period the validator returns to the set automatically, provided its stake is still at least min_stake. Slashes are listed at GET /api/v1/slashing/history.
Restarts and catch-up
- Restart. Malachite keeps a write-ahead log. A restarted validator replays it and may decide a height it had already committed; the node recognises the block as applied and carries on instead of halting.
- Catch-up. A node that was offline asks peers for the decided blocks it missed — each with its commit certificate and full transaction list — and applies them in order, exactly as if it had taken part. Peers keep the last 10 000 decided blocks for this (
NEUROCHAIN_SYNC_HISTORY_BLOCKS). A node further behind than that stays visibly behind rather than copying block records it has not applied; the old block-record sync now only compares state roots for blocks the node already holds. - Fresh node. A new node can start from a signed state snapshot instead of replaying from genesis — see Run a node.
Sharding experimental
A network can be split into shards, each running its own Malachite instance with its own validators and block height:
- An account or validator belongs to shard
FNV-1a(address) mod n_shards. - A node serves the shard in
NEUROCHAIN_SHARD_ID; the shard count isNEUROCHAIN_SHARDS. WithoutNEUROCHAIN_SHARD_ID, the node runs a single shard and there are no cross-shard transfers. - A transfer to another shard is a two-phase commit: the source shard locks the value; the destination shard commits it after checking a Merkle proof of the debit; if that has not happened within 10 blocks (
RELAY_TIMEOUT_BLOCKS), the source refunds the sender.
How it is tested. Unit tests in the node cover shard assignment, shard-filtered mempool and votes, the lock → commit path and the timeout refund (test_cross_shard_2pc_commit, test_cross_shard_2pc_timeout_refund, run with cargo test -p neurochain-node). Multi-validator network runs so far used a single shard; a run with several shards and several validators per shard has not yet been done. See IBC v2 & sharding for operation.
Check it yourself
curl -s "$NODE_RPC/health" # height, round, peers, validator_count, bft_quorum, bft_ready
curl -s "$NODE_RPC/api/v1/validators" # stake, trust score, blocks proposed, votes cast
curl -s "$NODE_RPC/api/v1/blocks?limit=1" # last block: proposer, tx_root, state_root