Дополнить дизайн ИТХ: форки, списки доверия, децентрализация

- Раздел 6: правило признания — правило чтения (дефолт rota PDA,
  наблюдатель может считать по своему списку доверия); параллельные
  группы закрытий разрешены by design.
- Новый раздел 10 «Форки пользовательских цепочек»: fork-choice
  дежурного (длиннейшая ветка + tie-break), зачекпоинченное побеждает,
  заморозка chain_frozen только при противоречии архивов двух групп,
  рестарт с login-002, байт-в-байт повтор для клиентов.
- Шаг 9.9 проверки: конфликт -> принять версию чекпоинта (один
  пользователь больше не срывает закрытие дня).
- TODO: хэш-выбор дежурного, вход по депозиту, механизм цепочки -002.
- CHANGELOG блокчейна дополнен; формат блоков и AddBlock не менялись.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
AidarKC
2026-07-14 18:50:41 +04:00
co-authored by Claude Fable 5
parent 4cb98f7ca0
commit 172f007e1e
3 changed files with 120 additions and 13 deletions
+19
View File
@@ -1,5 +1,24 @@
# История изменений документации блокчейна
## 2026-07-14 18:43:18 +0400
- Базовый коммит-ориентир: `4cb98f7`.
- Дизайн ИТХ дополнен по итогам обсуждения (`docs/ИТХ/README.md`, `docs/ИТХ/Спецификация_ИТХ_v1.md`):
- правило признания явно объявлено правилом чтения: дефолт — rota PDA, но наблюдатель
может считать голоса по собственному списку доверия («запись — по общему списку,
чтение — по любому»); параллельные группы закрытий разрешены by design;
- новый раздел 10 «Форки пользовательских цепочек»: fork-choice дежурного
(длиннейшая ветка от зачекпоинченной вершины + tie-break по меньшему `hash32`),
зачекпоинченное всегда побеждает локальные ветки, заморозка цепочки (`chain_frozen`)
только при противоречии между архивами двух групп закрытий, рестарт с `login-002`;
- шаг 9.9 проверки изменён с «конфликт → отказ» на «конфликт → принять версию
чекпоинта»: один пользователь больше не может сорвать закрытие дня;
- правило для клиентов: повтор отправки блока — только байт-в-байт (дедупликация,
а не форк);
- TODO дополнен: детерминированный выбор дежурного через hash, вход в график
по депозиту, механизм новой цепочки `-002`.
- Формат блоков и правила `AddBlock` по-прежнему не изменены (код не реализован);
при реализации дописать `chain_frozen` в `docs/API/04_Add_Block_to_Blockchain_API.md`.
## 2026-07-13 14:04:59 +0400
- Базовый коммит-ориентир: `af12d7b`.
- Добавлена новая папка документации `docs/ИТХ/` — ежедневное закрытие блокчейна (чекпоинты):
+16
View File
@@ -60,6 +60,22 @@ Arweave почти не читается, это гарантия, а не ра
каждый сервер публикует только своё мнение, а наблюдатель считает голоса сам.
Сервер, который систематически ошибается, DAO просто исключает из списка.
Правило работает относительно списка: по умолчанию считают по DAO-списку,
но любой наблюдатель вправе считать голоса по **собственному списку доверенных
серверов**. Коротко: запись — по общему списку, чтение — по любому.
## Форки и параллельные группы
- Пользователь раздвоил свою цепочку до чекпоинта — дежурный возьмёт самую
длинную ветку, проигравшая отбросится; пользователь не наказывается.
- Ветка против уже зачекпоинченного — побеждает чекпоинт, ветка отбрасывается.
- Умудрился дописать противоречащие ветки в архивы **двух разных групп закрытий**
цепочка замораживается навсегда (два неизменных архива слить нельзя),
дальше пользователь начинает новую цепочку `login-002`.
- Любая другая группа серверов может вести свою параллельную цепочку чекпоинтов
над теми же данными — это разрешено by design: подделать блоки они не могут,
а независимых «нотариусов» у истории становится больше.
## Восстановление сервера с нуля
1. Прочитать DAO PDA → список серверов графика.
@@ -70,6 +70,11 @@ RotaV1
- Полнота относительно других серверов **не требуется**: блоки, которых у дежурного
не было, войдут в следующий чекпоинт. Проверяющие не считают «у меня есть больше»
причиной несогласия.
- **Выбор ветки (fork-choice дежурного)**: если из вершины `prevState[c]` растут
несколько валидных веток (пользователь подписал противоречащие блоки), дежурный
берёт самую длинную валидную; при равной длине — ветку с меньшим `hash32` первого
расходящегося блока (байтовое сравнение). Ветки, не растущие из `prevState[c]`,
кандидатами не являются. Подробнее о форках — раздел 10.
## 5. Формат чекпоинта (один файл / одна Arweave-транзакция)
@@ -198,6 +203,23 @@ CheckpointStateBlock (block_type = 60, block_version = 0)
> из rota PDA. Рекомендация клиентам: до достижения половины считать актуальным
> предыдущий признанный.
Правило признания — правило **чтения**, а не записи:
- дефолтное множество для подсчёта — список rota PDA;
- любой наблюдатель (клиент, сторонний сервер, восстановление) вправе применять то же
правило к **собственному списку доверенных серверов**; DAO-список — рекомендация
по умолчанию (конфиг сервера: `checkpoint.trust.logins`; пусто = rota PDA);
- серверы графика при закрытии и проверке обязаны использовать общий DAO-список,
иначе ротация теряет общую точку отсчёта. Коротко: **запись — по общему списку,
чтение — по любому**.
Параллельные группы закрытий допустимы by design: любая другая группа серверов может
вести собственную цепочку чекпоинтов над теми же данными (свой генезис, свои PDA-слоты,
свои Arweave-транзакции). Группы механически не пересекаются (разные `prev`-цепочки)
и не могут подделать содержимое блоков — только заверять историю или не включать её
часть. Запрещать это кодом сознательно не планируется; «официальность» DAO-группы
держится только на том, что её список — дефолт в клиентах.
## 7. Канал закрытий в Сиянии
- У каждого сервера графика — свой публичный канал `type=1`, slug `checkpoints`
@@ -222,7 +244,8 @@ CheckpointStateBlock (block_type = 60, block_version = 0)
1. В окне `D+1 00:0006:00` убедиться по rota PDA, что дежурный — я.
2. Определить предыдущий признанный чекпоинт (свой слот + слоты остальных, раздел 6);
загрузить его `state` (локальный кэш или Arweave). Для генезиса — пустой.
3. Зафиксировать срез: для каждой цепочки — локальная вершина `(lastNumber, lastHash)`.
3. Зафиксировать срез: для каждой цепочки — вершина выбранной ветки
`(lastNumber, lastHash)` по правилу fork-choice из раздела 4.
4. Собрать бандл по разделу 4, отсортировать по 5.4, посчитать `bundleRoot`.
5. Сформировать `state` по срезу, отсортировать, посчитать `stateRoot` (ИТХ).
6. Собрать preimage, `checkpointHash`, подписать серверным ключом.
@@ -254,19 +277,58 @@ CheckpointStateBlock (block_type = 60, block_version = 0)
владельца цепочки, действующим на день закрытия.
8. Пересчитать `stateRoot` по `state`; сверить (это и есть проверка ИТХ).
9. **Конфликты с локальными данными**: если локально для `(цепочка, номер)` есть блок
с другим `hash32` — отказ (v1 форки автоматически не разруливает; см. TODO).
«У меня есть блоки, которых нет в бандле» — НЕ отказ.
с другим `hash32` — побеждает версия чекпоинта: локальная ветка отбрасывается
(реорганизация), согласие НЕ отклоняется. Отказ по конфликту возможен только когда
чекпоинт противоречит уже признанному прошлому — это ловится шагом 7 (мостик не
соединится с `prevState`). «У меня есть блоки, которых нет в бандле» — тоже НЕ отказ.
10. Импортировать недостающие блоки бандла в локальное хранилище тем же валидационным
путём, что и межсерверная синхронизация (`sync-between-servers.md`).
11. Успех → обновить свой PDA-слот (`role=2`) и поставить лайк на пост S.
Отказ → не делать ничего on-chain; подробно залогировать причину
(рекомендуемый префикс лога: `CHECKPOINT_REJECT`).
Проверка детерминирована: два честных сервера с любым набором локальных блоков
приходят к одному вердикту, кроме шага 9 (локальный конфликт), который сигналит
о реальном форке пользовательской цепочки.
Проверка детерминирована: два честных сервера с любым набором локальных блоков приходят
к одному вердикту. Проверяется валидность выбора дежурного и стыковка с прошлым
чекпоинтом, но не «длиннейшесть» выбранной ветки — её никто, кроме дежурного, проверить
не может, и условием согласия она не является.
## 10. Восстановление сервера с нуля
## 10. Форки пользовательских цепочек
Форк = два блока одной цепочки с одинаковым `blockNumber`, разным `hash32` и валидными
подписями владельца. Случайно-честно такое не возникает: каждый блок подписывается
владельцем и ссылается на `prevHash`. Пара таких блоков — самодостаточная
криптографическая улика, проверяемая любым сервером локально, без арбитра и голосования.
Лесенка разруливания:
| Ситуация | Арбитр | Исход |
| --- | --- | --- |
| Ветки разошлись до чекпоинта | Дежурный (длиннейшая + tie-break, раздел 4) | Проигравшая ветка отбрасывается, цепочка живёт |
| Локальная ветка против зачекпоинченной в своей группе | Цепочка чекпоинтов | Локальная ветка отбрасывается (шаг 9.9) |
| Чекпоинт противоречит признанному прошлому | Проверяющие | Согласий нет, день пропущен |
| Противоречие между архивами двух групп закрытий | Никто — улика | Заморозка цепочки |
Заморозка применяется **только** в последнем случае — когда обе ветки уже зачекпоинчены
разными группами закрытий и неизменны в Arweave: слить два неизменных архива невозможно
в принципе.
- Улика: два конфликтующих блока, каждый из которых входит в валидный чекпоинт
своей группы.
- Сервер, проверивший улику, перестаёт принимать `AddBlock` в эту цепочку
(код отказа: `chain_frozen`); улика синхронизируется партнёрам как обычные данные,
каждый проверяет и замораживает независимо.
- Уже записанная история цепочки остаётся валидной и читаемой навсегда.
- Пользователь продолжает с новой цепочкой `<login>-<NNN+1>` (например, `login-002`);
`BlockchainRegistryBlock` в `user_pda` уже поддерживает несколько цепочек.
Старые связи/подписки указывают на корни старой цепочки — репутационная история
не переносится, это осознанная часть последствий.
Обязательное правило для клиентов (защищает честных пользователей от гонок):
повтор отправки блока после таймаута/ошибки — только **байт-в-байт** тем же блоком.
Байт-идентичный дубль — дедупликация, а не форк. Пересборка блока с новым `timestamp`
на тот же номер создаёт форк на ровном месте.
## 11. Восстановление сервера с нуля
1. Прочитать rota PDA → список серверов.
2. Прочитать их PDA-слоты → признанный чекпоинт (правило раздела 6).
@@ -274,14 +336,14 @@ CheckpointStateBlock (block_type = 60, block_version = 0)
от генезиса вверх, валидируя каждый чекпоинт по разделу 9 (шаги 2–8).
4. Хвост после последнего чекпоинта добрать живой синхронизацией с серверами.
## 11. Стоимость и ключи
## 12. Стоимость и ключи
- Каждый сервер платит сам: Arweave — за свои дежурные дни, Solana — за свои слоты.
- Arweave JWK сервера хранится только на хосте (например,
`/home/player/SHiNE/secrets/…`), путь задаётся конфигом; в git не попадает —
по той же схеме, что `test.freeAvatar.walletJwkPath`.
## 12. Ориентир для реализации на сервере (не нормативно)
## 13. Ориентир для реализации на сервере (не нормативно)
- `CheckpointScheduler` — таймер окон 00:00/06:00 UTC, определение роли по rota PDA.
- `CheckpointBuilder` — срез, бандл, меркл, preimage, подпись (разделы 4–5, 8).
@@ -294,13 +356,21 @@ CheckpointStateBlock (block_type = 60, block_version = 0)
- Локально хранить: `state` последнего признанного чекпоинта + его
`(dayIndex, hash, arweaveTxId)` — чтобы не ходить в Arweave на каждое закрытие.
## 13. TODO / сознательно отложено
## 14. TODO / сознательно отложено
- **История ключей при ротации**: если сервер/пользователь меняет ключ несколько раз
за день, для проверки старых подписей нужна история версий ключей. v1 живёт с
«ключ, актуальный на день закрытия»; при внедрении ротации — хранить версии.
- **Разруливание форков** пользовательских цепочек (шаг 9.9): правило «наиболее
длинная валидная цепочка» + детерминированный tie-break. v1 — отказ и лог.
- **Механизм новой цепочки `-002`**: пользовательский flow «продолжить с новой
цепочкой» после заморозки (регистрация в `BlockchainRegistryBlock`, UI) пока
не реализован — см. раздел 10.
- **Детерминированный выбор дежурного**:
`дежурный = SHA-256(prevCheckpointHash || dayIndex) mod N` по байтово отсортированным
логинам — убирает порядок списка как рычаг управления, DAO остаётся только
вход/выход участников.
- **Вход в график по депозиту** вместо редактирования списка DAO: permissionless
rota PDA, вступление под залог SOL, выход с возвратом после таймаута — сибил-фильтр
становится экономическим (инфраструктурно близко к `shine_payments`).
- **Автопропуск штрафника** в ротации (2 подряд закрытия без единого согласия).
- **On-chain кворум** вместо правила чтения «>половины» — если понадобится.
- **Сжатие бандла** (gzip) и **чанкование** очень больших бандлов на несколько
@@ -308,11 +378,13 @@ CheckpointStateBlock (block_type = 60, block_version = 0)
- Экономия Solana-транзакций: считать согласия только по лайкам канала — возможно
после обкатки v1.
## 14. Связанные документы (обновлять при реализации)
## 15. Связанные документы (обновлять при реализации)
- `docs/Blockchain/01_Common_Block_Format.md` — формат блока (используется в 5.4, 9.7).
- `docs/Blockchain/02_Channel_Commands.md` — дописать `/.checkpoint`.
- `docs/Blockchain/sync-between-servers.md` — путь импорта блоков (9.10).
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md` — дописать
`CheckpointStateBlock` и rota PDA при реализации Solana-части.
- `docs/API/04_Add_Block_to_Blockchain_API.md` — при реализации дописать код отказа
`chain_frozen` (раздел 10).
- `docs/Blockchain/CHANGELOG.md` — фиксировать изменения.