SHA256
416 lines
14 KiB
Markdown
416 lines
14 KiB
Markdown
# Деплой 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` действительно обновлена этой версией.
|