SHA256
Аввив 2.0 - работает, заливка!!
This commit is contained in:
@@ -1,49 +1,116 @@
|
||||
# Общий формат добавляемого блока (Frame v0)
|
||||
# Общий формат пользовательского блока SHiNE — Frame v1 / ANS-104
|
||||
|
||||
Этот файл описывает **единый бинарный формат** блока, который клиент отправляет через `AddBlock` в поле `blockBytesB64`.
|
||||
Актуальный формат не поддерживает старый Frame v0. Новый пользовательский блок сразу создаётся как готовый подписанный ANS-104 DataItem.
|
||||
|
||||
## 1. Полная структура блока
|
||||
## 1. Два уровня формата
|
||||
|
||||
Блок состоит из двух частей:
|
||||
Полный объект, который клиент отправляет в `AddBlock`, хранится в PostgreSQL и затем архивируется в Arweave:
|
||||
|
||||
1. **PREIMAGE** (подписывается)
|
||||
2. **TAIL** (маркер подписи + подпись)
|
||||
```text
|
||||
ANS-104 DataItem
|
||||
signature_type = 2 (generic Ed25519)
|
||||
signature = 64 bytes
|
||||
owner = 32-byte blockchain public key
|
||||
target = absent
|
||||
anchor = absent
|
||||
tags
|
||||
data = SHiNE Frame v1
|
||||
```
|
||||
|
||||
### PREIMAGE
|
||||
Цепочка SHiNE **не зависит от Arweave**. Arweave DataItem ID хранится отдельно и используется для дедупликации/поиска, но не является `prevHash`.
|
||||
|
||||
- `frameCode (uint16)`
|
||||
- `prevHash32 (32 bytes)`
|
||||
- `blockSize (int32)` — размер PREIMAGE
|
||||
- `blockNumber (int32)`
|
||||
- `timestamp (int64)`
|
||||
- `type (uint16)`
|
||||
- `subType (uint16)`
|
||||
- `version (uint16)`
|
||||
- `bodyBytes (N)`
|
||||
## 2. SHiNE Frame v1
|
||||
|
||||
### TAIL
|
||||
Все целые поля Frame v1 — BigEndian.
|
||||
|
||||
- `sigMarker (uint16)`
|
||||
- `signature64 (64 bytes, Ed25519)`
|
||||
| Поле | Размер | Описание |
|
||||
|---|---:|---|
|
||||
| `frameCode` | 2 | `0x0001` |
|
||||
| `prevHash32` | 32 | SHA-256 полного Frame v1 предыдущего SHiNE-блока; для блока 0 — нули |
|
||||
| `blockSize` | 4 | точный размер Frame v1, включая header и body |
|
||||
| `blockNumber` | 4 | номер блока, начиная с 0 |
|
||||
| `timestamp` | 8 | Unix time seconds |
|
||||
| `type` | 2 | тип сообщения |
|
||||
| `subType` | 2 | подтип |
|
||||
| `version` | 2 | версия body |
|
||||
| `body` | N | данные конкретного типа |
|
||||
|
||||
## 2. Что проверяет сервер при AddBlock
|
||||
`FRAME_HEADER_SIZE = 56` bytes.
|
||||
|
||||
- `frameCode` должен быть `0x0000`.
|
||||
- `sigMarker` должен быть `0x0100`.
|
||||
- `blockNumber` должен идти строго по порядку (`last + 1`).
|
||||
- `prevHash32` должен совпасть с вершиной цепочки на сервере.
|
||||
- `body` должен пройти `check()` для конкретного типа.
|
||||
- подпись должна валидироваться публичным ключом блокчейна.
|
||||
```text
|
||||
blockHash32 = SHA256(FrameV1Bytes)
|
||||
next.prevHash32 = blockHash32
|
||||
```
|
||||
|
||||
## 3. Ограничения
|
||||
Подписи внутри Frame v1 нет. Единственная подпись пользователя — подпись окружающего ANS-104 DataItem.
|
||||
|
||||
- максимальный полный размер блока: до 4 MiB;
|
||||
- timestamp не должен сильно уходить в будущее;
|
||||
- `bodyBytes` парсится по `type/subType/version` из заголовка блока.
|
||||
## 3. ANS-104 DataItem
|
||||
|
||||
## 4. Почему это важно
|
||||
Для тестового контура используется generic Ed25519 signature type `2`: подпись 64 bytes, owner 32 bytes. Это отдельный generic Ed25519 signer type; Solana-specific signer в текущем Turbo/arbundles имеет другой type.
|
||||
|
||||
Одинаковый общий формат позволяет:
|
||||
- передавать разные виды записей через один RPC `AddBlock`;
|
||||
- валидировать блоки единообразно;
|
||||
- расширять типы `body`, не ломая каркас блока.
|
||||
Подписывается стандартный ANS-104 deep-hash:
|
||||
|
||||
```text
|
||||
[
|
||||
"dataitem",
|
||||
"1",
|
||||
"2",
|
||||
owner,
|
||||
target(empty),
|
||||
anchor(empty),
|
||||
rawAvroTags,
|
||||
FrameV1Bytes
|
||||
]
|
||||
```
|
||||
|
||||
`rawAvroTags` — ровно те Avro-serialized bytes тегов, которые лежат внутри DataItem.
|
||||
|
||||
`dataItemId32 = SHA256(signature64)`.
|
||||
|
||||
### Обязательный тестовый тег
|
||||
|
||||
Каждый блок:
|
||||
|
||||
```text
|
||||
App = test5590
|
||||
```
|
||||
|
||||
Это временное namespace-значение для разработки. Перед реальным запуском оно будет заменено отдельным изменением протокола/кода.
|
||||
|
||||
### Канальный тег
|
||||
|
||||
Если блок относится к конкретному каналу, он дополнительно содержит:
|
||||
|
||||
```text
|
||||
c = <canonical_channel_slug>
|
||||
```
|
||||
|
||||
Slug входит в подпись DataItem и не может быть изменён сервером после подписи.
|
||||
|
||||
## 4. Что хранится в PostgreSQL
|
||||
|
||||
`blocks.block_bytes` содержит **полный serialized ANS-104 DataItem**, а не только Frame.
|
||||
|
||||
Отдельно индексируются:
|
||||
|
||||
- `block_hash` — SHA-256(Frame v1);
|
||||
- `block_signature` — 64-byte Ed25519 signature из DataItem;
|
||||
- `data_item_id` — SHA-256(signature), UNIQUE;
|
||||
- `block_number`, `bch_name`, message fields;
|
||||
- состояние публикации в Arweave.
|
||||
|
||||
Локальные `.bch`, `.tmp_bch` и marker-файлы для пользовательских blockchain больше не используются.
|
||||
|
||||
## 5. Проверка AddBlock
|
||||
|
||||
Сервер обязан:
|
||||
|
||||
1. распарсить полный ANS-104 DataItem;
|
||||
2. проверить `App=test5590`;
|
||||
3. проверить `c`, если тип блока требует канал;
|
||||
4. проверить ANS-104 Ed25519 подпись;
|
||||
5. проверить, что `owner` равен текущему blockchain public key пользователя;
|
||||
6. распарсить Frame v1 и body;
|
||||
7. проверить `blockNumber == last + 1`;
|
||||
8. проверить `prevHash32 == lastBlockHash`;
|
||||
9. записать DataItem и новое состояние атомарно в PostgreSQL.
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# ANS-104 / Arweave transport для пользовательских блоков SHiNE
|
||||
|
||||
## Цель
|
||||
|
||||
Каждый пользовательский блок уже на клиенте является самостоятельным подписанным ANS-104 DataItem. Сервер не переподписывает пользовательский контент: он проверяет его, хранит в PostgreSQL и объединяет готовые DataItems в стандартный ANS-104 bundle.
|
||||
|
||||
## Child DataItem tags
|
||||
|
||||
Обязательно для тестового контура:
|
||||
|
||||
```text
|
||||
App=test5590
|
||||
```
|
||||
|
||||
Дополнительно для блоков конкретного канала:
|
||||
|
||||
```text
|
||||
c=<canonical_channel_slug>
|
||||
```
|
||||
|
||||
Теги входят в ANS-104 подпись пользователя.
|
||||
|
||||
## Publisher
|
||||
|
||||
По умолчанию цикл — раз в 15 минут.
|
||||
|
||||
```text
|
||||
blocks.arweave_publish_pending=true
|
||||
↓
|
||||
готовые serialized DataItems
|
||||
↓
|
||||
ANS-104 binary bundle
|
||||
↓
|
||||
обычная Arweave L1 transaction
|
||||
```
|
||||
|
||||
Если pending-блоков нет, транзакция не создаётся.
|
||||
|
||||
Root transaction содержит стандартные bundle tags:
|
||||
|
||||
```text
|
||||
Bundle-Format=binary
|
||||
Bundle-Version=2.0.0
|
||||
Content-Type=application/octet-stream
|
||||
App=test5590-batch
|
||||
```
|
||||
|
||||
`App=test5590-batch` намеренно отличается от child `App=test5590`, чтобы discovery-запрос находил пользовательские блоки, а не root bundles.
|
||||
|
||||
После успешной L1-загрузки сервер ставит child-блокам:
|
||||
|
||||
- `arweave_publish_pending=false`;
|
||||
- `arweave_published_at_ms`;
|
||||
- `arweave_root_tx_id`.
|
||||
|
||||
## Importer
|
||||
|
||||
Каждый сервер может независимо искать:
|
||||
|
||||
```text
|
||||
App=test5590
|
||||
```
|
||||
|
||||
через GraphQL gateway с cursor pagination.
|
||||
|
||||
Для каждого нового DataItem:
|
||||
|
||||
1. взять `id` и `bundledIn.id`;
|
||||
2. получить root bundle;
|
||||
3. извлечь точные serialized bytes child DataItem по bundle index;
|
||||
4. проверить `dataItemId == SHA256(signature)`;
|
||||
5. проверить ANS-104 Ed25519 подпись;
|
||||
6. определить пользователя по `owner`;
|
||||
7. применить обычные проверки `AddBlock`;
|
||||
8. записать в PostgreSQL с `arweave_publish_pending=false`.
|
||||
|
||||
### Блоки могут прийти не по порядку
|
||||
|
||||
Discovery/import использует persistent queue `arweave_block_import_queue`. Если, например, block 102 увиден раньше block 101, block 102 остаётся `PENDING`; после появления 101 очередь повторно проигрывается.
|
||||
|
||||
## Дедупликация и несколько серверов
|
||||
|
||||
Один и тот же готовый DataItem имеет один `data_item_id = SHA256(signature)`. Если несколько серверов включили его в разные root bundles, локально это всё равно один логический блок: `blocks.data_item_id` уникален.
|
||||
|
||||
Импортированный из Arweave блок **не ставится обратно в publish queue**. Это предотвращает бесконечное переархивирование между серверами.
|
||||
|
||||
## Локальное хранение
|
||||
|
||||
Пользовательские blockchain-файлы на диске больше не используются. Полный serialized DataItem находится в `blocks.block_bytes` PostgreSQL.
|
||||
|
||||
## Настройки
|
||||
|
||||
См. `application.properties` и `CODEX_APPLY_ANS104_TEST5590_PATCH.md`.
|
||||
|
||||
## Что намеренно не входит в этот патч
|
||||
|
||||
Remote/homeserver signing path, связанный с внешним homeserver/ESP32 signer, не мигрируется этим патчем. Каталог `ESP32/` не изменяется. До отдельной миграции новый Frame v1/ANS-104 production path рассчитан на клиент, у которого локально доступен blockchain Ed25519 key.
|
||||
@@ -236,3 +236,16 @@
|
||||
- Добавлена поддержка командного префикса `/.` и команды `/.desc` для актуализации описания канала при чтении.
|
||||
- Зафиксированы команды `/.add` и `/.remove` для каналов `type=200` (зарезервировано под расширение участниками).
|
||||
- В `AGENTS.md` добавлено обязательное правило актуализации документации в `docs/Blockchain/`.
|
||||
|
||||
## 2026-09-20 — Frame v1 + ANS-104 DataItem / Arweave per-block transport
|
||||
|
||||
- Основание: пользователь явно подтвердил breaking change и запуск с чистой историей; patch archive, commit hash ещё отсутствует.
|
||||
- Удалена поддержка старого Frame v0 (`preimage + sigMarker + signature`).
|
||||
- Новый пользовательский block — SHiNE Frame v1 внутри стандартного ANS-104 DataItem, generic Ed25519 signature type 2.
|
||||
- Signing message синхронизирован с фактическим arbundles/Turbo DataItem: включает строку signature type и raw Avro tag bytes.
|
||||
- SHiNE chain hash остаётся независимым: `SHA256(FrameV1Bytes)`; Arweave DataItem ID хранится отдельно.
|
||||
- В тестовом контуре каждый DataItem подписывает tag `App=test5590`; channel blocks дополнительно `c=<canonical slug>`.
|
||||
- PostgreSQL стал единственным локальным хранилищем пользовательских block bytes; `.bch/.tmp_bch/marker` storage/recovery удалён.
|
||||
- Старый custom `SHINE-ARCHIVE` pipeline удалён. Добавлены standard ANS-104 bundle publisher через существующий Arweave L1 uploader и GraphQL importer.
|
||||
- Импортированные из Arweave blocks не публикуются повторно; дедупликация идёт по `data_item_id`.
|
||||
- ESP32 и связанный device code этим изменением не затронуты.
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# Инструкция Codex: применить ANS-104 test5590 patch
|
||||
|
||||
## Цель
|
||||
|
||||
Перевести пользовательский blockchain SHiNE на Frame v1 внутри готовых ANS-104 DataItems и убрать старый SHINE-ARCHIVE/файловое хранение цепочек.
|
||||
|
||||
## Жёсткое ограничение
|
||||
|
||||
**Не изменять ничего в `ESP32/`.** В этом patch нет ни одного файла `ESP32/**`.
|
||||
|
||||
Remote/homeserver signer, завязанный на устройство, намеренно не мигрирован. Не пытаться «заодно исправить» его в рамках этого patch.
|
||||
|
||||
## Применение
|
||||
|
||||
1. Распаковать patch поверх корня репозитория, сохраняя относительные пути.
|
||||
2. Удалить все пути из корневого `DELETE_FILES.txt`.
|
||||
3. Проверить, что `git diff -- ESP32` пуст.
|
||||
4. Использовать чистую/dev test DB. `migration_v24.sql` намеренно откажется мигрировать непустую blockchain DB, потому что backward compatibility со старым block format не требуется.
|
||||
|
||||
## Arweave config
|
||||
|
||||
Минимально для публикации:
|
||||
|
||||
```properties
|
||||
arweave.blocks.publish.enabled=true
|
||||
arweave.blocks.publish.intervalMinutes=15
|
||||
arweave.blocks.publish.gateway=https://arweave.net
|
||||
arweave.blocks.publish.walletJwkPath=/ABSOLUTE/SECRET/PATH/arweave-wallet.json
|
||||
```
|
||||
|
||||
JWK не коммитить.
|
||||
|
||||
Для discovery/import:
|
||||
|
||||
```properties
|
||||
arweave.blocks.sync.enabled=true
|
||||
arweave.blocks.sync.intervalMinutes=15
|
||||
arweave.blocks.sync.gateway=https://turbo-gateway.com
|
||||
arweave.blocks.sync.startBlockHeight=0
|
||||
```
|
||||
|
||||
На тестах желательно установить `startBlockHeight` на высоту начала `test5590`, чтобы не сканировать лишнюю историю.
|
||||
|
||||
## Test namespace
|
||||
|
||||
Child DataItem:
|
||||
|
||||
```text
|
||||
App=test5590
|
||||
```
|
||||
|
||||
Channel child:
|
||||
|
||||
```text
|
||||
App=test5590
|
||||
c=<canonical_channel_slug>
|
||||
```
|
||||
|
||||
Root bundle:
|
||||
|
||||
```text
|
||||
Bundle-Format=binary
|
||||
Bundle-Version=2.0.0
|
||||
App=test5590-batch
|
||||
```
|
||||
|
||||
Перед production-start test namespace должен быть заменён отдельным осознанным изменением.
|
||||
|
||||
## Проверки после применения
|
||||
|
||||
Из корня репозитория:
|
||||
|
||||
```bash
|
||||
node --check shine-UI/js/services/ans104-data-item.js
|
||||
node --check shine-UI/js/services/auth-service.js
|
||||
node --check shine-UI/js/app.js
|
||||
node --check shine-UI/js/pages/settings-view.js
|
||||
```
|
||||
|
||||
Java/Gradle:
|
||||
|
||||
```bash
|
||||
./gradlew testClasses
|
||||
./gradlew test
|
||||
```
|
||||
|
||||
Затем локальный smoke test по штатной инструкции проекта, например `./gradlew startLocal`.
|
||||
|
||||
В среде, где готовился patch, Gradle wrapper не смог скачать Gradle 8.14 из-за отсутствия внешнего сетевого доступа к `services.gradle.org`. Поэтому полный Gradle compile/test обязательно прогнать после применения в обычной dev-среде.
|
||||
|
||||
## Smoke scenario
|
||||
|
||||
1. Создать/использовать тестового пользователя с локальным blockchain Ed25519 key.
|
||||
2. Добавить обычный block и убедиться, что `blocks.block_bytes` начинается с ANS-104 DataItem, а `data_item_id` заполнен.
|
||||
3. Создать channel и post; проверить `c=<canonical slug>`.
|
||||
4. Включить publisher, дождаться цикла или вызвать сервис тестом; проверить root Arweave tx.
|
||||
5. На второй чистой test DB включить importer и убедиться, что `App=test5590` blocks восстанавливаются в правильном порядке.
|
||||
6. Убедиться, что imported blocks имеют `arweave_publish_pending=false`.
|
||||
7. Проверить, что повторный discovery не создаёт дублей.
|
||||
|
||||
## Не делать в этом patch
|
||||
|
||||
- не добавлять backward compatibility Frame v0;
|
||||
- не возвращать `.bch` storage;
|
||||
- не возвращать SHINE-ARCHIVE;
|
||||
- не менять ESP32;
|
||||
- не мигрировать remote/homeserver signing без отдельного решения пользователя;
|
||||
- не заменять `prevHash` на Arweave DataItem ID.
|
||||
+34
-41
@@ -1,47 +1,40 @@
|
||||
# Документация блокчейна SHiNE (MVP)
|
||||
# SHiNE Blockchain
|
||||
|
||||
Этот каталог описывает только текущий рабочий формат протокола для MVP.
|
||||
Актуальная версия пользовательского blockchain использует **SHiNE Frame v1 внутри подписанного ANS-104 DataItem**. Старый Frame v0 и файловое хранение `.bch` не поддерживаются.
|
||||
|
||||
## Основные документы
|
||||
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. [15_STATUS_ACTION_Blocks.md](./15_STATUS_ACTION_Blocks.md)
|
||||
Статусные действия пользователя (`msg_type=5`).
|
||||
9. [16_TEXT_Attachments.md](./16_TEXT_Attachments.md)
|
||||
Вложения в TEXT-сообщениях через `S:att v=1`, включая опциональные `preAr/preSha256` для видео и крупных изображений.
|
||||
10. [16_TEXT_Channel_Meta.md](./16_TEXT_Channel_Meta.md)
|
||||
Скрытый `TEXT_CHANNEL_META` для профиля канала.
|
||||
11. [01_Channel_Types_and_CreateChannel.md](./01_Channel_Types_and_CreateChannel.md)
|
||||
Типы каналов и формат `CreateChannelBody`.
|
||||
12. [02_Channel_Commands.md](./02_Channel_Commands.md)
|
||||
Команды в текстовых сообщениях каналов.
|
||||
13. [CHANGELOG.md](./CHANGELOG.md)
|
||||
Журнал изменений документации.
|
||||
|
||||
## Смежная документация
|
||||
- [../ИТХ/README.md](../ИТХ/README.md) — ежедневное закрытие блокчейна (ИТХ): краткий обзор.
|
||||
- [../ИТХ/Спецификация_ИТХ_v1.md](../ИТХ/Спецификация_ИТХ_v1.md) — точная спецификация чекпоинтов (Arweave/Solana/канал закрытий).
|
||||
- [sync-between-servers.md](./sync-between-servers.md) — живая межсерверная синхронизация блокчейна и доставка DM на единственный сервер получателя.
|
||||
- [`01_Common_Block_Format.md`](01_Common_Block_Format.md) — точный Frame v1, ANS-104, подпись, хэши и теги.
|
||||
- [`17_ANS104_Arweave_Transport.md`](17_ANS104_Arweave_Transport.md) — публикация блоков в Arweave и обратный импорт между серверами.
|
||||
- [`sync-between-servers.md`](sync-between-servers.md) — межсерверная синхронизация и full-resync без файловой копии blockchain.
|
||||
- [`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.
|
||||
- Для runtime-агрегатов статистики `user_stats_state` и `channel_stats_state` действует тот же принцип derived state: они обновляются вместе с `AddBlock` и полностью пересобираются при full resync.
|
||||
- Если в старых данных есть канал владельца, которого ещё нет в `solana_user_pda_current`, сервер не падает: `channel_stats_state` всё равно обновляется, а `user_stats_state` создаётся только после появления пользователя в Solana PDA.
|
||||
- Каждое обновление документов фиксируется в `CHANGELOG.md` с датой/временем и хэшем коммита-основания.
|
||||
```text
|
||||
User
|
||||
-> создаёт Frame v1
|
||||
-> tags: App=test5590, при канале c=<slug>
|
||||
-> Ed25519 подписывает ANS-104 deep-hash
|
||||
-> готовый DataItem
|
||||
-> AddBlock
|
||||
|
||||
Server
|
||||
-> verify DataItem + SHiNE chain
|
||||
-> PostgreSQL
|
||||
-> каждые ~15 минут ANS-104 bundle
|
||||
-> Arweave L1
|
||||
|
||||
Other servers
|
||||
-> GraphQL App=test5590
|
||||
-> скачивают/извлекают DataItem
|
||||
-> verify
|
||||
-> PostgreSQL без повторной публикации
|
||||
```
|
||||
|
||||
## Важные свойства
|
||||
|
||||
- SHiNE `prevHash` остаётся SHA-256 предыдущего Frame v1 и не зависит от Arweave.
|
||||
- `data_item_id` нужен для Arweave, поиска и дедупликации.
|
||||
- PostgreSQL — единственное локальное хранилище пользовательских блоков.
|
||||
- Тестовый namespace `App=test5590` специально отделён от будущего production namespace.
|
||||
|
||||
@@ -1,280 +1,42 @@
|
||||
# Синхронизация блокчейнов и доставка DM между серверами SHiNE
|
||||
# Синхронизация blockchain между SHiNE-серверами
|
||||
|
||||
Документ описывает архитектуру и протокол синхронизации данных между партнёрскими серверами SHiNE.
|
||||
## Локальная модель хранения
|
||||
|
||||
## 1. Зачем нужна синхронизация
|
||||
PostgreSQL является единственным локальным хранилищем пользовательских блоков. Файлы `<blockchain>.bch`, `.tmp_bch`, `.write_pending`, `.write_check`, `.resync_pending` не используются.
|
||||
|
||||
Пользователи SHiNE могут быть «приписаны» к разным серверам.
|
||||
Когда пользователь A (на сервере X) пишет пользователю B (на сервере Y):
|
||||
`blocks.block_bytes` хранит полный подписанный ANS-104 DataItem. `blockchain_state` хранит текущую вершину цепочки.
|
||||
|
||||
1. Сервер X принимает сообщение;
|
||||
2. Сервер X должен переслать DM-блок серверу Y;
|
||||
3. Сервер Y сохраняет блок и доставляет в активные сессии пользователя B.
|
||||
## Обычный AddBlock
|
||||
|
||||
Аналогично, блоки пользовательского блокчейна (записи `AddBlock`) должны синхронизироваться,
|
||||
чтобы любой партнёрский сервер мог отдать полную историю пользователя.
|
||||
Все проверки и запись выполняются под lock конкретной chain. После проверки Frame v1, подписи и `prevHash` одна SQL-транзакция записывает block + derived state + новую вершину.
|
||||
|
||||
## 2. Список серверов синхронизации (`sync_servers`)
|
||||
Локально созданный пользовательский блок получает `arweave_publish_pending=true`.
|
||||
|
||||
Каждый сервер регистрирует в своей Solana PDA список `sync_servers` —
|
||||
логины SHiNE-аккаунтов партнёрских серверов, с которыми он синхронизируется.
|
||||
## Периодический peer-to-peer sync
|
||||
|
||||
`sync_servers` относится к серверному узлу и не является списком
|
||||
access-серверов обычного пользователя. У пользователя действует только первый
|
||||
`access_servers[0]`.
|
||||
Старый межсерверный P2P sync может получать `GetBlockchainBlock` и применять полученный полный DataItem через `AddBlock`. При divergence full-resync:
|
||||
|
||||
- Список хранится в блоке `ServerProfileBlock` внутри `user_pda` сервера.
|
||||
- Адрес каждого партнёрского сервера читается из его PDA на Solana.
|
||||
- Синхронизация двусторонняя: оба сервера должны иметь друг друга в `sync_servers`.
|
||||
1. берёт lock chain;
|
||||
2. очищает derived rows/blocks/state через `BlockchainResyncCleanupDAO`;
|
||||
3. пересоздаёт state из актуального пользовательского реестра;
|
||||
4. последовательно проигрывает удалённую цепочку с блока 0;
|
||||
5. никаких файловых swap/recovery операций нет.
|
||||
|
||||
## 3. Что синхронизируется
|
||||
## Arweave sync
|
||||
|
||||
### 3.1 Личные сообщения (DM)
|
||||
Дополнительно каждый сервер может независимо включить `ArweaveBlockSyncScheduler`.
|
||||
|
||||
- Все DM-блоки форматов типов `1/2` (текст) и `3/4` (read-receipt).
|
||||
- Сервер-отправитель: сохраняет пару и ставит асинхронную delivery-задачу.
|
||||
- Сервер-получатель: сохраняет входящий блок в `signed_messages`, затем доставляет его активным сессиям.
|
||||
- Дедупликация по уникальному `message_key = from|to|timeMs|nonce|type`.
|
||||
- Между access-серверами одного пользователя DM не реплицируются.
|
||||
- Полная актуальная схема: `docs/Personal_Messages/Доставка_и_синхронизация_DM.md`.
|
||||
Он ищет child DataItems по тестовому тегу `App=test5590`, проверяет их и импортирует через ту же бизнес-проверку AddBlock. Импортированные блоки не публикуются повторно.
|
||||
|
||||
### 3.2 Блоки пользовательского блокчейна
|
||||
Если блоки обнаружены не по порядку, persistent queue оставляет более поздние блоки pending до появления предыдущих.
|
||||
|
||||
- Все блоки `AddBlock` пользователей, зарегистрированных на сервере или синхронизирующихся через него.
|
||||
- Синхронизируются в обе стороны между всеми партнёрами из `sync_servers`.
|
||||
- Порядок блоков сохраняется (по глобальному номеру блока и хэшу).
|
||||
- Дедупликация по глобальному номеру блока и хэшу.
|
||||
## Источник истины для цепочки
|
||||
|
||||
### 3.3 Пользовательские настройки
|
||||
Arweave не участвует в вычислении SHiNE chain hash:
|
||||
|
||||
Пользовательские настройки хранятся локально на единственном access-сервере и
|
||||
между серверами не синхронизируются.
|
||||
```text
|
||||
block_hash = SHA256(FrameV1Bytes)
|
||||
next.prevHash32 = block_hash
|
||||
```
|
||||
|
||||
## 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` — минимальный профиль пользователя для локального создания runtime-проекции пользователя и `blockchain_state` без обращения в Solana RPC.
|
||||
|
||||
### 4.3 Как сейчас работает periodic sync
|
||||
|
||||
Для каждого сервера из локальной таблицы `sync_servers`:
|
||||
|
||||
1. запрашивается `ListBlockchainHeads`;
|
||||
2. для каждой удалённой цепочки сравниваются:
|
||||
- `lastBlockNumber`
|
||||
- `lastBlockHash`
|
||||
- локальное состояние;
|
||||
3. если локальная цепочка слабее, сервер по одному блоку вызывает `GetBlockchainBlock`;
|
||||
4. каждый скачанный блок локально применяется через существующий `AddBlock`;
|
||||
5. если у сервера ещё нет локальной записи пользователя/цепочки, перед этим подготавливается локальная runtime-проекция пользователя и `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-таблицы и current 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 локально создавались runtime-проекция пользователя и `blockchain_state`.
|
||||
|
||||
На практике это упёрлось в ограничение внешнего Solana RPC: при чистом старте и массовой подтяжке чужих цепочек сервер мог получать `HTTP 429`.
|
||||
|
||||
Поэтому добавлен отдельный обходной режим:
|
||||
|
||||
- настройка `sync.importUserProfileFromPartner.enabled=true`
|
||||
- в этом режиме сервер **не ходит в Solana RPC** для создания локальной цепочки во время sync;
|
||||
- вместо этого он запрашивает у сервера-партнёра `GetSyncUserProfile` и создаёт локальную запись по данным партнёра.
|
||||
- если локальная runtime-проекция пользователя уже существует, sync восстанавливает только `blockchain_state` и не трогает user-layer.
|
||||
|
||||
Это временная практическая заплатка, чтобы 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. Реализованный постоянный server-to-server транспорт
|
||||
|
||||
Этот раздел не меняет текущую семантику DM, settings и blockchain. Он описывает
|
||||
единый постоянный WSS-транспорт, через который выполняются уже существующие операции.
|
||||
|
||||
### 5.1 Межсерверное соединение
|
||||
|
||||
- Серверы устанавливают постоянное исходящее WebSocket-соединение друг с другом.
|
||||
- Адрес партнёра определяется по `server_address` из его Solana PDA.
|
||||
- После подключения отправляется `ServerHello` с `serverLogin`, версией протокола и capabilities.
|
||||
- На текущем этапе `serverLogin` принимается на доверии; подпись Ed25519 корневым ключом сервера отложена.
|
||||
- При разрыве выполняется переподключение с jitter/backoff до 60 секунд.
|
||||
- После 120 секунд отсутствия полезного трафика отправляется WebSocket ping; pong ожидается 15 секунд.
|
||||
- Один физический канал переиспользуют доставка DM и blockchain.
|
||||
|
||||
### 5.2 Доставка новых данных (push)
|
||||
|
||||
- При получении нового блока сервер может немедленно пушить его всем подключённым партнёрам.
|
||||
- Партнёр подтверждает приём (ACK). Без ACK — повтор с backoff.
|
||||
- DM использует отдельное расписание, описанное в `docs/Personal_Messages/Доставка_и_синхронизация_DM.md`.
|
||||
|
||||
### 5.3 Начальная синхронизация (backfill)
|
||||
|
||||
- При первом подключении к партнёру серверы могут обмениваться курсорами состояния блокчейнов.
|
||||
- Сервер с более полной историей досылает недостающее партнёру.
|
||||
- DM-history backfill между access-серверами отсутствует.
|
||||
|
||||
### 5.4 Разрешение конфликтов
|
||||
|
||||
- Блоки пользовательского блокчейна: порядок определяется глобальным номером блока.
|
||||
Конфликтующие ветки (fork) разрешаются по правилам `AddBlock` (см. `docs/Blockchain/README.md`).
|
||||
- DM: конфликтов нет, `message_key` уникален.
|
||||
|
||||
## 6. Маршрутизация DM между серверами
|
||||
|
||||
При отправке DM от пользователя A к пользователю B:
|
||||
|
||||
1. Клиент A отправляет пару блоков на свой сервер X.
|
||||
2. Сервер X валидирует и локально сохраняет пару.
|
||||
3. До ответа клиенту X отправляет входящую копию на единственный
|
||||
`access_servers[0]` пользователя B через `ReceiveIncomingMessage`.
|
||||
4. Успешное сохранение на сервере B означает терминальный `delivered`.
|
||||
5. При неудаче выполняются повторы через 30 секунд, 5 минут, 25 минут и 1 час.
|
||||
6. После неудачной попытки через час ставится терминальный `failed`.
|
||||
|
||||
Изменения routing B учитываются до терминального состояния, потому что список маршрутов перечитывается на каждой попытке.
|
||||
|
||||
## 7. Безопасность
|
||||
|
||||
- Все блоки подписаны ключами пользователя на клиенте — сервер не может подделать содержимое.
|
||||
- Серверы не расшифровывают DM-контент; E2EE уже выполняется клиентами.
|
||||
- При синхронизации каждый блок проходит валидацию подписи на принимающем сервере.
|
||||
- Межсерверная авторизация DM-операций пока отложена; `sourceServerLogin` временно считается доверенным.
|
||||
|
||||
## 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-канал | ✅ Реализован общий `ServerConnectionPool` |
|
||||
| Асинхронная доставка DM на единственный access-сервер получателя | ✅ Реализовано |
|
||||
| Retry DM до 1 часа + UI-state | ✅ Реализовано |
|
||||
| Репликация DM и настроек между access-серверами | Не используется |
|
||||
| Push блоков блокчейна партнёрам | ✅ Выполняется через постоянный WSS-пул |
|
||||
| Periodic backfill отсутствующего хвоста | ✅ Реализовано |
|
||||
| Разрешение рассинхрона / divergence | ✅ Реализована базовая full-resync схема во время periodic sync |
|
||||
| Startup recovery по `*.resync_pending` marker-file | ✅ Реализовано |
|
||||
| Маршрутизация DM только через `access_servers[0]` | ✅ Реализовано |
|
||||
| Криптографическая server-to-server авторизация DM | Нужна реализация |
|
||||
|
||||
Текущая версия сервера использует постоянный WSS-пул для существующих
|
||||
server-to-server JSON-операций. Отдельной будущей задачей остаётся
|
||||
криптографическая авторизация server-to-server вызовов.
|
||||
|
||||
Следующие отдельные шаги после текущего этапа:
|
||||
- отдельно проверить full-resync и startup-recovery на реальном тестовом прогоне после ручного удаления БД/файлов.
|
||||
|
||||
### 8.1 Практическая проверка на тестовом сервере
|
||||
|
||||
Проверка на `server2.shineup.me` показала, что текущая схема действительно поднимает цепочку при старте:
|
||||
|
||||
- после рестарта сервер сначала проходит `BlockchainTmpRecovery`;
|
||||
- затем обрабатывает `BlockchainResyncRecovery`;
|
||||
- после этого сам догружает цепочку `aidartest-001` с `shineup.me`;
|
||||
- итоговое состояние на тестовом сервере:
|
||||
- `blockchain_state.last_block_number = 13`
|
||||
- `blocks` по `aidartest-001` = `14` записей
|
||||
|
||||
Это подтверждает, что startup sync и full-resync flow работают в живом сценарии, а не только в коде.
|
||||
Поэтому цепочка остаётся проверяемой и переносимой независимо от конкретного storage backend.
|
||||
|
||||
Reference in New Issue
Block a user