Аввив 2.0 - работает, заливка!!

This commit is contained in:
AidarKC
2026-09-21 12:49:33 +03:00
parent 23969121e5
commit be696d116b
90 changed files with 2411 additions and 14938 deletions
+30 -291
View File
@@ -1,310 +1,49 @@
# API для разработчиков: 04 — Запись и чтение блока блокчейна
# AddBlock API
Документ описывает **текущий рабочий формат** сетевых вызовов:
## Назначение
- `AddBlock` — запись любого блока в блокчейн пользователя;
- `GetBlockchainBlock` — публичное чтение одного конкретного блока по имени цепочки и номеру.
Добавляет один готовый подписанный пользовательский SHiNE Frame v1 / ANS-104 DataItem в blockchain.
`GetBlockchainBlock` нужен в том числе для межсерверной синхронизации и для открытого чтения публичного блокчейна по одному блоку.
> Важный принцип: на уровне JSON API сейчас есть **один универсальный метод** записи — `AddBlock`.
> Конкретный смысл записи задаётся типом самого бинарного блока (`type/subType/version` в заголовке блока).
## 1. Что делает `AddBlock`
`AddBlock`:
- принимает имя блокчейна и base64 бинарного блока;
- проверяет непрерывность цепочки (`blockNumber`, `prevHash`);
- проверяет формат и подпись Ed25519;
- валидирует `body` по правилам типа блока;
- сохраняет блок и обновляет состояние цепочки.
## 2. JSON формат запроса
`op = "AddBlock"`.
## Request
```json
{
"op": "AddBlock",
"requestId": "req-1001",
"requestId": "...",
"payload": {
"blockchainName": "alice-001",
"blockNumber": 12,
"prevBlockHash": "ab12...ff",
"blockBytesB64": "AAAB..."
"blockchainName": "alice-...",
"blockNumber": 42,
"prevBlockHash": "64 hex chars",
"blockBytesB64": "..."
}
}
```
Поля `payload`:
- `blockchainName` — обязательно, формат `login-NNN`.
- `blockNumber` — обязательно (временное legacy-поле для совместимости; должно совпасть с номером внутри бинарного блока).
- `prevBlockHash` — legacy-поле, сейчас сервер использует `prevHash` из бинарного блока и состояние цепочки.
- `blockBytesB64` — обязательно: **полный бинарный блок** (`preimage + sigMarker + signature`) в Base64.
- `blockchainName` — целевая пользовательская chain;
- `blockNumber` — должен совпадать с номером внутри Frame v1 и быть `serverLast + 1`;
- `prevBlockHash` — 32-byte SHiNE hash предыдущего Frame в hex; должен совпадать с `prevHash32` внутри Frame и серверной вершиной;
- `blockBytesB64`**полный serialized ANS-104 DataItem** в Base64.
## 3. Успешный ответ
Старый формат `preimage + sigMarker + signature` не поддерживается.
```json
{
"op": "AddBlock",
"requestId": "req-1001",
"status": 200,
"ok": true,
"payload": {
"reasonCode": null,
"serverLastGlobalNumber": 12,
"serverLastGlobalHash": "9f0e...a1"
}
}
```
## Требования к DataItem
## 4. Ошибка (единый формат)
- generic Ed25519 signature type `2`;
- Ed25519 signature 64 bytes;
- owner 32 bytes и равен текущему blockchain public key;
- обязательный тестовый tag `App=test5590`;
- для channel block — `c=<canonical_channel_slug>`;
- `data` содержит SHiNE Frame v1 (`frameCode=1`).
При ошибках сервер отдаёт `Net_Exception_Response` со стандартными полями и дополнительно с состоянием сервера для ресинка:
## Основные ошибки
```json
{
"op": "AddBlock",
"requestId": "req-1001",
"status": 400,
"ok": false,
"error": "bad_prev_hash",
"message": "Некорректный prevHash (цепочка не совпадает)",
"payload": {
"serverLastGlobalNumber": 11,
"serverLastGlobalHash": "c3d4...98"
}
}
```
- `bad_app_tag` — отсутствует/неверен `App=test5590`;
- `bad_channel_tag``c` отсутствует, лишний или не совпадает с canonical slug;
- `bad_signature` / `signature_verify_failed` — DataItem не подписан текущим blockchain key;
- `bad_block_number` — нарушена последовательность;
- `bad_prev_hash` — нарушена SHiNE hash chain;
- `bad_block_bytes` — DataItem/Frame не парсится.
### Основные `reasonCode`
## Storage/publish
- `empty_blockchain_name`, `bad_blockchain_name`
- `blockchain_state_not_found`
- `bad_block_base64`, `bad_block_format`, `bad_block_body`
- `bad_block_number`, `req_global_mismatch`, `bad_prev_hash`
- `bad_signature`, `signature_verify_failed`
- `prev_line_block_not_found`, `bad_prev_line_hash`
- `limit_exceeded`
- `chain_resync_in_progress` — цепочка временно заблокирована полным resync
- `repost_disabled` — репосты временно отключены до будущей реализации
- `entrypoint_edit_forbidden``TEXT_ENTRYPOINT` нельзя редактировать через `TEXT_EDIT_POST`
- `status_confirmed_target_must_be_status_action``STATUS_CONFIRMED` должен ссылаться на статусный блок
- `status_action_target_not_allowed` — выбранный `STATUS_ACTION` нельзя ставить на этот тип материала
- `bad_channel_meta_line`, `channel_not_found`, `bad_channel_meta_*`, `channel_meta_*_too_long` — ошибки `TEXT_CHANNEL_META`
- `internal_error`
## 5. Какие блоки реально можно добавлять через `AddBlock`
Через `AddBlock` можно писать поддержанные форматы, кроме явно отключённых временных фич:
1. **TECH (type=0)**
- `HEADER_COMPAT (subType=0)`
- `TECH_CREATE_CHANNEL (subType=1)`
2. **TEXT (type=1)**
- `TEXT_POST (10)`
- `TEXT_EDIT_POST (11)`
- `TEXT_REPLY (20)`
- `TEXT_EDIT_REPLY (21)`
- `TEXT_RATING (30)` — target-based отзыв на конкретный блок
- `TEXT_REPOST (50)` — формат зарезервирован, но новые блоки временно отклоняются с `repost_disabled`
- `TEXT_CHANNEL_META (90)` — скрытый технический снимок профиля канала
- `TEXT_ENTRYPOINT (100)` — входная страница канала
- `TEXT_EXERCISE (110)` — line-based материал упражнения
- `TEXT_SERVICE (120)` — line-based материал услуги / процедуры
- `TEXT_COURSE (130)` — line-based материал курса
3. **REACTION (type=2)**
- `REACTION_LIKE (1)`
4. **CONNECTION (type=3)**
- `CONNECTION_FRIEND (10)`
- `CONNECTION_UNFRIEND (11)`
- `CONNECTION_CONTACT (20)`
- `CONNECTION_UNCONTACT (21)`
- `CONNECTION_FOLLOW (30)`
- `CONNECTION_UNFOLLOW (31)`
- `CONNECTION_SPOUSE (40)`
- `CONNECTION_UNSPOUSE (41)`
- `CONNECTION_PARENT (50)`
- `CONNECTION_UNPARENT (51)`
- `CONNECTION_CHILD (52)`
- `CONNECTION_UNCHILD (53)`
- `CONNECTION_SIBLING (54)`
- `CONNECTION_UNSIBLING (55)`
- `CONNECTION_KNOWN_PERSON (60)`
- `CONNECTION_UNKNOWN_PERSON (61)`
- `CONNECTION_SHINE_CONFIRMED (70)`
- `CONNECTION_SHINE_UNCONFIRMED (71)`
- `CONNECTION_SHINE_SEEN (74)`
- `CONNECTION_SHINE_UNSEEN (75)`
5. **USER_PARAM (type=4)**
- `USER_PARAM_TEXT_TEXT (1)`
6. **STATUS_ACTION (type=5)**
- `STATUS_DONE_ONCE (10)`
- `STATUS_LEARNED (20)`
- `STATUS_SERVICE_PASSED (30)`
- `STATUS_CONFIRMED (100)`
- `STATUS_INTERESTED (110)`
- `STATUS_STARTED (120)`
- `STATUS_IN_STUDY (130)`
- `STATUS_ABANDONED (140)`
- `STATUS_COMPLETED (150)`
## 6. Практические payload-форматы для каналов и вложений
`AddBlock` не имеет отдельных JSON-полей для вложений, аватаров или человекочитаемого имени канала. Клиент собирает бинарный блок нужного типа, а новые данные кладёт в текстовые поля тела блока по правилам blockchain-формата.
### Вложения в сообщениях
Для `TEXT_POST`, `TEXT_REPLY`, `TEXT_EDIT_POST` и `TEXT_EDIT_REPLY` вложения записываются в начало текста сообщения одним или несколькими тегами `S:att v=1`.
Пример текстового содержимого body:
```text
<S:att;v=1;nm=photo.jpg;sz=248193;sha256=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;ar=BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB>
<S:att;v=1;nm=report.pdf;sz=845221;sha256=cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc;ar=DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD>
Текст сообщения
```
Пример вложения с отдельным preview-файлом:
```text
<S:att;v=1;nm=video.mp4;sz=5820193;sha256=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb;ar=CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC;preAr=DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD;preSha256=eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee>
```
Сервер хранит это как обычный `TEXT`-блок. Отображение карусели, картинок, видео и карточек файлов делает клиент. Полная спецификация тега находится в `docs/Blockchain/15_TEXT_Attachments.md`.
### Создание публичного канала с профилем
Для публичного канала начальный профиль пишется одним блоком `TECH_CREATE_CHANNEL`. Поле `channelDescription` содержит meta-текст:
```text
<S:title;v=1;Человекочитаемое имя канала>
<S:ava;v=1;sz=248193;sha256=3f2c8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;ar=AbCdEfAbCdEfAbCdEfAbCdEfAbCdEfAbCdEfAbCdE>
Описание канала
```
Ограничение `channelDescription` — до `2048` UTF-8 байт. Новый клиент не пишет отдельный `TEXT_CHANNEL_META` сразу после создания канала: создание канала и начальный профиль должны попадать в один `TECH_CREATE_CHANNEL`.
### Изменение профиля канала
Последующие изменения аватара, человекочитаемого имени или описания канала пишутся отдельным скрытым `TEXT_CHANNEL_META (subType=90)`.
Текстовое содержимое body использует тот же формат полного снимка профиля:
```text
<S:title;v=1;Новое имя канала>
<S:ava;v=1;sz=248193;sha256=3f2c8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;ar=AbCdEfAbCdEfAbCdEfAbCdEfAbCdEfAbCdEfAbCdE>
Новое описание канала
```
Каждый `TEXT_CHANNEL_META` является полным состоянием профиля на момент записи. Если аватара нет, тег `S:ava` не пишется. Если описания нет, после meta-тегов не добавляется хвостовой текст. Полная спецификация находится в `docs/Blockchain/16_TEXT_Channel_Meta.md`.
## 7. Хватает ли функций сейчас
Коротко: **для записи событий в блокчейн — хватает**, для полноценного клиентского чтения — **пока не хватает**.
Что есть:
- единый надёжный write-путь `AddBlock`;
- есть `GetFriendsLists` и API по `UserParam`;
- есть унифицированные коды ошибок и поля для ресинхронизации.
Что пока ограничивает продукт:
- нет полноценного read API для каналов/постов/тредов;
- нет API списка подписок с серверными счётчиками непрочитанного;
- нет ленты событий (новые ответы/лайки/подписки) как отдельного RPC.
## 8. Рекомендации по клиенту при записи блоков
1. Перед отправкой держать локальный `lastNumber/lastHash`.
2. При `bad_prev_hash` или `bad_block_number`:
- взять `serverLastGlobalNumber/serverLastGlobalHash` из ошибки,
- пересобрать следующий блок на актуальной вершине.
3. Для edit-блоков всегда ссылаться на **оригинальный** блок, а не на предыдущий edit.
4. Для связей/подписок использовать target на **root** (HEADER или CREATE_CHANNEL), а не на произвольный пост.
## 9. USER_PARAM для «личных данных»
Да, на текущем API это можно добавить **без изменения серверного кода**:
- в `UserParam` поле `param` сейчас не ограничено фиксированным справочником;
- сервер хранит пары `param -> value` как строки (при наличии корректной подписи и `time_ms`);
- чтение уже есть через `GetUserParam` и `ListUserParams`.
Рекомендуемый стартовый набор ключей для профиля (MVP):
- `name`
- `last_name`
- `address_physical`
- `address_web`
- `phone`
Практическая рекомендация: заранее зафиксировать единый словарь ключей в клиенте/документации, чтобы избежать дублей вида `lastname` vs `last_name`, `site` vs `address_web` и т.д.
Ограничения, которые важно учесть:
- сейчас нет серверной ACL-политики чтения параметров (в MVP их может читать любой клиент, который знает `login`);
- нет валидации формата значений для конкретных ключей (телефон, URL и т.д. проверяются только на стороне клиента);
- нет отдельного индекса/поиска по этим полям — только точечное чтение и listing по `login`.
---
## 10. `GetBlockchainBlock`
### Назначение
Публичное чтение одного конкретного блока из цепочки.
Нужно для:
- открытого чтения блокчейна по одному блоку;
- межсерверной синхронизации;
- восстановления/докачки отсутствующего хвоста цепочки.
### JSON формат запроса
`op = "GetBlockchainBlock"`.
```json
{
"op": "GetBlockchainBlock",
"requestId": "req-2001",
"payload": {
"blockchainName": "alice-001",
"blockNumber": 12
}
}
```
Поля `payload`:
- `blockchainName` — обязательно, формат `login-NNN`.
- `blockNumber` — обязательно, номер блока в цепочке, `>= 0`.
### Успешный ответ
```json
{
"op": "GetBlockchainBlock",
"requestId": "req-2001",
"status": 200,
"ok": true,
"payload": {
"blockchainName": "alice-001",
"blockNumber": 12,
"blockHash": "9f0eaabbccddeeff00112233445566778899aabbccddeeff0011223344556677",
"blockBytesB64": "AAAB..."
}
}
```
### Ошибки
- `400 / BAD_FIELDS` — некорректные `blockchainName` или `blockNumber`.
- `404 / BLOCK_NOT_FOUND` — такого блока нет.
- `500 / INTERNAL_ERROR` — внутренняя ошибка сервера.
После успешного локального AddBlock полный DataItem хранится в PostgreSQL и ставится в Arweave publish queue. Импорт из Arweave использует ту же проверку, но не ставит блок на повторную публикацию.
File diff suppressed because it is too large Load Diff
-137
View File
@@ -1,137 +0,0 @@
# Карта реализации SHiNE Archive Publisher v1.0
Этот документ связывает протокол с конкретными файлами проекта. Он нужен, чтобы другой агент мог быстро понять, где искать каждую часть реализации.
## 1. Новый Java-модуль `shine-server-archive`
Путь: `SHiNE-server/shine-server-archive/`.
Основные классы:
- `ArchivePublisherScheduler` — включает publisher только при `archive.publish.enabled=true`, сразу продолжает незавершённый job после рестарта и планирует новый snapshot один раз в сутки в `archive.publish.time`. Следующая дата вычисляется по `ZoneId`, а не как `+24h`, поэтому DST не сдвигает локальную полночь.
- `ArchivePublisherService` — state machine: snapshot → локальный файл → Arweave → confirmations → User PDA → Solana finalized → cursor commit.
- `ArchivePublisherConfig` — читает настройки. Для Solana RPC отдельного archive URL нет: используется `solana.users.sync.rpcUrl`, затем fallback на `solana.rpcUrl`.
- `ShineArchiveWriter` — сериализует бинарный big block `SHINE-ARCHIVE v1.0`, FULL reference table, по одному chunk на `blockchain_name`, footer/hash/signature.
- `ArchiveFileNames` — временное и финальное локальные имена.
- `ArweaveArchiveService` + `ArweaveMerkle` — Arweave v2 transaction + chunk upload + polling confirmations.
- `SolanaArchiveHeadWriter` — обычный `update_user_pda`: root signature + текущая last-block signature + client key fee payer, затем ожидание `finalized`.
- `ArchiveKeyLoader` — читает Ed25519 seed/keypair из raw 32/64 bytes, Solana JSON 32/64 или Base64/PKCS8.
## 2. Локальное состояние PostgreSQL
Миграция: `SHiNE-server/shine-server-db/src/main/resources/postgres/migration_v22.sql`.
Таблицы:
### `archive_chain_cursor`
Одна строка на `blockchain_name`. Хранит только **последнее окончательно опубликованное** состояние:
- последний source block number/hash;
- big block number/hash, где находится последний chunk;
- offset/size последнего chunk.
Если blockchain не попала в новый big block, эта строка не меняется.
### `archive_publish_job`
Crash-safe state одной большой публикации и путь к локальному файлу.
Основные состояния:
`SNAPSHOT_CREATED → FILE_BUILT → ARWEAVE_UPLOADED → ARWEAVE_CONFIRMED → SOLANA_SUBMITTED → SOLANA_FINALIZED → CURSORS_COMMITTED`.
### `archive_publish_job_chain`
Frozen range каждой blockchain текущего job + старые и новые координаты chunk. Пока job не finalized, `archive_chain_cursor` не двигается.
`DatabaseInitializer` автоматически применяет migration v22 при старте существующей БД. Новая БД создаётся уже со схемой v22.
## 3. Как считается первая дельта
`ArchivePublisherService.createFrozenJob()` проходит по `BlockchainStateDAO.listAll()`.
- Если cursor для `blockchain_name` отсутствует: `from = 0`, поэтому первый архив содержит всё локально известное состояние `0..head`.
- Если cursor существует: `from = last_archived + 1`.
- Перед продолжением проверяется hash cursor-блока.
Папка `data/archive` сама по себе **не является источником истины** о том, был ли первый архив. Источник истины — БД cursor/job. Поэтому удаление локального файла не приводит к ошибочной повторной полной публикации.
## 4. Локальный lifecycle файла
До появления Arweave TX ID:
`<login>.<00001>.<dd.MM.yy>.tmp.SHiNE-archive`
После полной успешной загрузки transaction header + chunks в Arweave:
`<login>.<00001>.<dd.MM.yy>.<ARWEAVE_TX_ID>.SHiNE-archive`
Дата — реальная дата freeze snapshot в timezone archive publisher-а. Номер начинается с `00001`. Пять цифр — минимальная ширина, а не лимит.
После rename файл остаётся локально. При crash после сохранения TX ID, но до rename, recovery переименует тот же файл и не загрузит его повторно.
## 5. User PDA block type `100`
Содержимое:
```text
u8 block_type = 100
u8 block_version = 0
bytes[32] archive_tx_id
bytes[32] archive_hash
```
Используется существующий `update_user_pda`; отдельной instruction нет.
Совместимость:
- legacy update без archive extension должен сохранить старый archive head;
- новый update может заменить/очистить block `100`;
- Java/JS codecs и PostgreSQL Solana sync умеют читать новый блок.
Ключевой Rust-файл: `shine-solana/shine/programs/shine_users/src/lib.rs`.
## 6. Startup сервера
`WsServer` после текущего Solana/users sync и inter-server blockchain sync вызывает `ArchivePublisherScheduler.startOrLog()`.
При `archive.publish.enabled=false` scheduler пишет лог о выключенной функции и больше ничего не делает. Ключи/Arweave wallet на обычном сервере тогда не требуются.
## 7. Legacy TestFreeAvatar
Старый временный `TestFreeAvatarArweaveService` больше не является частью активного WS-протокола. Registry/API документация убраны. При наложении changed-files ZIP поверх старого дерева старые исходники физически останутся, поэтому их список для удаления находится в `05_PATCH_CONTENTS_AND_REMOVALS.md`.
## Trusted importer / location index / Viewer
Server importer:
```text
shine-server-archive/src/main/java/server/archive/ArchiveImportConfig.java
shine-server-archive/src/main/java/server/archive/ArchiveImportScheduler.java
shine-server-archive/src/main/java/server/archive/ArchiveImportService.java
shine-server-archive/src/main/java/server/archive/ShineArchiveReader.java
```
Database:
```text
shine-server-db/src/main/java/shine/db/dao/ArchiveImportDAO.java
shine-server-db/src/main/java/shine/db/archive/ArchiveBlockchainLocation.java
shine-server-db/src/main/java/shine/db/archive/ArchivePublisherHead.java
shine-server-db/src/main/resources/postgres/migration_v23.sql
shine-server-solana-users-sync/src/main/java/sync/storage/postgres/PostgresStorageRepository.java
```
`PostgresStorageRepository` сохраняет `archive_imported=true` при повторном sync того же head и автоматически сбрасывает флаг в `false`, если `archive_head_tx_id` или `archive_head_hash` изменились.
WS API:
```text
GetArchiveBlockchainLocation
```
UI:
```text
shine-UI/js/pages/blockchain-archive-view.js
shine-UI/Blockchain-Viewer.html
```
`Blockchain-Viewer.html` получает `tx + offset + size + blockchain`, идёт назад по `PreviousBlockchainChunkRef` и использует существующий parser каналов старого Viewer-а.
-466
View File
@@ -1,466 +0,0 @@
# Деплой SHiNE Archive Publisher v1.0 на тестовый сервер
Документ рассчитан на человека или автономного coding/deploy агента. Выполнять шаги по порядку. Не включать publisher до проверки Solana-программы и ключей.
## 0. Что именно меняется
Нужны изменения одновременно в:
1. серверном Java-коде;
2. PostgreSQL schema v23;
3. Solana-программе `shine_users` (PDA block type `100` + backward-compatible update parser);
4. Java/JS User PDA codecs.
**Критично:** новый серверный writer отправляет расширенный обычный `update_user_pda`. Если в целевом кластере работает старая `shine_users`, включать publisher нельзя.
---
# 1. Применить пакет к исходникам
ZIP из этой поставки содержит только новые/изменённые файлы с путями относительно корня репозитория.
Сделать backup текущего проекта, затем распаковать ZIP поверх рабочего дерева.
После распаковки удалить legacy-файлы из списка `05_PATCH_CONTENTS_AND_REMOVALS.md`.
Проверить:
```bash
git status --short
```
или, если это не git checkout, сравнить список файлов с manifest из той же документации.
---
# 2. Обязательно обновить `shine_users` в нужном Solana-кластере
## 2.1. Проверить целевой кластер
Не деплоить вслепую. Сначала:
```bash
solana config get
```
и проверить RPC/кластер, upgrade authority и Program ID. В проекте `shine_users` использует Program ID:
```text
SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6
```
Если тестовый сервер использует mainnet RPC, обновляется именно mainnet-программа. Если тестовый контур использует devnet — сначала убедиться, что программа с нужным ID действительно существует в devnet.
## 2.2. Собрать Solana program
```bash
cd shine-solana/shine
anchor build
```
Минимальная host-проверка Rust, если Anchor/SBF toolchain временно недоступен:
```bash
cargo build -p shine_users
```
Но для реального deploy нужен SBF/Anchor build.
## 2.3. Обновить существующую программу
Использовать существующий project deploy/upgrade authority. Типовой вариант:
```bash
solana program deploy target/deploy/shine_users.so \
--program-id target/deploy/shine_users-keypair.json \
--upgrade-authority /PATH/TO/UPGRADE_AUTHORITY.json \
--url <TARGET_RPC_URL>
```
Если в проекте используется рабочий Anchor deploy workflow, допустимо использовать его вместо прямого `solana program deploy`; главное — сохранить тот же Program ID.
После обновления:
```bash
solana program show SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6 --url <TARGET_RPC_URL>
```
---
# 3. Собрать серверный JAR
Из корня репозитория:
```bash
./gradlew clean shadowJar
```
Ожидаемый файл:
```text
SHiNE-server/build/libs/shine-server.jar
```
Если Gradle wrapper не может скачать зависимости, сборку выполнять на машине/CI с доступом к Maven/Gradle или с уже заполненным cache.
---
# 4. Подготовить PostgreSQL backup
Перед первым стартом версии со schema v23 сделать backup тестовой БД. Например:
```bash
pg_dump -Fc -d '<DATABASE_URL_OR_NAME>' -f shine-before-archive-v22.dump
```
Точная команда зависит от текущей схемы доступа PostgreSQL.
При старте сервер последовательно применит `migration_v22.sql` и `migration_v23.sql`, если это требуется текущей версии БД. Вручную migrations выполнять обычно не нужно.
После старта проверить:
```sql
SELECT * FROM db_schema_version WHERE id=1;
```
Ожидается:
```text
schema_version = 23
```
И наличие:
```sql
SELECT to_regclass('public.archive_chain_cursor');
SELECT to_regclass('public.archive_publish_job');
SELECT to_regclass('public.archive_publish_job_chain');
```
---
# 5. Подготовить секреты на тестовом сервере
Пример:
```bash
sudo -u player mkdir -p /home/player/SHiNE/secrets
sudo chmod 700 /home/player/SHiNE/secrets
```
Положить:
```text
/home/player/SHiNE/secrets/archive-arweave-wallet.json
/home/player/SHiNE/secrets/server-root.key
/home/player/SHiNE/secrets/server-client.key
```
Права:
```bash
sudo chown player:player /home/player/SHiNE/secrets/*
sudo chmod 600 /home/player/SHiNE/secrets/*
```
### Форматы root/client key
Поддерживаются:
- raw seed 32 bytes;
- raw keypair 64 bytes;
- Solana JSON array на 32/64 байта;
- Base58 seed на 32 байта или Solana secret key на 64 байта;
- Base64 raw/PKCS8, где seed извлекается из последних 32 bytes.
### Arweave wallet
Ожидается RSA JWK с полями `n,e,d,p,q,dp,dq,qi`. Кошелёк должен иметь достаточно AR для размера первого полного архива.
---
# 6. Настроить внешний `application.properties`
Сервер читает внешний `application.properties` из **WorkingDirectory процесса** и накладывает его поверх встроенного конфига. Сохранять существующие DB/Solana/server параметры и добавить archive-секцию.
Минимум:
```properties
archive.publish.enabled=true
archive.publish.time=00:00
archive.publish.zoneId=Europe/Warsaw
archive.workDir=data/archive
archive.maxFileBytes=4000000000
archive.arweave.gateway=https://arweave.net
archive.arweave.walletJwkPath=/home/player/SHiNE/secrets/archive-arweave-wallet.json
archive.arweave.minConfirmations=1
archive.arweave.confirmPollSeconds=30
archive.arweave.confirmTimeoutMinutes=180
archive.solana.rootKeyPath=/home/player/SHiNE/secrets/server-root.key
archive.solana.clientKeyPath=/home/player/SHiNE/secrets/server-client.key
archive.solana.confirmPollSeconds=5
archive.solana.confirmTimeoutMinutes=30
archive.solana.commitment=finalized
```
`archive.publish.zoneId` выбрать осознанно. Если оставить пустым, используется timezone JVM/машины. Для ежедневного запуска ровно в нужную локальную полночь лучше задать ZoneId явно.
### Solana RPC
**Отдельного archive RPC нет.** Writer использует:
1. `solana.users.sync.rpcUrl`, если он задан;
2. иначе `solana.rpcUrl`.
Поэтому существующий рабочий RPC не дублировать в archive settings.
---
# 7. Убедиться, что server login соответствует ключам
`server.SHiNE.login` должен быть тем User PDA, чей archive head будет обновляться.
На startup `SolanaArchiveHeadWriter` проверяет:
```text
derive(root private) == UserPDA.root_key
derive(client private) == UserPDA.client_key
```
При несовпадении archive publisher не стартует.
---
# 8. Развернуть JAR
Можно использовать существующий `deploy/scripts/deploy_server.sh`. Он:
- собирает `shadowJar`;
- копирует JAR;
- создаёт/обновляет systemd unit;
- перезапускает сервис.
Типовой запуск задаётся уже существующими переменными проекта. Либо вручную заменить `shine-server.jar` в рабочей директории и перезапустить systemd service.
После deploy убедиться, что WorkingDirectory содержит внешний `application.properties`.
---
# 9. Первый startup
Смотреть лог:
```bash
sudo journalctl -u <SERVICE_NAME> -f
```
Ожидаемые события:
1. DB migration до v22;
2. обычный Solana users sync;
3. обычный server-to-server blockchain sync;
4. archive publisher preflight;
5. строка примерно:
```text
Archive publisher включён: login=... dir=data/archive dailyAt=00:00 zone=...
Следующая архивная публикация запланирована на ...
```
Если остался незавершённый job, он будет продолжен **сразу после старта**, не ожидая полуночи. Новый snapshot создаётся только по расписанию.
---
# 10. Что произойдёт в первую полночь
Если `archive_chain_cursor` пуст:
- сервер проходит все локально известные `blockchain_name`;
- для каждой берёт range `0..current_head`;
- создаёт первый big block `#1`;
- для каждой blockchain создаёт максимум один `UserBlockchainChunk`;
- внутри chunk лежат все её raw records из frozen range;
- backlink первого chunk пустой (`previous_big_block_ref = 0xFFFFFFFF`);
- создаёт локальный файл, например:
```text
data/archive/archive01.00001.12.09.26.tmp.SHiNE-archive
```
- загружает его в Arweave;
- после успешной загрузки переименовывает тот же файл, например:
```text
data/archive/archive01.00001.12.09.26.<REAL_TX_ID>.SHiNE-archive
```
- ждёт confirmations;
- обычным `update_user_pda` записывает block type `100`;
- ждёт Solana `finalized`;
- только затем commit-ит cursors.
Если новых данных нет, пустой big block не создаётся.
---
# 11. Проверка результата
## Локальные файлы
```bash
ls -lah data/archive/
```
После успешного upload `.tmp.SHiNE-archive` для завершённого job оставаться не должен; должен быть файл с реальным TX ID в имени.
## Job DB
```sql
SELECT id, big_block_number, status, created_at_ms, local_archive_path,
arweave_confirmations, solana_signature, error_text
FROM archive_publish_job
ORDER BY id DESC
LIMIT 10;
```
Успех:
```text
status = CURSORS_COMMITTED
```
## Cursors
```sql
SELECT blockchain_name,
last_archived_source_block_number,
last_archive_big_block_number,
last_chunk_offset,
last_chunk_size
FROM archive_chain_cursor
ORDER BY blockchain_name;
```
## Локальная проекция User PDA
После очередной Solana sync:
```sql
SELECT login, record_number, archive_head_tx_id, archive_head_hash
FROM solana_user_pda_current
WHERE login = '<SERVER_LOGIN>';
```
`archive_head_tx_id` и `archive_head_hash` должны быть непустыми.
## Arweave
TX берётся прямо из имени финального файла. Проверить:
```bash
curl -sS 'https://arweave.net/tx/<TX_ID>/status'
```
---
# 12. Быстрый тест до полуночи
Если не хочется ждать 00:00, на тестовом сервере временно установить `archive.publish.time` на ближайшие 5–10 минут в будущем в выбранной `archive.publish.zoneId`, затем перезапустить сервис.
После проверки вернуть:
```properties
archive.publish.time=00:00
```
Не использовать интервал в минутах: scheduler специально работает по календарному локальному времени один раз в сутки.
---
# 13. Откат / выключение
Самый безопасный функциональный rollback:
```properties
archive.publish.enabled=false
```
и рестарт сервера. Тогда обычная серверная работа продолжается, archive scheduler ничего не публикует.
Не удалять `archive_chain_cursor`/job таблицы без причины: они нужны, чтобы после повторного включения publisher продолжил дельту, а не загрузил всю историю заново.
Уже опубликованные Arweave данные являются постоянными и обычным rollback сервера не удаляются.
---
# 14. Наиболее вероятные ошибки
### `Root key archive publisher-а не совпадает с User PDA`
Положен неправильный root key или неверный `server.SHiNE.login`.
### `Client key archive publisher-а не совпадает с User PDA`
Неверный client key.
### `Недостаточно AR`
Пополнить Arweave wallet. Первый архив может быть существенно больше ежедневных дельт.
### Arweave upload прошёл, Solana update не прошёл
Не удалять локальный файл/job. После исправления RPC/program/key причины restart продолжит незавершённый job.
### `archive cursor hash не совпадает`
Локальная blockchain изменилась относительно уже зафиксированного cursor. Не форсировать публикацию; сначала разобраться с resync/fork.
### Старый `shine_users`
Если новый update payload отклоняется программой, проверить, что целевая Solana `shine_users` действительно обновлена этой версией.
## Trusted archive importer
На обычном тестовом сервере publisher можно оставить выключенным, но разрешить импорт от конкретного архиватора:
```properties
archive.publish.enabled=false
archive.import.allowedPublishers=<LOGIN_ARCHIVE_SERVER>
archive.import.intervalMinutes=60
archive.import.workDir=data/archive-import
```
Несколько логинов:
```properties
archive.import.allowedPublishers=server-a,server-b
```
Пустая строка означает, что importer не запускается.
После старта проверить логи:
```text
Archive importer включён. approvedPublishers=...
```
Если сервер подключается к publisher впервые, importer скачает head, прочитает FULL reference table и обработает все ещё не известные big blocks от старых к новым.
Проверка БД:
```sql
SELECT login, archive_head_tx_id, archive_imported, archive_last_imported_tx_id
FROM solana_user_pda_current
WHERE is_server=TRUE AND archive_head_tx_id<>''
ORDER BY login;
SELECT blockchain_name, publisher_login, arweave_tx_id,
big_block_number, chunk_offset, chunk_size, source_last_block_number
FROM archive_blockchain_location
ORDER BY updated_at_ms DESC
LIMIT 20;
```
После деплоя UI файл должен быть доступен по:
```text
https://<UI_HOST>/Blockchain-Viewer.html
```
В приложении: `Настройки → Архив блокчейна`.
-113
View File
@@ -1,113 +0,0 @@
# Проверка и эксплуатация Archive Publisher
## Перед ночным тестом
- [ ] Новый `shine_users` уже развёрнут на том Solana-кластере, который использует сервер.
- [ ] Серверный JAR собран из этого пакета.
- [ ] БД забэкаплена.
- [ ] `archive.publish.enabled=true`.
- [ ] `archive.publish.time=00:00`.
- [ ] `archive.publish.zoneId` соответствует желаемой локальной полуночи.
- [ ] `server.SHiNE.login` соответствует root/client keys.
- [ ] Arweave JWK читается пользователем процесса.
- [ ] На Arweave wallet достаточно AR.
- [ ] `data/archive` доступна на запись.
- [ ] В логе есть `Archive publisher включён` и точное время следующего запуска.
## Во время job
Нормальная последовательность логов/статусов:
```text
SNAPSHOT_CREATED
FILE_BUILT
ARWEAVE_UPLOADED
ARWEAVE_CONFIRMED
SOLANA_SUBMITTED
SOLANA_FINALIZED
CURSORS_COMMITTED
```
После `FILE_BUILT` существует `.tmp.SHiNE-archive`.
После полного Arweave upload имя уже содержит настоящий TX ID.
## После успешного первого job
Проверить:
```bash
find data/archive -maxdepth 1 -type f -name '*.SHiNE-archive' -ls
```
```sql
SELECT big_block_number, status, local_archive_path, arweave_confirmations
FROM archive_publish_job ORDER BY id DESC LIMIT 1;
```
```sql
SELECT count(*) AS archived_blockchains FROM archive_chain_cursor;
```
```sql
SELECT login, archive_head_tx_id, archive_head_hash
FROM solana_user_pda_current
WHERE login='<SERVER_LOGIN>';
```
## Проверка второй публикации
До следующей полуночи добавить несколько новых SHiNE records только в часть blockchain. После следующего job:
- в новый big block должны попасть только изменившиеся blockchain;
- одна blockchain в новом big block должна иметь один chunk независимо от числа новых records;
- cursor blockchain, которая не изменилась, должен остаться на старом big block/chunk;
- backlink изменившегося chunk должен указывать на предыдущий chunk этой же blockchain;
- FULL reference table нового big block должна содержать все предыдущие finalized big blocks.
## Crash/restart сценарии
### Restart после FILE_BUILT
Должен использоваться тот же frozen job и тот же локальный файл.
### Restart после ARWEAVE_UPLOADED
Не должно быть повторной оплаты/upload. Если TX сохранён, но rename не успел произойти, recovery переименует `.tmp` в имя с TX ID.
### Restart после SOLANA_FINALIZED
При совпадении PDA head с job сервер должен только commit cursors.
## Обычный сервер без публикации
Проверить отдельно:
```properties
archive.publish.enabled=false
```
Сервер должен запускаться без Arweave/root/client archive key files и не создавать `archive_publish_job`.
## Проверка trusted importer
1. На принимающем сервере указать только тестовый publisher:
```properties
archive.import.allowedPublishers=<publisher-login>
```
2. Перезапустить сервер.
3. Дождаться Solana PDA sync и цикла importer-а.
4. Проверить, что у publisher в `solana_user_pda_current` после успешного цикла `archive_imported=true`, а `archive_last_imported_tx_id=archive_head_tx_id`.
5. Проверить `archive_blockchain_location`.
6. Для blockchain, которой локально не хватало блоков, убедиться, что `blockchain_state.last_block_number` вырос.
7. Для уже существующих блоков importer должен пропускать совпадающий hash, а не создавать дубликат.
8. Временно удалить publisher из whitelist и убедиться, что новые archive heads больше не скачиваются.
### Проверка Viewer
Открыть `Настройки → Архив блокчейна`, получить ссылку и проверить:
- `tx`, `offset`, `size`, `blockchain` присутствуют;
- Viewer собирает несколько chunks по backlink;
- неправильный `blockchain` в URL приводит к ошибке проверки;
- `channel` открывает нужный канал;
- `message` прокручивает к нужному block number.
@@ -1,38 +0,0 @@
# Состав текущего patch-пакета
Этот этап рассчитан **поверх последнего рабочего ZIP**, присланного после успешного запуска archive publisher.
Пакет этого этапа добавляет:
- trusted archive importer;
- whitelist publisher-ов через настройки сервера;
- строгую проверку больших `SHINE-ARCHIVE`;
- импорт недостающих raw SHiNE blocks через обычный validator `AddBlock`;
- локальные поля состояния импорта в `solana_user_pda_current` и таблицу `archive_blockchain_location`;
- schema migration v23;
- WS API `GetArchiveBlockchainLocation`;
- экран `Настройки → Архив блокчейна`;
- `shine-UI/Blockchain-Viewer.html`;
- документацию importer/viewer.
## Удаления
В **этом** обновлении удалять файлы не требуется.
Старые test/free-avatar исходники, если они всё ещё физически присутствуют в рабочем дереве, этим patch-пакетом не затрагиваются. Они не относятся к trusted archive importer и не должны удаляться автоматически при наложении этого обновления.
## Как накладывать ZIP changed-files
ZIP содержит только новые/изменённые файлы с путями от корня репозитория.
Распаковать поверх той рабочей версии, из которой сделан пакет, с заменой совпадающих файлов.
После наложения:
```bash
./gradlew shadowJar
```
Если Gradle wrapper ещё не установлен локально, сначала обеспечить доступ к уже используемой версии Gradle/кэшу.
При старте сервер сам должен поднять schema с v22 до v23.
-50
View File
@@ -1,50 +0,0 @@
# Manifest changed/new files
Основа сравнения: последний присланный рабочий ZIP `3d14e34c-4249-4e11-8042-3f3349c8e9fd.zip`.
- Изменённых файлов: 19
- Новых файлов: 14
- Удаляемых файлов: 0
## Изменённые файлы
- `SHiNE-server/shine-server-archive/src/main/java/server/archive/ArchivePublisherService.java`
- `SHiNE-server/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java`
- `SHiNE-server/shine-server-db/src/main/resources/postgres/schema_v1.sql`
- `SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java`
- `SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_AddBlock_Handler.java`
- `SHiNE-server/shine-server-solana-users-sync/src/main/java/sync/storage/postgres/PostgresStorageRepository.java`
- `SHiNE-server/src/main/java/server/ws/WsServer.java`
- `SHiNE-server/src/main/resources/application.properties`
- `docs/Archive/01_PROTOCOL_v1.0.md`
- `docs/Archive/02_IMPLEMENTATION_MAP.md`
- `docs/Archive/03_DEPLOY_TEST_SERVER.md`
- `docs/Archive/04_TEST_AND_OPERATIONS.md`
- `docs/Archive/05_PATCH_CONTENTS_AND_REMOVALS.md`
- `docs/Archive/06_FILE_MANIFEST.md`
- `docs/Archive/README.md`
- `docs/Archive/archive-publisher.example.properties`
- `shine-UI/js/app.js`
- `shine-UI/js/pages/settings-view.js`
- `shine-UI/js/services/auth-service.js`
## Новые файлы
- `SHiNE-server/shine-server-archive/src/main/java/server/archive/ArchiveImportConfig.java`
- `SHiNE-server/shine-server-archive/src/main/java/server/archive/ArchiveImportScheduler.java`
- `SHiNE-server/shine-server-archive/src/main/java/server/archive/ArchiveImportService.java`
- `SHiNE-server/shine-server-archive/src/main/java/server/archive/ShineArchiveReader.java`
- `SHiNE-server/shine-server-db/src/main/java/shine/db/archive/ArchiveBlockchainLocation.java`
- `SHiNE-server/shine-server-db/src/main/java/shine/db/archive/ArchivePublisherHead.java`
- `SHiNE-server/shine-server-db/src/main/java/shine/db/dao/ArchiveImportDAO.java`
- `SHiNE-server/shine-server-db/src/main/resources/postgres/migration_v23.sql`
- `SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_GetArchiveBlockchainLocation_Handler.java`
- `SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/entyties/Net_GetArchiveBlockchainLocation_Request.java`
- `SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/entyties/Net_GetArchiveBlockchainLocation_Response.java`
- `docs/Archive/07_ARCHIVE_IMPORT_AND_VIEWER.md`
- `shine-UI/Blockchain-Viewer.html`
- `shine-UI/js/pages/blockchain-archive-view.js`
## Удаления
На этом этапе файлов для удаления нет.
@@ -1,303 +0,0 @@
# Импорт доверенных SHINE-ARCHIVE и Blockchain Viewer
Этот документ описывает вторую половину архивной системы: как обычный SHiNE-сервер узнаёт о новых archive head других серверов, кому доверяет, как импортирует недостающие SHiNE-блоки и как UI получает ссылку на историю конкретной `blockchain_name`.
## 1. Источник archive head
Archive importer **не делает отдельные Solana RPC-запросы**.
Уже существующий Solana Users Sync разбирает User PDA block type `100` и копирует его в локальную таблицу:
```text
solana_user_pda_current.archive_head_tx_id
solana_user_pda_current.archive_head_hash
```
Для локального состояния importer v23 добавляет туда же:
```text
archive_imported BOOLEAN
archive_last_imported_tx_id TEXT
```
Это локальные поля сервера, в Solana они не записываются.
Когда обычный Solana Users Sync видит тот же archive head повторно, `archive_imported` сохраняется как есть.
Когда `archive_head_tx_id` или `archive_head_hash` изменился:
```text
archive_imported = false
```
а `archive_last_imported_tx_id` сохраняет последнюю успешно обработанную точку и позволяет продолжить после сбоя.
## 2. Whitelist доверенных publisher-ов
В `application.properties` задаётся список логинов серверов, архивы которых разрешено принимать:
```properties
archive.import.allowedPublishers=archive-server-1,archive-server-2
archive.import.intervalMinutes=60
archive.import.workDir=data/archive-import
```
Правила:
- логины разделяются запятыми;
- сравнение без учёта регистра;
- пустое `archive.import.allowedPublishers=` полностью выключает importer;
- принимаются только строки `solana_user_pda_current` с `is_server=true`;
- whitelist является только первым фильтром, криптографические проверки всё равно обязательны.
## 3. Периодическая проверка
После запуска сервера importer делает первую проверку примерно через 10 секунд, затем по умолчанию раз в 60 минут.
Каждый цикл — дешёвый запрос только к локальной PostgreSQL:
```text
approved publisher
AND is_server=true
AND archive_head_tx_id != ''
AND archive_imported=false
```
Если таких строк нет, Arweave не вызывается.
## 4. Проверки archive block
Для каждого pending publisher сервер проверяет:
1. publisher находится в whitelist;
2. `archive_head_tx_id/archive_head_hash` уже пришли через обычный User PDA sync;
3. SHA-256 скачанного файла совпадает с `archive_head_hash`;
4. `creator_login == closer_login == publisher login`;
5. Ed25519 archive signature проверяется root key publisher-а из User PDA;
6. каждый вложенный raw SHiNE block проходит обычную SHiNE-проверку через существующий `AddBlock` path.
Подпись большого архива защищает контейнер и навигацию, а подписи обычных SHiNE blocks защищают сами пользовательские данные.
## 5. Как определяется, что head новый
Отдельная таблица обработанных TX для основной логики не нужна.
Текущий User PDA snapshot уже содержит:
```text
archive_head_tx_id
archive_head_hash
archive_imported
archive_last_imported_tx_id
```
Пример:
```text
archive_head_tx_id = TX100
archive_imported = false
archive_last_imported_tx_id = TX97
```
Это означает: Solana уже объявила `TX100` текущей головой publisher-а, но локальный сервер успел импортировать только до `TX97`.
После полной успешной обработки `TX100`:
```text
archive_imported = true
archive_last_imported_tx_id = TX100
```
При следующем новом PDA head Users Sync сам сбросит `archive_imported=false`.
## 6. Догон пропущенных больших блоков
Если сервер был выключен и вместо `TX97` сразу увидел `TX100`, он скачивает и проверяет `TX100`, читает его FULL reference table и находит `TX97`.
После этого импортирует только:
```text
TX98
TX99
TX100
```
После каждого полностью импортированного большого блока `archive_last_imported_tx_id` сдвигается вперёд.
Если сервер впервые видит publisher и `archive_last_imported_tx_id` пустой, импортируются все previous refs от старых к новым, затем текущий head.
Если непустой `archive_last_imported_tx_id` отсутствует в FULL history текущего head, importer останавливается: это рассматривается как возможная смена/fork archive chain, а не как повод молча забыть старый cursor.
## 7. Crash recovery
Если процесс упал после `TX98`, но до `TX100`:
```text
archive_imported = false
archive_last_imported_tx_id = TX98
```
Следующий часовой цикл продолжит с `TX99`.
Если процесс успел импортировать head и записать `archive_last_imported_tx_id = TX100`, но упал до установки `archive_imported=true`, следующий цикл просто завершит отметку без повторной загрузки всей цепочки.
Все cursor updates выполняются условно по ожидаемому `archive_head_tx_id`. Если обычный Solana Users Sync успел заменить head во время импорта, старый процесс не сможет пометить новый head импортированным.
## 8. Импорт `UserBlockchainChunk`
Для каждого chunk:
1. берётся lock этой `blockchain_name`;
2. если локального `blockchain_state` нет, identity создаётся по синхронизированному User PDA;
3. raw records разбираются как обычные `BchBlockEntry`;
4. уже существующий block допускается только при совпадении hash;
5. новый block должен идти строго `localLast + 1`;
6. новый block добавляется существующим validator/write path;
7. конфликт hash или gap останавливает импорт этой archive chain.
## 9. Индекс последнего archive chunk
Таблица:
```text
archive_blockchain_location
```
содержит для каждой `blockchain_name`:
```text
blockchain_name
publisher_login
arweave_tx_id
archive_hash
big_block_number
chunk_offset
chunk_size
source_last_block_number
updated_at_ms
```
Если blockchain встретилась в новом archive block, её location обновляется. Если не встретилась — старая ссылка остаётся.
Эту таблицу заполняют как trusted importer, так и локальный archive publisher.
## 10. API для UI
WS operation:
```text
GetArchiveBlockchainLocation
```
Request:
```json
{
"op": "GetArchiveBlockchainLocation",
"blockchainName": "alice-001"
}
```
Response содержит:
```text
blockchainName
publisherLogin
arweaveTxId
archiveHash
bigBlockNumber
chunkOffset
chunkSize
sourceLastBlockNumber
```
## 11. UI и ссылка Viewer
В настройках пользователя есть экран `Архив блокчейна`.
Viewer-файл:
```text
shine-UI/Blockchain-Viewer.html
```
Основные параметры ссылки:
```text
/Blockchain-Viewer.html
?tx=<ARWEAVE_TX_ID>
&offset=<CHUNK_OFFSET>
&size=<CHUNK_SIZE>
&blockchain=<BLOCKCHAIN_NAME>
```
Дополнительно:
```text
&channel=<CHANNEL_NAME>
&message=<BLOCK_NUMBER>
```
`blockchain` используется также для проверки: если загруженный chunk имеет другое имя blockchain, Viewer прекращает обработку.
`channel` открывает нужный канал, а `message` прокручивает к указанному сообщению/block number и выделяет его.
## 12. Как Viewer собирает всю цепочку
Viewer начинает с последнего `TX + offset + size`:
```text
последний UserBlockchainChunk
PreviousBlockchainChunkRef
FULL reference table текущего big block
TX предыдущего big block
Range предыдущего chunk
следующий backlink
до NO_REFERENCE
```
Чужие chunks скачивать не требуется.
## 13. Минимальная настройка принимающего сервера
```properties
archive.publish.enabled=false
archive.import.allowedPublishers=server-a,server-b
archive.import.intervalMinutes=60
archive.import.workDir=data/archive-import
```
Если импорт архивов не нужен:
```properties
archive.import.allowedPublishers=
```
Тогда importer вообще не запускается.
## 14. Диагностика PostgreSQL
Pending archive heads:
```sql
SELECT login, archive_head_tx_id, archive_imported, archive_last_imported_tx_id
FROM solana_user_pda_current
WHERE is_server = TRUE
AND archive_head_tx_id <> ''
ORDER BY login;
```
Последние известные пользовательские chunks:
```sql
SELECT blockchain_name, publisher_login, arweave_tx_id,
big_block_number, chunk_offset, chunk_size, source_last_block_number
FROM archive_blockchain_location
ORDER BY updated_at_ms DESC;
```
-42
View File
@@ -1,42 +0,0 @@
# SHiNE Archive Publisher — документация
Эта папка — **актуальная точка входа** для механизма серверной архивации SHiNE в Arweave с фиксацией archive head в Solana User PDA.
Если задачу выполняет другая нейронка/агент, читать документы нужно в таком порядке:
1. `01_PROTOCOL_v1.0.md` — бинарный формат `SHINE-ARCHIVE`, big-block references, `UserBlockchainChunk`, подписи, PDA block `100`, crash-safety.
2. `02_IMPLEMENTATION_MAP.md` — как спецификация разложена по Java/Rust/JS/SQL файлам текущего проекта.
3. `03_DEPLOY_TEST_SERVER.md` — полный порядок установки на тестовый сервер, включая обязательный апгрейд `shine_users`, конфиг, ключи, сборку и запуск.
4. `04_TEST_AND_OPERATIONS.md` — что проверять до полуночи, после полуночи и при сбоях.
5. `05_PATCH_CONTENTS_AND_REMOVALS.md` — какие файлы содержит пакет и какие legacy test-free-avatar файлы нужно удалить при наложении ZIP поверх старого исходника.
6. `07_ARCHIVE_IMPORT_AND_VIEWER.md` — whitelist доверенных publisher-ов, импорт archive chain, индекс последнего chunk, UI и `Blockchain-Viewer.html`.
7. `archive-publisher.example.properties` — пример конфигурации publisher + importer.
## Коротко
- Архиватор **по умолчанию выключен**: `archive.publish.enabled=false`.
- При включении создаёт новый snapshot **один раз в сутки в заданное локальное время**, по умолчанию `00:00`.
- При первом успешном запуске, когда архивных курсоров ещё нет, в первый big block попадает **всё локально известное состояние всех blockchain, начиная с source block 0**.
- Далее публикуется только дельта.
- Один `blockchain_name` в одном big block представлен максимум одним `UserBlockchainChunk`; внутри него лежат все новые raw SHiNE records этой цепочки.
- В конце chunk одна ссылка на предыдущий chunk этой же blockchain. Если blockchain в текущем big block отсутствует, её cursor/head не меняется.
- Готовый файл сначала существует локально как `<login>.<00001>.<dd.MM.yy>.tmp.SHiNE-archive`. После успешной загрузки в Arweave он переименовывается в `<login>.<00001>.<dd.MM.yy>.<REAL_ARWEAVE_TX_ID>.SHiNE-archive` и остаётся локально.
- После Arweave confirmations обычным `update_user_pda` обновляется PDA block type `100`: `archive_tx_id[32] + archive_hash[32]`.
- Cursor commit выполняется только после Solana `finalized`.
## Важно перед тестом
Изменён формат/парсер `shine_users`. **Нельзя просто заменить серверный JAR и включить archive publisher, если целевая Solana-программа `shine_users` ещё не обновлена кодом из этого пакета.** Сначала обновить программу на нужном кластере, затем сервер.
## Импорт архивов других серверов
Импорт по умолчанию также выключен. Настройка:
```properties
archive.import.allowedPublishers=
```
Пустой список означает: не доверять архивам ни одного внешнего сервера. Для разрешения перечислить логины через запятую. Подробности — `07_ARCHIVE_IMPORT_AND_VIEWER.md`.
Текущая схема БД: **v23**. `v22` добавила publisher, `v23` добавляет локальные `archive_imported/archive_last_imported_tx_id`, trusted importer и универсальный `archive_blockchain_location`.
@@ -1,33 +0,0 @@
# Минимальный пример для archive-capable сервера.
# Добавлять во внешний application.properties; существующие DB/Solana/server настройки не удалять.
archive.publish.enabled=true
archive.publish.time=00:00
archive.publish.zoneId=Europe/Warsaw
archive.workDir=data/archive
archive.maxFileBytes=4000000000
archive.arweave.gateway=https://arweave.net
archive.arweave.walletJwkPath=/home/player/SHiNE/secrets/archive-arweave-wallet.json
archive.arweave.minConfirmations=1
archive.arweave.confirmPollSeconds=30
archive.arweave.confirmTimeoutMinutes=180
# Отдельный archive Solana RPC НЕ задаётся.
# Используется solana.users.sync.rpcUrl, иначе solana.rpcUrl.
archive.solana.rootKeyPath=/home/player/SHiNE/secrets/server-root.key
archive.solana.clientKeyPath=/home/player/SHiNE/secrets/server-client.key
# Эти файлы могут содержать Base58 seed 32 bytes или Base58 Solana secret key 64 bytes.
archive.solana.confirmPollSeconds=5
archive.solana.confirmTimeoutMinutes=30
archive.solana.commitment=finalized
# =============================================================
# Trusted archive import (independent from publisher)
# Empty = do not import archives from any external server.
# Comma-separated SHiNE server logins, case-insensitive.
# =============================================================
archive.import.allowedPublishers=
archive.import.intervalMinutes=60
archive.import.workDir=data/archive-import
+102 -35
View File
@@ -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.
+13
View File
@@ -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
View File
@@ -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.
+25 -263
View File
@@ -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.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -94,7 +94,7 @@ UserPdaRecordV1
| `40` | `AccessServersBlock` | Серверы доступа/relay. |
| `50` | `SessionsBlock` | Опубликованные пользовательские сессии и homeserver-ы. |
| `70` | `TrustedStateBlock` | Счетчик trusted-связей. |
| `100` | `ArchiveHeadBlock` | Текущая голова серверного SHINE-ARCHIVE: Arweave TX ID + SHA-256 архива. |
| `100` | `ArchiveHeadBlock` | Legacy/reserved. Новый per-block ANS-104 transport не использует это поле для пользовательской истории. |
| `255` | `ReservedBlock` | Зарезервировано, пока не используется. |
Правила:
@@ -374,7 +374,7 @@ ArchiveHeadBlock
Семантика:
- `archive_tx_id`raw 32-byte Arweave transaction id последнего опубликованного большого `SHINE-ARCHIVE`; текстовая Base64URL-форма получается вне PDA;
- `archive_tx_id`legacy/reserved поле старой archive-head схемы; новый per-block ANS-104 transport его не обновляет;
- `archive_hash` — SHA-256 большого archive block по правилам `docs/Archive/01_PROTOCOL_v1.0.md`;
- отсутствие block `100` означает, что аккаунт ещё не объявлял archive head;
- обычный legacy `update_user_pda`, в instruction которого archive extension отсутствует, **обязан сохранить существующий ArchiveHeadBlock без изменений**;
@@ -136,7 +136,7 @@
- upgrade-authority программы после проверки передается DAO;
- пользовательские операции `create_user_pda` и `update_user_pda` остаются доступными обычным пользователям при корректных подписях и оплате.
## ArchiveHeadBlock и серверный SHINE-ARCHIVE
## ArchiveHeadBlock (legacy/reserved)
Формат User PDA поддерживает необязательный `ArchiveHeadBlock` (`block_type = 100`, `block_version = 0`):
@@ -145,7 +145,7 @@ archive_tx_id [32]
archive_hash [32]
```
Он хранит текущую голову архива конкретного SHiNE-аккаунта: raw Arweave TX ID и SHA-256 соответствующего большого `SHINE-ARCHIVE`. Подробный бинарный формат и серверный workflow находятся в `docs/Archive/01_PROTOCOL_v1.0.md`.
Поле `ArchiveHeadBlock` осталось в Solana/PDA как legacy/reserved для совместимости формата PDA. Новый transport пользовательских блоков не использует server-level SHINE-ARCHIVE или archive head: каждый пользовательский block публикуется как ANS-104 DataItem внутри стандартных bundles.
Отдельной инструкции программы для архива нет. Используется существующий `update_user_pda`. Парсер update instruction обратно совместим:
+1 -2
View File
@@ -1,4 +1,4 @@
shine-server-blockchain — это библиотека, которая задаёт формат блока, правила парсинга/валидации тела, крипто-проверку (hash+Ed25519) и безопасную работу с файлами блокчейна (data/<name>.bch через временный .tmp_bch).
shine-server-blockchain задаёт Frame v1, ANS-104 DataItem, правила парсинга/валидации body и криптографическую проверку. Пользовательские chain-файлы на диске больше не используются; полные DataItems хранятся в PostgreSQL.
Как устроена структура и логика работы
@@ -33,7 +33,6 @@ BchCryptoVerifier отвечает за “как получить хэш и к
4) Утилиты вокруг имени и файлов
BlockchainNameUtil — извлекает login из blockchainName (отрезает 3 символа суффикса).
FileStoreUtil — безопасное файловое хранилище:
5) Объяснение структуры работы