Перенести документацию в docs и добавить социальный граф

This commit is contained in:
AidarKC
2026-07-13 12:58:41 +04:00
parent c9cfb394d7
commit af12d7b954
91 changed files with 653 additions and 2 deletions
@@ -0,0 +1,25 @@
# Форматы блокчейнов и блоков (индекс раздела)
Этот раздел разбит на несколько файлов, чтобы формат добавляемых блоков было проще читать и сопровождать.
## Структура раздела
- `01_Common_Block_Format.md` — общий бинарный формат блока (Frame v0), правила подписи и проверки.
- `02_Blockchain_Kinds_and_Lines.md` — виды блокчейнов и логические линии внутри цепочки.
- `10_TECH_Blocks.md` — системные блоки (`type=0`).
- `11_TEXT_Blocks.md` — текстовые блоки (`type=1`).
- `12_REACTION_Blocks.md` — реакции (`type=2`).
- `13_CONNECTION_Blocks.md` — связи/подписки (`type=3`).
- `14_USER_PARAM_Blocks.md` — пользовательские параметры (`type=4`).
## Быстрая карта типов
- `type=0` — TECH: HEADER, CREATE_CHANNEL.
- `type=1` — TEXT: POST/EDIT_POST/REPLY/EDIT_REPLY/REPOST.
- `type=2` — REACTION: LIKE/UNLIKE.
- `type=3` — CONNECTION: FRIEND/CONTACT/FOLLOW/SPOUSE/PARENT/CHILD/SIBLING и обратные операции.
- `type=4` — USER_PARAM: key/value-параметры пользователя.
## Примечание
Если нужно добавить новый тип или подтип блока, сначала обновляйте профильный файл этого раздела, затем API-документацию в `Dev_Docs/API`.
@@ -0,0 +1,40 @@
# Типы каналов и CreateChannel
## 1. Формат `CreateChannelBody` (`msg_type=0`, `subType=1`, `version=1`)
Payload включает:
1. line-поля канала (`lineCode`, `prevLineNumber`, `prevLineHash32`, `thisLineNumber`);
2. `channelName`;
3. `channelDescription`;
4. `channelType` (`uint16`, 2 байта);
5. `channelTypeVersion` (`uint16`, 2 байта).
## 2. Типы каналов
- `0``stories` (root-канал пользователя).
- `1` — публичный канал.
- `100` — персональный канал.
- `200` — групповой/чатовый канал.
Версия типа (`channelTypeVersion`) сейчас используется со значением `1`.
Важно для MVP:
- `100` и `200` в формате поддерживаются, но в текущем UI не используются.
- В MVP рабочий UI-флоу — каналы `0` и `1`.
## 3. Имя root-канала
- Root-канал (`line_code = 0`) в API/чтении отображается как `stories`.
- Публикации в `stories` разрешены владельцу собственного блокчейна.
## 4. Уникальность имени канала
Проверка уникальности выполняется на сервере по ключу:
`owner_bch_name + channel_type_code + slug(channel_name)`
Это означает:
- одно и то же имя допустимо у одного владельца для разных типов (`1`, `100`, `200`);
- в рамках одного типа и одного владельца имя уникально.
## 5. Персональные каналы (`type=100`)
- Сервер не проверяет бизнес-валидность собеседника по имени канала.
- Проверка существования login для персонального канала выполняется на UI при создании.
- При чтении сервер пытается собрать парный поток `A->B` + `B->A` (если обратный канал существует).
+49
View File
@@ -0,0 +1,49 @@
# Общий формат добавляемого блока (Frame v0)
Этот файл описывает **единый бинарный формат** блока, который клиент отправляет через `AddBlock` в поле `blockBytesB64`.
## 1. Полная структура блока
Блок состоит из двух частей:
1. **PREIMAGE** (подписывается)
2. **TAIL** (маркер подписи + подпись)
### PREIMAGE
- `frameCode (uint16)`
- `prevHash32 (32 bytes)`
- `blockSize (int32)` — размер PREIMAGE
- `blockNumber (int32)`
- `timestamp (int64)`
- `type (uint16)`
- `subType (uint16)`
- `version (uint16)`
- `bodyBytes (N)`
### TAIL
- `sigMarker (uint16)`
- `signature64 (64 bytes, Ed25519)`
## 2. Что проверяет сервер при AddBlock
- `frameCode` должен быть `0x0000`.
- `sigMarker` должен быть `0x0100`.
- `blockNumber` должен идти строго по порядку (`last + 1`).
- `prevHash32` должен совпасть с вершиной цепочки на сервере.
- `body` должен пройти `check()` для конкретного типа.
- подпись должна валидироваться публичным ключом блокчейна.
## 3. Ограничения
- максимальный полный размер блока: до 4 MiB;
- timestamp не должен сильно уходить в будущее;
- `bodyBytes` парсится по `type/subType/version` из заголовка блока.
## 4. Почему это важно
Одинаковый общий формат позволяет:
- передавать разные виды записей через один RPC `AddBlock`;
- валидировать блоки единообразно;
- расширять типы `body`, не ломая каркас блока.
@@ -0,0 +1,47 @@
# Виды блокчейнов и логических линий
## 1. Именованный блокчейн
Базовый идентификатор цепочки пользователя:
- `blockchainName = <login>-<NNN>`
- пример: `alice-001`
Обычно это одна основная цепочка пользователя.
## 2. Логические линии внутри одной цепочки
Физически цепочка одна, но внутри есть независимые логические последовательности (линии), которые ведутся через поля:
- `lineCode`
- `prevLineNumber`
- `prevLineHash32`
- `thisLineNumber`
Линии используются для:
- TECH-событий;
- каналов с текстовыми постами;
- связей и подписок;
- пользовательских параметров.
## 3. Правила line-полей (фактическая серверная валидация)
Line-поля: `lineCode`, `prevLineNumber`, `prevLineHash32`, `thisLineNumber`.
- Line-поля разрешены только для `msg_type`: `0`, `1`, `3`, `4`.
- Если передано хотя бы одно line-поле, должны быть переданы все 4.
- `prevLineNumber/prevLineHash32` должны указывать на существующий блок этой же цепочки.
- Для первого шага после root (`prevLineNumber == lineCode`):
- `TEXT (msg_type=1)`: `thisLineNumber = 0`;
- `TECH/CONNECTION/USER_PARAM (0/3/4)`: `thisLineNumber = 1`.
- Для обычного шага:
- `TEXT`: `thisLineNumber` допускает `same` или `+1` от предыдущего блока линии;
- `TECH/CONNECTION/USER_PARAM`: строго `+1`.
## 4. Root-идея для каналов и подписок
Для ссылок вида follow/friend/contact принято ссылаться на корневые блоки:
- `HEADER` для базовой сущности пользователя/канала `0`;
- `CREATE_CHANNEL` для пользовательских каналов.
Так ссылки остаются стабильными, даже когда в канале появляются новые сообщения.
+33
View File
@@ -0,0 +1,33 @@
# Командные сообщения каналов
## 1. Общий префикс
Командные сообщения распознаются по префиксу:
`/.`
Пример:
- `/.desc Новый комментарий канала`
## 2. Поддерживаемые команды
### Для всех типов каналов (`0`, `1`, `100`, `200`)
- `/.desc <text>` — смена описания канала.
Примечание:
- Описание канала в чтении определяется последней командой `/.desc` в линии канала.
- Если `/.desc` не было, используется описание из `CreateChannel`.
### Дополнительно для `type=200`
- `/.add <login> <channelName>`
- `/.remove <login> <channelName>`
Формат аргументов фиксирован: через пробел.
## 3. Текущая модель применения
- Команды передаются как обычные `TEXT_POST` сообщения.
- Сервер уже применяет `/.desc` при вычислении актуального описания канала.
- Команды `/.add` и `/.remove` зарезервированы под расширенную модель участников `type=200` на уровне UI/агрегации.
## 4. Статус для MVP
- В текущем UI каналы `type=100` и `type=200` не используются.
- Соответственно, `/.add` и `/.remove` считаются запланированными и пока не участвуют в рабочем UI-сценарии.
+18
View File
@@ -0,0 +1,18 @@
# TECH блоки (`type=0`, `version=1`)
TECH-тип покрывает системные записи цепочки.
## Подтипы
1. `subType=0``HEADER_COMPAT`
- стартовый блок цепочки;
- payload: tag `SHiNE` + login владельца.
2. `subType=1``TECH_CREATE_CHANNEL`
- создание нового канала;
- хранит line-поля + `channelName` + `channelDescription` + `channelType` + `channelTypeVersion`.
## Назначение
- инициализация блокчейна;
- управление набором каналов пользователя.
+38
View File
@@ -0,0 +1,38 @@
# TEXT блоки (`type=1`, `version=1`)
TEXT-тип хранит сообщения и редактирования.
## Подтипы
1. `subType=10``TEXT_POST`
- пост в линии канала;
- содержит line-поля + текст.
2. `subType=11``TEXT_EDIT_POST`
- редактирование поста;
- line-поля + target на оригинальный POST + новый текст.
3. `subType=20``TEXT_REPLY`
- ответ на сообщение;
- target (`toBlockchainName`, `toBlockGlobalNumber`, `toBlockHash32`) + текст.
4. `subType=21``TEXT_EDIT_REPLY`
- редактирование ответа;
- target на исходный REPLY + новый текст.
- допускается пустой `text` для логического удаления сообщения (без физического удаления блока).
5. `subType=30``TEXT_REPOST`
- репост сообщения в линию канала;
- содержит line-поля + target на оригинальное сообщение + текст комментария;
- на текущем этапе продуктовой логики репост не редактируется (версии не накапливаются);
- временно отключён для записи через `AddBlock` до будущей реализации репостов.
## Правило для edit
`EDIT_POST` и `EDIT_REPLY` должны ссылаться на **оригинальный** блок, а не на предыдущий edit.
## Пустой text в edit
- Для `TEXT_EDIT_POST` и `TEXT_EDIT_REPLY` допустим `textLen=0`.
- Такой edit трактуется как логическое удаление содержимого сообщения.
- Для удаления используется именно edit-блок; отдельного `DELETE`-подтипа нет.
+14
View File
@@ -0,0 +1,14 @@
# REACTION блоки (`type=2`, `version=1`)
## Подтипы
1. `subType=1``REACTION_LIKE`
- лайк на целевой блок;
- хранит target: `toBlockchainName`, `toBlockGlobalNumber`, `toBlockHash32`.
2. `subType=2``REACTION_UNLIKE`
- снятие лайка с целевого блока;
- хранит target: `toBlockchainName`, `toBlockGlobalNumber`, `toBlockHash32`.
## Назначение
- реакция на текстовые сообщения (и потенциально другие target-блоки, если это разрешено бизнес-логикой).
+29
View File
@@ -0,0 +1,29 @@
# CONNECTION блоки (`type=3`, `version=1`)
CONNECTION-тип описывает социальные связи и подписки.
## Подтипы
`10/11``close_friend / unclose_friend` (близкий друг)
`20/21``contact / uncontact` (контакт)
`30/31``follow / unfollow` (подписан)
`40/41``spouse / unspouse` (супруг/супруга)
`50/51``parent / unparent` (родитель)
`52/53``child / unchild` (ребёнок)
`54/55``sibling / unsibling` (брат/сестра)
`60/61``known_person / unknown_person` (знаю этого человека)
`70/71``shine_confirmed / shine_unconfirmed` (точно уверен, что сияющий)
`74/75``shine_seen / shine_unseen` (мало знаком, но видел сияющим)
## Общий формат payload
- line-поля (`lineCode`, `prevLineNumber`, `prevLineHash32`, `thisLineNumber`)
- target (`toBlockchainName`, `toBlockGlobalNumber`, `toBlockHash32`)
## Правила target
- FRIEND/CONTACT обычно указывают на `HEADER` цели (`block 0`).
- FOLLOW указывает на root канала:
- `HEADER` для канала `0`;
- `CREATE_CHANNEL` для пользовательского канала.
- Для остальных типов связи (`SPOUSE/PARENT/CHILD/SIBLING`) используется тот же target-формат.
+14
View File
@@ -0,0 +1,14 @@
# USER_PARAM блоки (`type=4`, `version=1`)
## Подтипы
1. `subType=1``USER_PARAM_TEXT_TEXT`
- хранит line-поля + `paramKey` + `paramValue`.
## Назначение
- сохранение пользовательского состояния (настройки клиента, синк-метки, курсоры чтения и т.д.).
## Практика
Для сложных структур удобно хранить JSON-строку в `paramValue` с версией схемы.
+64
View File
@@ -0,0 +1,64 @@
# История изменений документации блокчейна
# 2026-06-26 17:45:18 +0400
- Базовый коммит-ориентир: `44a1ba0`.
- На `t.shineup.me` подтверждена рабочая схема startup sync и full-resync:
- после рестарта сервер добивает `BlockchainTmpRecovery` и `BlockchainResyncRecovery`;
- `aidartest-001` успешно подтягивается с `shineup.me`;
- итоговое локальное состояние по `aidartest-001` дошло до `last_block_number=13`.
- В `Dev_Docs/Blockchain/sync-between-servers.md` добавлен практический результат ручной проверки на тестовом сервере.
## 2026-06-26 17:03:22 +0400
- Базовый коммит-ориентир: `71fdee0`.
- Обычный `AddBlock` переведён на crash-safe схему через временный кандидат `<blockchainName>.tmp_bch`, sidecar `<blockchainName>.write_check` и marker `<blockchainName>.write_pending`.
- `BlockchainTmpRecoveryOnStartup` теперь разбирает marker-driven recovery для обычной записи блока:
- если marker есть, recovery либо завершает swap tmp -> main, либо удаляет мусор;
- если marker нет, временные артефакты считаются мусором и удаляются.
- В `Dev_Docs/Blockchain/sync-between-servers.md` добавлено описание обычного `AddBlock` recovery и разделение между `write_pending` и `resync_pending`.
## 2026-05-24 11:40:00 +0300
- Базовый коммит-ориентир: `abdce05`.
- `TEXT_REPOST (subType=30)` оставлен как зарезервированный формат, но новые блоки репоста временно отключены на уровне `AddBlock`.
- В `11_TEXT_Blocks.md` зафиксировано, что запись `TEXT_REPOST` временно не используется до будущей реализации.
- В `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md` добавлен код отказа `repost_disabled`.
## 2026-05-21 19:05:00 +0300
- Базовый коммит-ориентир: `5344c42`.
- Добавлен новый TEXT-подтип `TEXT_REPOST (subType=30)`:
- обновлён перечень типов в `11_TEXT_Blocks.md`;
- обновлена быстрая карта типов в `00_Blockchain_Formats_and_Block_Types.md`.
- Уточнено API-описание поддержанных подтипов в `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`.
- В документе `Dev_Docs/API/08_MCP_Чтение_и_дозапись_персонального_публичного_чата.md` зафиксировано, что чтение канала учитывает `TEXT_POST` и `TEXT_REPOST`.
## 2026-05-20 11:34:17 +0300
- Базовый коммит-ориентир: `a53444b`.
- В `13_CONNECTION_Blocks.md` добавлены новые CONNECTION подтипы:
- `60/61``known_person / unknown_person` (знаю этого человека);
- `70/71``shine_confirmed / shine_unconfirmed` (точно уверен, что сияющий);
- `74/75``shine_seen / shine_unseen` (мало знаком, но видел сияющим).
- Обновлён список CONNECTION-подтипов в `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`.
## 2026-05-19 20:30:21 +0300
- Базовый коммит-ориентир: `7986184`.
- Уточнён документ `11_TEXT_Blocks.md`: для `TEXT_EDIT_POST` и `TEXT_EDIT_REPLY` зафиксировано, что `textLen=0` допустим и трактуется как логическое удаление сообщения.
- Явно закреплено, что отдельного `DELETE`-подтипа нет, удаление выполняется edit-блоком.
## 2026-05-19 00:22:46 +0300
- Базовый коммит-ориентир: `c27da63a3e65`.
- Актуализирован `README.md` как точка входа для MVP-документации по протоколу.
- В документации явно зафиксировано, что `channelType=100` и `channelType=200` присутствуют в формате, но пока не используются в UI.
- Актуализирован перечень REACTION-подтипов: добавлен `REACTION_UNLIKE (subType=2)`.
- Актуализирован перечень CONNECTION-подтипов: добавлены `SPOUSE/PARENT/CHILD/SIBLING` и обратные операции.
- В документ `02_Blockchain_Kinds_and_Lines.md` добавлены фактические серверные правила валидации line-полей.
- Обновлён корневой `AGENTS.md`: формат блокчейна менять только после явного подтверждения пользователя и с предварительным предупреждением.
## 2026-05-13 00:02:32 +0300
- Базовый коммит-ориентир: `f63f40f1eb2f`.
- Добавлен текущий формат `CreateChannelBody` с полями `channelType (2 байта)` и `channelTypeVersion (2 байта)`.
- Зафиксированы типы каналов: `0=stories`, `1=public`, `100=personal`, `200=group`.
- Серверная уникальность имени канала изменена на `owner + type + name(slug)`.
- Root-канал `0` переименован в `stories` на уровне API-чтения.
- Для персонального канала (`type=100`) включена сборка парного потока при чтении (`A->B` + `B->A`, если существует).
- Добавлена поддержка командного префикса `/.` и команды `/.desc` для актуализации описания канала при чтении.
- Зафиксированы команды `/.add` и `/.remove` для каналов `type=200` (зарезервировано под расширение участниками).
- В `AGENTS.md` добавлено обязательное правило актуализации документации в `Dev_Docs/Blockchain/`.
+34
View File
@@ -0,0 +1,34 @@
# Документация блокчейна SHiNE (MVP)
Этот каталог описывает только текущий рабочий формат протокола для MVP.
## Основные документы
1. [01_Common_Block_Format.md](./01_Common_Block_Format.md)
Единый бинарный формат блока (Frame v0), подпись, базовые проверки.
2. [02_Blockchain_Kinds_and_Lines.md](./02_Blockchain_Kinds_and_Lines.md)
Виды цепочек и правила line-полей.
3. [10_TECH_Blocks.md](./10_TECH_Blocks.md)
Системные блоки (`msg_type=0`).
4. [11_TEXT_Blocks.md](./11_TEXT_Blocks.md)
Текстовые блоки (`msg_type=1`).
5. [12_REACTION_Blocks.md](./12_REACTION_Blocks.md)
Реакции (`msg_type=2`).
6. [13_CONNECTION_Blocks.md](./13_CONNECTION_Blocks.md)
Социальные связи (`msg_type=3`).
7. [14_USER_PARAM_Blocks.md](./14_USER_PARAM_Blocks.md)
Параметры пользователя (`msg_type=4`).
8. [01_Channel_Types_and_CreateChannel.md](./01_Channel_Types_and_CreateChannel.md)
Типы каналов и формат `CreateChannelBody`.
9. [02_Channel_Commands.md](./02_Channel_Commands.md)
Команды в текстовых сообщениях каналов.
10. [CHANGELOG.md](./CHANGELOG.md)
Журнал изменений документации.
## Важные ограничения MVP
- Каналы `type=100` и `type=200` присутствуют в формате, но сейчас не используются в UI.
- Поддерживаемый рабочий сценарий UI на текущем этапе: `stories (type=0)` и `public (type=1)`.
## Обязательное сопровождение
- При любом изменении формата/правил блокчейна в коде документы этого каталога обновляются в том же наборе изменений.
- Обычный `AddBlock` сейчас пишет через `<blockchainName>.tmp_bch`, `<blockchainName>.write_check` и `<blockchainName>.write_pending`; эта схема и `BlockchainTmpRecoveryOnStartup` должны быть описаны в актуальной документации по синхронизации и recovery.
- Каждое обновление документов фиксируется в `CHANGELOG.md` с датой/временем и хэшем коммита-основания.
+256
View File
@@ -0,0 +1,256 @@
# Синхронизация блоков и DM между серверами SHiNE
Документ описывает архитектуру и протокол синхронизации данных между партнёрскими серверами SHiNE.
## 1. Зачем нужна синхронизация
Пользователи SHiNE могут быть «приписаны» к разным серверам.
Когда пользователь A (на сервере X) пишет пользователю B (на сервере Y):
1. Сервер X принимает сообщение;
2. Сервер X должен переслать DM-блок серверу Y;
3. Сервер Y сохраняет блок и доставляет в активные сессии пользователя B.
Аналогично, блоки пользовательского блокчейна (записи `AddBlock`) должны синхронизироваться,
чтобы любой партнёрский сервер мог отдать полную историю пользователя.
## 2. Список серверов синхронизации (`sync_servers`)
Каждый сервер регистрирует в своей Solana PDA список `sync_servers`
логины SHiNE-аккаунтов партнёрских серверов, с которыми он синхронизируется.
- Список хранится в блоке `ServerProfileBlock` внутри `user_pda` сервера.
- Адрес каждого партнёрского сервера читается из его PDA на Solana.
- Синхронизация двусторонняя: оба сервера должны иметь друг друга в `sync_servers`.
## 3. Что синхронизируется
### 3.1 Личные сообщения (DM)
- Все DM-блоки форматов типов `1/2` (текст) и `3/4` (read-receipt).
- Сервер-отправитель: при получении пары блоков от клиента перенаправляет их серверу получателя.
- Сервер-получатель: сохраняет блоки в `signed_messages_v2`, доставляет в активные сессии.
- Дедупликация по уникальному `message_key = from|to|timeMs|nonce|type`.
### 3.2 Блоки пользовательского блокчейна
- Все блоки `AddBlock` пользователей, зарегистрированных на сервере или синхронизирующихся через него.
- Синхронизируются в обе стороны между всеми партнёрами из `sync_servers`.
- Порядок блоков сохраняется (по глобальному номеру блока и хэшу).
- Дедупликация по глобальному номеру блока и хэшу.
## 4. Текущая реализованная схема
На текущем этапе сервер уже умеет базовую межсерверную синхронизацию пользовательских блокчейнов.
### 4.1 Что уже сделано
1. При старте сервер читает свой `server.SHiNE.login`.
2. По этому логину он загружает из Solana свою server PDA.
3. Из неё вытаскивает список `sync_servers`.
4. Для каждого логина партнёра сервер читает его PDA и сохраняет локально:
- `login`
- `server_address`
5. После этого:
- новые локальные `AddBlock` рассылаются партнёрам в фоне;
- при старте запускается periodic sync;
- periodic sync повторяется каждые `12` часов после старта.
### 4.2 Какие server-to-server API уже используются
- `ListBlockchainHeads` — список heads всех локальных цепочек партнёра;
- `GetBlockchainBlock` — чтение одного конкретного блока партнёра;
- `GetSyncUserProfile` — минимальный профиль пользователя для локального создания `solana_users + blockchain_state` без обращения в Solana RPC.
### 4.3 Как сейчас работает periodic sync
Для каждого сервера из локальной таблицы `sync_servers`:
1. запрашивается `ListBlockchainHeads`;
2. для каждой удалённой цепочки сравниваются:
- `lastBlockNumber`
- `lastBlockHash`
- локальное состояние;
3. если локальная цепочка слабее, сервер по одному блоку вызывает `GetBlockchainBlock`;
4. каждый скачанный блок локально применяется через существующий `AddBlock`;
5. если у сервера ещё нет локальной записи пользователя/цепочки, перед этим подготавливается локальный `solana_users + blockchain_state`.
6. если во время replay обнаруживается рассинхрон или на одинаковой высоте удалённая цепочка сильнее, запускается полный resync:
- цепочка помечается in-memory как `resync in progress`;
- создаётся marker-file в `data/`;
- в одной SQL-транзакции очищаются локальные данные цепочки и корректируются чужие счётчики;
- удаляются `.bch` и `.tmp_bch`;
- цепочка подтягивается заново с `0` через `GetBlockchainBlock`.
- обычный `AddBlock` на эту цепочку в этот момент возвращает `chain_resync_in_progress`.
### 4.4 Как именно работает full resync
Full resync запускается только тогда, когда:
- локальная chain отстаёт и обычная докачка хвоста упирается в `bad_prev_hash` или `bad_block_number`;
- либо высота цепочек одинаковая, но удалённая версия сильнее по правилу:
- `lastBlockNumber`;
- `fileSizeBytes`;
- `lastBlockHash`.
Порядок действий:
1. Ставится in-memory guard на `blockchainName`.
2. Создаётся marker-file `<blockchainName>.resync_pending`.
3. Обычный `AddBlock` на эту chain временно получает `chain_resync_in_progress`.
4. Вызывается атомарный SQL cleanup одной chain:
- уменьшаются чужие `likes_count` и `replies_count`;
- удаляются локальные derived-state записи этой chain;
- удаляются `blocks` и `blockchain_state` этой chain.
5. Удаляются файлы `<blockchainName>.bch` и `<blockchainName>.tmp_bch`.
6. Локальная chain создаётся заново через `GetSyncUserProfile` или через Solana import, если `sync.importUserProfileFromPartner.enabled=false`.
7. Chain replay-ится с `0` через `GetBlockchainBlock`.
8. Если всё прошло успешно, marker-file удаляется.
9. Если на любом шаге произошёл сбой, marker-file остаётся на диске, и сервер добивает эту chain при следующем старте.
Важно:
- full resync не делает умный rollback по одному блоку;
- full resync не трогает DM-таблицы и `solana_users`;
- висячие cross-chain ссылки считаются допустимым поведением системы.
### 4.5 Как работает обычный `AddBlock` и его recovery
Обычная запись блока теперь тоже идёт через временные артефакты:
1. собирается `<blockchainName>.tmp_bch` как полный кандидат на замену основного файла;
2. пишется маленький sidecar `<blockchainName>.write_check` с `blockNumber` и `blockHash`;
3. только после этого создаётся пустой marker `<blockchainName>.write_pending`;
4. выполняется SQL-транзакция;
5. после `commit` tmp атомарно ставится на место основного `.bch`;
6. marker и sidecar удаляются.
На старте `BlockchainTmpRecoveryOnStartup` смотрит именно на эту пару:
- если `write_pending` есть, recovery проверяет sidecar и БД, а затем либо завершает swap, либо чистит временные файлы;
- если `write_pending` нет, а `tmp_bch` или `write_check` остались, это мусор и он удаляется;
- `resync_pending` сюда не относится, это отдельный recovery-поток.
### 4.6 Startup recovery по marker-file
При старте сервер идёт в таком порядке:
1. `BlockchainTmpRecoveryOnStartup` для `*.write_pending` и orphan `*.tmp_bch` / `*.write_check`;
2. `BlockchainResyncRecoveryOnStartup` для `*.resync_pending`;
3. только потом поднимается обычный сервер и запускается `PeriodicBlockchainSyncService`.
Если marker-file существует:
- сервер не должен начинать обычную работу поверх этой chain;
- recovery снова выполняет cleanup и replay с нуля;
- если recovery не завершился, marker остаётся, и сервер не переходит к обычному режиму для этой chain.
### 4.7 Зачем понадобился `GetSyncUserProfile`
Изначально подготовка локальной цепочки делалась через Solana:
- из `blockchainName` извлекался `login`;
- сервер вызывал import пользователя из Solana PDA;
- по данным PDA локально создавались `solana_users + blockchain_state`.
На практике это упёрлось в ограничение внешнего Solana RPC: при чистом старте и массовой подтяжке чужих цепочек сервер мог получать `HTTP 429`.
Поэтому добавлен отдельный обходной режим:
- настройка `sync.importUserProfileFromPartner.enabled=true`
- в этом режиме сервер **не ходит в Solana RPC** для создания локальной цепочки во время sync;
- вместо этого он запрашивает у сервера-партнёра `GetSyncUserProfile` и создаёт локальную запись по данным партнёра.
- если локальный `solana_users` уже существует, sync восстанавливает только `blockchain_state` и не трогает identity-слой.
Это временная практическая заплатка, чтобы clean-start sync не зависел от rate limit внешнего Solana endpoint.
### 4.8 Что делает настройка `sync.importUserProfileFromPartner.enabled`
- `false` — стандартный режим, подготовка локального пользователя идёт через Solana PDA;
- `true` — sync-режим обхода Solana, локальный пользователь создаётся по server-to-server `GetSyncUserProfile`.
Настройка влияет именно на этап подготовки отсутствующей локальной цепочки во время periodic sync.
## 5. Целевой протокол следующего этапа
### 5.1 Межсерверное соединение
- Серверы устанавливают постоянное WebSocket-соединение друг с другом.
- Адрес партнёра определяется по `server_address` из его Solana PDA.
- Аутентификация: подпись Ed25519 корневым ключом сервера (`root_key` из PDA).
- При разрыве — переподключение с экспоненциальным backoff.
### 5.2 Доставка новых данных (push)
- При получении нового блока или DM сервер немедленно пушит его всем подключённым партнёрам.
- Партнёр подтверждает приём (ACK). Без ACK — повтор с backoff.
### 5.3 Начальная синхронизация (backfill)
- При первом подключении к партнёру серверы обмениваются «курсорами» состояния:
последний глобальный номер блока, последний известный DM-ключ.
- Сервер с более полной историей досылает недостающее партнёру.
### 5.4 Разрешение конфликтов
- Блоки пользовательского блокчейна: порядок определяется глобальным номером блока.
Конфликтующие ветки (fork) разрешаются по правилам `AddBlock` (см. `Dev_Docs/Blockchain/README.md`).
- DM: конфликтов нет, `message_key` уникален.
## 6. Маршрутизация DM между серверами
При отправке DM от пользователя A к пользователю B:
1. Клиент A отправляет пару блоков на свой сервер X.
2. Сервер X определяет, на каком сервере зарегистрирован пользователь B.
- Сначала проверяет локально (если B зарегистрирован на X).
- Иначе читает PDA пользователя B из Solana и смотрит `access_servers`.
- Выбирает первый доступный сервер из `access_servers` и перенаправляет туда DM.
3. Сервер Y (из `access_servers` B) сохраняет и доставляет блоки.
Кэш адресов серверов: обновляется раз в сессию (при ошибке соединения).
## 7. Безопасность
- Все блоки подписаны ключами пользователя на клиенте — сервер не может подделать содержимое.
- Серверы не расшифровывают DM-контент (шифрование — задача следующего этапа).
- При синхронизации каждый блок проходит валидацию подписи на принимающем сервере.
## 8. Статус реализации
| Компонент | Статус |
|-----------|--------|
| Регистрация серверной PDA в Solana | ✅ Реализовано |
| Чтение `sync_servers` из PDA | ✅ Реализовано |
| Локальная таблица `sync_servers` | ✅ Реализовано |
| Публичный `ListBlockchainHeads` | ✅ Реализовано |
| Публичный `GetBlockchainBlock` | ✅ Реализовано |
| Публичный `GetSyncUserProfile` | ✅ Реализовано |
| Плановый blockchain sync при старте + каждые 12 часов | ✅ Реализовано |
| Обход Solana RPC через `sync.importUserProfileFromPartner.enabled` | ✅ Реализовано |
| Обычный `AddBlock` через `tmp_bch`/`write_check`/`write_pending` | ✅ Реализовано |
| Межсерверный постоянный WebSocket-канал | Нужна реализация |
| Push новых DM партнёрам | Нужна реализация |
| Push блоков блокчейна партнёрам | ✅ Реализована базовая one-shot версия |
| Periodic backfill отсутствующего хвоста | ✅ Реализовано |
| Разрешение рассинхрона / divergence | ✅ Реализована базовая full-resync схема во время periodic sync |
| Startup recovery по `*.resync_pending` marker-file | ✅ Реализовано |
| Маршрутизация DM через access_servers | Нужна реализация (заглушка) |
Текущая версия сервера уже умеет базовую синхронизацию блокчейнов между партнёрами.
Не реализованы ещё DM-sync и постоянные server-to-server соединения.
Следующие отдельные шаги после текущего этапа:
- отдельно проверить full-resync и startup-recovery на реальном тестовом прогоне после ручного удаления БД/файлов.
### 8.1 Практическая проверка на тестовом сервере
Проверка на `t.shineup.me` показала, что текущая схема действительно поднимает цепочку при старте:
- после рестарта сервер сначала проходит `BlockchainTmpRecovery`;
- затем обрабатывает `BlockchainResyncRecovery`;
- после этого сам догружает цепочку `aidartest-001` с `shineup.me`;
- итоговое состояние на тестовом сервере:
- `blockchain_state.last_block_number = 13`
- `blocks` по `aidartest-001` = `14` записей
Это подтверждает, что startup sync и full-resync flow работают в живом сценарии, а не только в коде.