# Консенсус

> Як NeuroChain досягає остаточності — Malachite (Tendermint) BFT з голосами ML-DSA-65, вибір пропозера за стейком, перевірка кореня стану, slashing за подвійний підпис, наздоганяння і окремі екземпляри на шард.

Source: https://docs.nro.world/uk/learn/consensus

NeuroChain використовує **Malachite** (Informal Systems) — Tendermint BFT у вигляді бібліотеки Rust, вбудованої в ноду. Блок **остаточний**, щойно валідатори з понад 2/3 стейку дали за нього precommit: жодних форків, на які треба чекати, і жодних підтверджень, які треба рахувати.

| Властивість | Значення |
|-------------|----------|
| Алгоритм | Tendermint BFT: propose → prevote → precommit |
| Сила голосу | Стейк валідатора, в uNRO |
| Поріг коміту | Понад 2/3 усієї сили голосу |
| Стійкість до збоїв | До `f` несправних валідаторів із `3f + 1` |
| Підписи голосів і пропозицій | ML-DSA-65 (постквантовий) |
| Остаточність | Одразу після коміту |

## Одна висота крок за кроком

![Одна висота консенсусу: пропозер надсилає пропозицію зі списком TX, tx_root і попереднім коренем стану; валідатори перевіряють її, дають prevote, потім precommit; коли понад 2/3 стейку дали precommit, кожен валідатор застосовує блок; без кворуму вчасно починається новий раунд з новим пропозером](/diagrams/uk/consensus-height.svg)

1. **Propose.** Пропозер для `(height, round)` складає блок із mempool: список транзакцій, їхній корінь Меркла `tx_root` і `prev_state_root` — корінь стану, якого він досяг після попереднього блоку.
2. **Перевірка.** Кожен інший валідатор звіряє транзакції з `tx_root` і порівнює `prev_state_root` зі своїм коренем для попередньої висоти. Розбіжність означає, що стан однієї зі сторін розійшовся; валідатор відмовляється голосувати і пише в журнал `STATE DIVERGENCE`.
3. **Prevote, precommit.** Валідатори голосують за блок або за nil, якщо він недійсний чи не надійшов вчасно.
4. **Коміт.** Коли є precommit понад 2/3 сили голосу, блок ухвалено. Кожен валідатор застосовує його транзакції, ділить комісії, на висотах виплат нараховує винагороди й обчислює новий корінь стану — усе детерміновано, тож кожна чесна нода отримує той самий корінь.
5. **Без кворуму вчасно** висота переходить у наступний раунд з новим пропозером і довшими тайм-аутами.

Тайм-аути раундів — стандартні для Malachite. Темп між блоками задається для кожної ноди:

| Змінна | За замовчуванням | Значення |
|--------|------------------|----------|
| `NEUROCHAIN_BLOCK_MS` | 200 | Мінімальна пауза перед пропозицією блоку з транзакціями |
| `NEUROCHAIN_EMPTY_BLOCK_MS` | 2000 | Мінімальна пауза перед порожнім блоком, щоб простояча мережа не шуміла |

## Хто пропонує

Пропозер обирається **лише за стейком** і детерміновано, тож кожна нода отримує ту саму відповідь:

1. Беруться активні валідатори та їхні стейки; усі стейки ділять на найбільший спільний дільник, щоб важили лише пропорції.
2. `SHA3-256("neuro-proposer|" ‖ height ‖ round)` дає число, рівномірно розподілене по всій вазі.
3. Валідатори проходяться по черзі; пропонує той, у чий діапазон ваги потрапило число.

Валідатор з удвічі більшим стейком пропонує вдвічі частіше. Зміна раунду дає нове число, тож валідатор, що лежить, пропускається. Якщо ні в кого не лишилось стейку, вибір переходить на почергове, щоб мережа йшла далі.

## Набір валідаторів {#validator-set}

Валідатор бере участь у консенсусі, коли виконано **все** з переліченого:

- Його адреса закінчується на `.node`, і його консенсусний публічний ключ ML-DSA-65 записано в мережі.
- Його стейк щонайменше `min_stake` (за замовчуванням 10 000 NRO).
- Він не оштрафований і не в jail.
- У шардованій мережі він належить до цього шарду.

Набір щоразу виводиться зі стану мережі, тож зміни стейку, штрафи й нові реєстрації набувають сили через транзакції, які застосував кожен валідатор.

Для справжньої візантійської стійкості протоколу потрібно щонайменше **7 валідаторів** (`f = 2`). Нода з меншою кількістю попереджає про це під час старту; `GET /health` показує `validator_count`, `bft_quorum` (кількість `2f + 1` для поточного набору) і `bft_ready`. Для тестових мереж мінімум змінює `NEUROCHAIN_MIN_VALIDATORS`.

## Де AI може і не може діяти

Доказ безпеки Tendermint спирається на те, що сила голосу — це стейк. AI ніколи не повинен її змінювати.

| | Дозволено? | Стан |
|---|---|---|
| Змінити силу голосу або поріг 2/3 | **Ніколи** | Гарантовано: голоси рахує Malachite за стейком |
| Вивести валідатора (jail, slash → сила голосу 0) | Так — лише виведення, ніколи додаткова вага | Працює, спрацьовує на доказ подвійного підпису |
| Зміщувати вибір пропозера за довірою | Так, у принципі | **Не діє**: пропозер обирається лише за стейком |

**ConsensusAI** обчислює для кожного валідатора оцінку довіри за його недавньою участю. Зараз ця оцінка **показується, але не використовується**: її видно в `GET /api/v1/validators` і в журналі рішень оракула для кожного блоку. З вибору пропозера її прибрано, бо кожна нода рахувала участь зі свого погляду; після простою ці погляди розходились, ноди не погоджувались, хто може пропонувати, і вже не сходились. Повернути довіру у вибір пропозера можна лише на даних про участь, які самі узгоджені в консенсусі (коміт попереднього блоку, переданий у наступному).

## Slashing і jail {#slashing-and-jail}

Підпис двох різних голосів для тієї самої висоти й раунду — доказове порушення. Коли такий доказ подано й перевірено в консенсусі:

| Наслідок | За замовчуванням | Параметр управління |
|----------|------------------|---------------------|
| Штраф зі стейку | 5% від `min_stake` (не більше стейку валідатора) | `slash_fraction_double_sign_bps` = 500 |
| Винагорода тому, хто повідомив | 1% штрафу | `slash_reward_fraction_bps` = 100 |
| Решта | спалюється | — |
| Jail (сила голосу 0) | 10 000 блоків | `jail_blocks` (змінна `NEUROCHAIN_JAIL_BLOCKS`) |

Кожен доказ застосовується один раз. Після строку jail валідатор автоматично повертається в набір, якщо його стейк і далі щонайменше `min_stake`. Штрафи — у `GET /api/v1/slashing/history`.

## Перезапуск і наздоганяння

- **Перезапуск.** Malachite веде журнал попереднього запису. Перезапущений валідатор програє його і може ухвалити висоту, яку вже закомітив; нода розпізнає блок як застосований і продовжує роботу, а не зупиняється.
- **Наздоганяння.** Нода, що була офлайн, запитує в пірів пропущені ухвалені блоки — кожен із сертифікатом коміту й повним списком транзакцій — і застосовує їх по черзі так, ніби брала участь. Піри тримають для цього останні 10 000 ухвалених блоків (`NEUROCHAIN_SYNC_HISTORY_BLOCKS`). Нода, що відстала більше, лишається помітно позаду, а не копіює записи блоків, які не застосувала; стара синхронізація записів блоків тепер лише порівнює корені стану для блоків, які нода вже має.
- **Нова нода.** Може стартувати з підписаного знімка стану, а не програвати все від генезису — див. [Запуск ноди](/uk/operate/node).

## Шардинг <Badge type="warning" text="експериментально" /> {#sharding}

Мережу можна поділити на шарди, кожен зі **своїм екземпляром Malachite**, своїми валідаторами і своєю висотою:

- Акаунт або валідатор належить шарду `FNV-1a(адреса) mod n_shards`.
- Нода обслуговує шард із `NEUROCHAIN_SHARD_ID`; кількість шардів — `NEUROCHAIN_SHARDS`. Без `NEUROCHAIN_SHARD_ID` нода працює з одним шардом і міжшардових переказів немає.
- Переказ в інший шард — двофазний коміт: шард-джерело **блокує** суму; шард-призначення **зараховує** її після перевірки доказу Меркла про списання; якщо цього не сталося за 10 блоків (`RELAY_TIMEOUT_BLOCKS`), джерело **повертає** суму відправнику.

**Як тестується.** Модульні тести ноди покривають розподіл за шардами, фільтрацію mempool і голосів за шардом, шлях блокування → зарахування та повернення за тайм-аутом (`test_cross_shard_2pc_commit`, `test_cross_shard_2pc_timeout_refund`, запуск — `cargo test -p neurochain-node`). Мережеві прогони з кількома валідаторами поки були з одним шардом; прогону з кількома шардами й кількома валідаторами на шард ще не було. Експлуатація — у розділі [IBC v2 і шардинг](/uk/operate/ibc).

## Перевірте самі

```bash
curl -s "$NODE_RPC/health"                # висота, раунд, піри, validator_count, bft_quorum, bft_ready
curl -s "$NODE_RPC/api/v1/validators"     # стейк, оцінка довіри, запропоновані блоки, голоси
curl -s "$NODE_RPC/api/v1/blocks?limit=1" # останній блок: пропозер, tx_root, state_root
```
