diff --git a/docs/Blockchain/CHANGELOG.md b/docs/Blockchain/CHANGELOG.md index 466c9a5..4a599a8 100644 --- a/docs/Blockchain/CHANGELOG.md +++ b/docs/Blockchain/CHANGELOG.md @@ -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/ИТХ/` — ежедневное закрытие блокчейна (чекпоинты): diff --git a/docs/ИТХ/README.md b/docs/ИТХ/README.md index 1e1227e..521eee3 100644 --- a/docs/ИТХ/README.md +++ b/docs/ИТХ/README.md @@ -60,6 +60,22 @@ Arweave почти не читается, это гарантия, а не ра каждый сервер публикует только своё мнение, а наблюдатель считает голоса сам. Сервер, который систематически ошибается, DAO просто исключает из списка. +Правило работает относительно списка: по умолчанию считают по DAO-списку, +но любой наблюдатель вправе считать голоса по **собственному списку доверенных +серверов**. Коротко: запись — по общему списку, чтение — по любому. + +## Форки и параллельные группы + +- Пользователь раздвоил свою цепочку до чекпоинта — дежурный возьмёт самую + длинную ветку, проигравшая отбросится; пользователь не наказывается. +- Ветка против уже зачекпоинченного — побеждает чекпоинт, ветка отбрасывается. +- Умудрился дописать противоречащие ветки в архивы **двух разных групп закрытий** — + цепочка замораживается навсегда (два неизменных архива слить нельзя), + дальше пользователь начинает новую цепочку `login-002`. +- Любая другая группа серверов может вести свою параллельную цепочку чекпоинтов + над теми же данными — это разрешено by design: подделать блоки они не могут, + а независимых «нотариусов» у истории становится больше. + ## Восстановление сервера с нуля 1. Прочитать DAO PDA → список серверов графика. diff --git a/docs/ИТХ/Спецификация_ИТХ_v1.md b/docs/ИТХ/Спецификация_ИТХ_v1.md index 4b82094..66981b1 100644 --- a/docs/ИТХ/Спецификация_ИТХ_v1.md +++ b/docs/ИТХ/Спецификация_ИТХ_v1.md @@ -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:00–06: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-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` — фиксировать изменения.