Архивация в Arweave

This commit is contained in:
AidarKC
2026-09-12 10:17:23 +03:00
parent ef068eca81
commit c3253dceac
48 changed files with 6681 additions and 1227 deletions
-2
View File
@@ -14,8 +14,6 @@
| --- | --- | --- |
| `GetUser` | `01_User_Registration_API.md` | чтение/проверка пользователя + server-состояние его блокчейна |
| `SearchUsers` | `01_User_Registration_API.md` | поиск логинов по префиксу |
| `TestGetFreeAvatarQuota` | `14_Test_Free_Avatar_Upload_API.md` | временный тестовый просмотр остатка бесплатных загрузок аватара |
| `TestUploadFreeAvatar` | `14_Test_Free_Avatar_Upload_API.md` | временная тестовая бесплатная загрузка маленького аватара в Arweave |
| `ResolveLoginForAuth` | `02_Authentication_API.md` | проверка login перед входом: LOCAL / REMOTE / NOT_FOUND / NO_ACCESS_SERVER + URL правильного access server |
| `AuthChallenge` | `02_Authentication_API.md` | challenge для создания новой сессии |
| `CreateAuthSession` | `02_Authentication_API.md` | создание новой авторизованной сессии |
File diff suppressed because it is too large Load Diff
+98
View File
@@ -0,0 +1,98 @@
# Карта реализации 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`.
+415
View File
@@ -0,0 +1,415 @@
# Деплой SHiNE Archive Publisher v1.0 на тестовый сервер
Документ рассчитан на человека или автономного coding/deploy агента. Выполнять шаги по порядку. Не включать publisher до проверки Solana-программы и ключей.
## 0. Что именно меняется
Нужны изменения одновременно в:
1. серверном Java-коде;
2. PostgreSQL schema v22;
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 v22 сделать backup тестовой БД. Например:
```bash
pg_dump -Fc -d '<DATABASE_URL_OR_NAME>' -f shine-before-archive-v22.dump
```
Точная команда зависит от текущей схемы доступа PostgreSQL.
При старте сервер сам применит `migration_v22.sql`, если `db_schema_version < 22`. Вручную migration выполнять обычно не нужно.
После старта проверить:
```sql
SELECT * FROM db_schema_version WHERE id=1;
```
Ожидается:
```text
schema_version = 22
```
И наличие:
```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` действительно обновлена этой версией.
+86
View File
@@ -0,0 +1,86 @@
# Проверка и эксплуатация 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`.
@@ -0,0 +1,60 @@
# Состав patch-пакета и удаление legacy-файлов
ZIP `SHINE_archive_changed_files_with_docs.zip` предназначен для распаковки **поверх исходного дерева той версии сервера, из которой он был сделан**. Внутри находятся только новые и изменённые файлы, пути сохранены относительно корня репозитория.
## Важное ограничение ZIP-overlay
Распаковка ZIP может добавить/заменить файлы, но не удалит старые. Поэтому после распаковки нужно удалить legacy test-free-avatar исходники ниже. Они больше не зарегистрированы в `JsonHandlerRegistry`, однако физическое удаление сохраняет дерево в точном состоянии новой версии и не оставляет старый тестовый Arweave-код рядом с production archive publisher.
## Удалить после распаковки
```text
SHiNE-server/shine-server-db/src/main/java/shine/db/dao/TestFreeAvatarUploadsDAO.java
SHiNE-server/shine-server-db/src/main/java/shine/db/entities/TestFreeAvatarUploadEntry.java
SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/Net_TestGetFreeAvatarQuota_Handler.java
SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/Net_TestUploadFreeAvatar_Handler.java
SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/TestFreeAvatarArweaveService.java
SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_TestGetFreeAvatarQuota_Request.java
SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_TestGetFreeAvatarQuota_Response.java
SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_TestUploadFreeAvatar_Request.java
SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_TestUploadFreeAvatar_Response.java
docs/API/14_Test_Free_Avatar_Upload_API.md
```
Linux-команда из корня репозитория:
```bash
rm -f \
'SHiNE-server/shine-server-db/src/main/java/shine/db/dao/TestFreeAvatarUploadsDAO.java' \
'SHiNE-server/shine-server-db/src/main/java/shine/db/entities/TestFreeAvatarUploadEntry.java' \
'SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/Net_TestGetFreeAvatarQuota_Handler.java' \
'SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/Net_TestUploadFreeAvatar_Handler.java' \
'SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/TestFreeAvatarArweaveService.java' \
'SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_TestGetFreeAvatarQuota_Request.java' \
'SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_TestGetFreeAvatarQuota_Response.java' \
'SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_TestUploadFreeAvatar_Request.java' \
'SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_TestUploadFreeAvatar_Response.java' \
'docs/API/14_Test_Free_Avatar_Upload_API.md'
```
## Что пакет принципиально добавляет
- новый Gradle module `shine-server-archive`;
- migration/schema v22;
- archive state DAO/entities;
- server startup scheduler;
- Arweave chunk uploader;
- Solana User PDA writer;
- PDA block type `100` в Rust/Java/JS;
- обновлённую документацию формата User PDA;
- отдельную папку `docs/Archive/` с полным protocol/deploy/runbook.
## После overlay + удаления
Минимально проверить:
```bash
./gradlew shadowJar
```
И отдельно собрать/задеплоить изменённую Solana `shine_users` согласно `03_DEPLOY_TEST_SERVER.md`.
+73
View File
@@ -0,0 +1,73 @@
# Manifest changed/new files
Основа сравнения: последний исходный ZIP пользователя, на который рассчитан этот пакет.
- Изменённых файлов: 20
- Новых файлов до добавления этого manifest: 25
- Удаляемых legacy-файлов: 10
## Изменённые файлы
- `SHiNE-server/shine-server-config/src/main/java/utils/config/AppConfig.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-solana-users-sync/src/main/java/sync/codec/ShineUsersCodec.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`
- `build.gradle`
- `docs/API/09_Operations_Index.md`
- `docs/SHINE_ARCHIVE_PROTOCOL_v1.0_RU.md`
- `docs/SHINE_ARCHIVE_PROTOCOL_v1.0_RU_FINAL.md`
- `docs/Solana/user_pda/README.md`
- `docs/Solana_Architecture/details/shine_users.md`
- `settings.gradle`
- `shine-UI/js/services/auth-service.js`
- `shine-UI/js/services/shine-user-pda-service.js`
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md`
- `shine-solana/shine/doc/programs/shine_users.md`
- `shine-solana/shine/programs/shine_users/src/lib.rs`
## Новые файлы
- `SHiNE-server/shine-server-archive/build.gradle`
- `SHiNE-server/shine-server-archive/src/main/java/server/archive/ArchiveFileNames.java`
- `SHiNE-server/shine-server-archive/src/main/java/server/archive/ArchiveKeyLoader.java`
- `SHiNE-server/shine-server-archive/src/main/java/server/archive/ArchivePublisherConfig.java`
- `SHiNE-server/shine-server-archive/src/main/java/server/archive/ArchivePublisherScheduler.java`
- `SHiNE-server/shine-server-archive/src/main/java/server/archive/ArchivePublisherService.java`
- `SHiNE-server/shine-server-archive/src/main/java/server/archive/ArweaveArchiveService.java`
- `SHiNE-server/shine-server-archive/src/main/java/server/archive/ArweaveMerkle.java`
- `SHiNE-server/shine-server-archive/src/main/java/server/archive/ShineArchiveWriter.java`
- `SHiNE-server/shine-server-archive/src/main/java/server/archive/SolanaArchiveHeadWriter.java`
- `SHiNE-server/shine-server-archive/src/test/java/server/archive/ArchiveFileNamesTest.java`
- `SHiNE-server/shine-server-archive/src/test/java/server/archive/ShineArchiveWriterTest.java`
- `SHiNE-server/shine-server-db/src/main/java/shine/db/archive/ArchiveBigBlockRef.java`
- `SHiNE-server/shine-server-db/src/main/java/shine/db/archive/ArchiveChainCursor.java`
- `SHiNE-server/shine-server-db/src/main/java/shine/db/archive/ArchivePublishJob.java`
- `SHiNE-server/shine-server-db/src/main/java/shine/db/archive/ArchivePublishJobChain.java`
- `SHiNE-server/shine-server-db/src/main/java/shine/db/dao/ArchivePublicationDAO.java`
- `SHiNE-server/shine-server-db/src/main/resources/postgres/migration_v22.sql`
- `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/README.md`
- `docs/Archive/archive-publisher.example.properties`
## Legacy-файлы, которые ZIP сам не удаляет
- `SHiNE-server/shine-server-db/src/main/java/shine/db/dao/TestFreeAvatarUploadsDAO.java`
- `SHiNE-server/shine-server-db/src/main/java/shine/db/entities/TestFreeAvatarUploadEntry.java`
- `SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/Net_TestGetFreeAvatarQuota_Handler.java`
- `SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/Net_TestUploadFreeAvatar_Handler.java`
- `SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/TestFreeAvatarArweaveService.java`
- `SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_TestGetFreeAvatarQuota_Request.java`
- `SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_TestGetFreeAvatarQuota_Response.java`
- `SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_TestUploadFreeAvatar_Request.java`
- `SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_TestUploadFreeAvatar_Response.java`
- `docs/API/14_Test_Free_Avatar_Upload_API.md`
Подробная команда удаления находится в `05_PATCH_CONTENTS_AND_REMOVALS.md`.
+28
View File
@@ -0,0 +1,28 @@
# 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. `archive-publisher.example.properties` — минимальный конфиг архиватора.
## Коротко
- Архиватор **по умолчанию выключен**: `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` ещё не обновлена кодом из этого пакета.** Сначала обновить программу на нужном кластере, затем сервер.
@@ -0,0 +1,23 @@
# Минимальный пример для 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
File diff suppressed because it is too large Load Diff
+120 -54
View File
@@ -168,19 +168,78 @@ BigBlock 70
---
# 6. Периодичность
# 6. Расписание публикации
Частота архивной публикации конфигурируется отдельно от существующей межсерверной синхронизации.
Архивная публикация запускается один раз в сутки в заданное локальное время. Она НЕ использует интервал «каждые N минут».
Пример:
```properties
archive.publish.enabled=true
archive.publish.intervalMinutes=720
archive.publish.initialDelayMinutes=15
archive.publish.time=00:00
archive.publish.zoneId=
```
`720` минут = раз в 12 часов.
Для v1.0 значение по умолчанию:
```text
00:00
```
То есть новый snapshot и новый большой архивный блок создаются один раз в сутки в полночь.
Если `archive.publish.zoneId` пуст, используется системная timezone сервера. При необходимости её можно задать явно, например `Europe/Warsaw`. Это сохраняет публикацию ровно в указанное локальное время даже при переходах летнего/зимнего времени.
Незавершённый archive job после рестарта не ждёт следующей полуночи: сервер продолжает именно его сразу. Новый snapshot при старте вне назначенного времени не создаётся.
## 6.1. Первая архивная публикация
Если у данного archive publisher ещё нет подтверждённых архивных курсоров, первая публикация берёт ВСЁ локально известное состояние:
```text
для каждой blockchain_name:
source block 0 .. current local head
```
То есть первый большой архивный блок содержит все SHiNE-блоки, которые сервер успел узнать к моменту первого суточного snapshot. После успешной публикации следующие большие блоки содержат только дельту относительно подтверждённых курсоров.
## 6.2. Локальная папка и имена файлов
Перед любой сетевой загрузкой большой блок сначала полностью создаётся на локальном диске. По умолчанию каталог:
```text
data/archive/
```
До получения Arweave TX ID файл имеет временное имя:
```text
<login>.<00001>.<дд.мм.гг>.tmp.SHiNE-archive
```
Например:
```text
archive01.00001.11.09.26.tmp.SHiNE-archive
```
Дата — реальная дата создания/freeze snapshot большого блока в timezone archive publisher-а. Номер имеет минимальную ширину 5 цифр. Пять цифр — форматирование, а не лимит: блок `100000` получает шестизначный номер.
После успешной загрузки Arweave возвращает реальный TX ID. Сервер сначала надёжно сохраняет TX ID в БД, затем атомарно переименовывает тот же локальный файл в:
```text
<login>.<00001>.<дд.мм.гг>.<ARWEAVE_TX_ID>.SHiNE-archive
```
Например:
```text
archive01.00001.11.09.26.Xm32...kP9.SHiNE-archive
```
Поле `<ARWEAVE_TX_ID>` — не слово `trx`, а настоящий Base64URL TX ID загруженного объекта в Arweave. Финальный файл остаётся локально как постоянная копия.
Crash recovery обязан продолжать работу с этим же файлом. Если TX ID уже сохранён, но процесс упал до rename, при следующем запуске сервер вычисляет финальное имя из сохранённого TX ID и завершает переименование без повторной сборки дельты.
---
@@ -380,7 +439,7 @@ Offsets и sizes внутри `SHINE-ARCHIVE v1.0` используют `u32`.
archive.maxFileBytes=4000000000
```
Если данных больше, один scheduler-run формирует несколько последовательных больших блоков.
Если собранный frozen job превышает этот лимит, v1.0 останавливает публикацию с явной ошибкой `ArchiveTooLargeException` и не двигает курсоры. Практически лимит очень велик; для такого сервера следует уменьшить объём данных между суточными закрытиями или реализовать деление snapshot на несколько big blocks. Автоматическое деление одного snapshot на несколько big blocks оставлено как совместимое будущее расширение.
---
@@ -480,15 +539,17 @@ bytes[N] creator_login UTF-8
u32
```
Рекомендуемая нумерация:
Рекомендуемая нумерация опубликованных больших блоков:
```text
genesis = 0
next = 1
next = 2
first = 1
next = 2
next = 3
...
```
Первый реально публикуемый большой блок имеет номер `1`, поэтому его локальное имя содержит `00001`. Неудачная незавершённая попытка не становится частью опубликованной archive-цепочки.
---
# 20. `created_at_ms`
@@ -530,9 +591,9 @@ Writer v1.0 MUST использовать `FULL`.
BigBlock #365
References:
#0
#1
#2
#3
...
#364
```
@@ -595,7 +656,7 @@ u32
Индекс непосредственного родителя внутри reference table.
Для genesis:
Для первого большого блока (`#1`), у которого нет родителя:
```text
0xFFFFFFFF
@@ -1061,7 +1122,7 @@ last_chunk_size = new_chunk_size
# 54. Arweave service
Старый `TestFreeAvatarArweaveService` больше не нужен как avatar-specific сервис.
Старый `TestFreeAvatarArweaveService` удаляется из активного протокола; archive publisher использует отдельный `ArweaveArchiveService`.
Его следует переделать/переименовать, например в:
@@ -1307,8 +1368,9 @@ derivePublic(client_private) == UserPDA.client_key
```properties
archive.publish.enabled=false
archive.publish.intervalMinutes=720
archive.publish.initialDelayMinutes=15
archive.publish.time=00:00
archive.publish.zoneId=
archive.workDir=data/archive
archive.maxFileBytes=4000000000
@@ -1321,6 +1383,9 @@ archive.arweave.confirmTimeoutMinutes=180
archive.solana.rootKeyPath=/opt/shine/secrets/root.key
archive.solana.clientKeyPath=/opt/shine/secrets/client.key
archive.solana.commitment=finalized
# Отдельного archive.solana.rpcUrl нет.
# Используется solana.users.sync.rpcUrl, а если он пуст — обычный solana.rpcUrl.
```
---
@@ -1381,54 +1446,55 @@ Lock не удерживается во время Arweave/Solana ожидани
## Database
- [ ] `archive_chain_cursor`
- [ ] `archive_publish_job`
- [ ] `archive_publish_job_chain`
- [ ] schema migration
- [ ] crash recovery
- [x] `archive_chain_cursor`
- [x] `archive_publish_job`
- [x] `archive_publish_job_chain`
- [x] schema migration
- [x] crash recovery
## Archive writer
- [ ] magic `SHINE-ARCHIVE`
- [ ] major/minor version
- [ ] fixed header
- [ ] creator login
- [ ] FULL previous big block table
- [ ] one chunk per `blockchain_name`
- [ ] many raw records inside one chunk
- [ ] one backlink per chunk
- [ ] closer login
- [ ] SHA-256
- [ ] Ed25519 signature
- [ ] max file size < 4 GiB
- [x] magic `SHINE-ARCHIVE`
- [x] major/minor version
- [x] fixed header
- [x] creator login
- [x] FULL previous big block table
- [x] one chunk per `blockchain_name`
- [x] many raw records inside one chunk
- [x] one backlink per chunk
- [x] closer login
- [x] SHA-256
- [x] Ed25519 signature
- [x] max file size < 4 GiB
- [x] local `.tmp.SHiNE-archive -> .<ArweaveTX>.SHiNE-archive` lifecycle in `data/archive`
## Arweave
- [ ] rename/refactor `TestFreeAvatarArweaveService`
- [ ] remove avatar-specific logic
- [ ] large/chunked upload
- [ ] confirmation polling
- [x] rename/refactor `TestFreeAvatarArweaveService`
- [x] remove avatar-specific logic
- [x] large/chunked upload
- [x] confirmation polling
## Solana
- [ ] add PDA block type `100`
- [ ] update Rust codec
- [ ] update Java codec
- [ ] update JS codec/UI writer
- [ ] use ordinary `update_user_pda`
- [ ] server-side transaction writer
- [ ] root signature
- [ ] client fee payer
- [ ] wait for `finalized`
- [x] add PDA block type `100`
- [x] update Rust codec
- [x] update Java codec
- [x] update JS codec/UI writer
- [x] use ordinary `update_user_pda`
- [x] server-side transaction writer
- [x] root signature
- [x] client fee payer
- [x] wait for `finalized`
## Scheduler
- [ ] `archive.publish.enabled`
- [ ] `archive.publish.intervalMinutes`
- [ ] `archive.publish.initialDelayMinutes`
- [ ] no concurrent jobs
- [ ] split files > max size
- [ ] skip when no new blocks
- [x] `archive.publish.enabled`
- [x] `archive.publish.time`
- [x] `archive.publish.zoneId`
- [x] no concurrent jobs
- [ ] future: автоматическое split > max size (v1.0 сейчас безопасно останавливается без cursor commit)
- [x] skip when no new blocks
---
@@ -1506,10 +1572,10 @@ BigBlock #100
| creator = server-A
|
+-- References
| #0 -> BigBlock #0 / hash / TX
| #1 -> BigBlock #1 / hash / TX
| ref[0] -> BigBlock #1 / hash / TX
| ref[1] -> BigBlock #2 / hash / TX
| ...
| #99 -> BigBlock #99 / hash / TX
| ref[98] -> BigBlock #99 / hash / TX
|
+-- alice-001 chunk
| records x4
+27 -2
View File
@@ -94,16 +94,17 @@ UserPdaRecordV1
| `40` | `AccessServersBlock` | Серверы доступа/relay. |
| `50` | `SessionsBlock` | Опубликованные пользовательские сессии и homeserver-ы. |
| `70` | `TrustedStateBlock` | Счетчик trusted-связей. |
| `100` | `ArchiveHeadBlock` | Текущая голова серверного SHINE-ARCHIVE: Arweave TX ID + SHA-256 архива. |
| `255` | `ReservedBlock` | Зарезервировано, пока не используется. |
Правила:
- неизвестный `block_type` в `format_major = 1` считается ошибкой;
- обязательные блоки: `RecoveryKeyBlock`, `RootKeyBlock`, `ClientKeyBlock`, `BlockchainRegistryBlock`;
- необязательные блоки: `ServerProfileBlock`, `AccessServersBlock`, `SessionsBlock`, `TrustedStateBlock`;
- необязательные блоки: `ServerProfileBlock`, `AccessServersBlock`, `SessionsBlock`, `TrustedStateBlock`, `ArchiveHeadBlock`;
- каждый обязательный блок должен встречаться ровно один раз;
- порядок блоков в записи фиксируется для простоты проверки:
`RecoveryKey`, `RootKey`, `ClientKey`, `BlockchainRegistry`, `ServerProfile`, `AccessServers`, `Sessions`, `TrustedState`.
`RecoveryKey`, `RootKey`, `ClientKey`, `BlockchainRegistry`, `ServerProfile`, `AccessServers`, `Sessions`, `TrustedState`, `ArchiveHead`.
## 6. RecoveryKeyBlock
@@ -359,6 +360,28 @@ TrustedStateBlock
Пока блок с доверенными лицами не реализуется, потому что полный формат trusted-логики еще не составлен. В будущем trusted-связи, очереди, таймеры и подтверждения должны быть вынесены в отдельный формат.
## 15.1. ArchiveHeadBlock
Необязательный блок текущей головы серверного архива. Он используется archive-capable сервером и хранится в том же User PDA.
```text
ArchiveHeadBlock
- block_type: u8 = 100
- block_version: u8 = 0
- archive_tx_id: [u8; 32]
- archive_hash: [u8; 32]
```
Семантика:
- `archive_tx_id` — raw 32-byte Arweave transaction id последнего опубликованного большого `SHINE-ARCHIVE`; текстовая Base64URL-форма получается вне PDA;
- `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 без изменений**;
- расширенный `update_user_pda` может заменить archive head или явно очистить его; отдельной Solana instruction для архива нет.
`ArchiveHeadBlock` входит в unsigned bytes User PDA и тем самым покрывается обычной root-подписью записи.
## 16. Подпись user_pda
Подписывается не вся PDA целиком, а unsigned-часть записи:
@@ -392,6 +415,7 @@ Solana-программа проверяет подпись через встр
- обязательные блоки присутствуют;
- создается минимум один `BlockchainRecord`;
- новый `SessionsBlock` может присутствовать, но при обычной регистрации сейчас записывается пустой список с `sessions_mode = 1`;
- `ArchiveHeadBlock` при регистрации не обязателен; обычный пользователь/сервер может начать публиковать архив позже;
- стартовый `paid_limit_bytes` равен стартовому бонусу плюс оплаченный дополнительный лимит;
- `used_bytes <= paid_limit_bytes`;
- пользователь платит регистрационную комиссию;
@@ -408,6 +432,7 @@ Solana-программа проверяет подпись через встр
- `prev_record_hash` равен хэшу unsigned-части предыдущей записи;
- `updated_at_ms` обновляется;
- unsigned-часть новой записи подписана `root_key`;
- если archive extension в instruction отсутствует (legacy client), старый `ArchiveHeadBlock` сохраняется; если extension присутствует, применяется переданное `archive_head_update`;
- лимиты блокчейнов могут только увеличиваться;
- занятый размер и номер последнего блока не могут уменьшаться;
- при увеличении оплаченного лимита пользователь доплачивает комиссию;
@@ -135,3 +135,23 @@
- economy-настройки меняет DAO-authority;
- upgrade-authority программы после проверки передается DAO;
- пользовательские операции `create_user_pda` и `update_user_pda` остаются доступными обычным пользователям при корректных подписях и оплате.
## ArchiveHeadBlock и серверный SHINE-ARCHIVE
Формат User PDA поддерживает необязательный `ArchiveHeadBlock` (`block_type = 100`, `block_version = 0`):
```text
archive_tx_id [32]
archive_hash [32]
```
Он хранит текущую голову архива конкретного SHiNE-аккаунта: raw Arweave TX ID и SHA-256 соответствующего большого `SHINE-ARCHIVE`. Подробный бинарный формат и серверный workflow находятся в `docs/Archive/01_PROTOCOL_v1.0.md`.
Отдельной инструкции программы для архива нет. Используется существующий `update_user_pda`. Парсер update instruction обратно совместим:
- legacy payload без archive extension сохраняет старый block `100`;
- новый payload может заменить/очистить archive head;
- итоговая полная User PDA запись, включая block `100`, покрывается обычной root-подписью.
Это позволяет обычным старым клиентским обновлениям профиля не стирать archive head серверного publisher-а.