Добавить sync-профиль пользователя и обход Solana RPC
This commit is contained in:
parent
f3e4233285
commit
23edad416c
@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
Этот файл описывает технические WebSocket-запросы, которые нужны для служебной работы клиента с сервером. Часть операций доступна без авторизации, часть требует успешной авторизованной сессии.
|
Этот файл описывает технические WebSocket-запросы, которые нужны для служебной работы клиента с сервером. Часть операций доступна без авторизации, часть требует успешной авторизованной сессии.
|
||||||
|
|
||||||
Сейчас здесь семь методов:
|
Сейчас здесь восемь методов:
|
||||||
|
|
||||||
- `Ping` — keep-alive запрос для поддержания живого WebSocket-соединения;
|
- `Ping` — keep-alive запрос для поддержания живого WebSocket-соединения;
|
||||||
- `GetServerInfo` — запрос базовой публичной информации о сервере для выбора узла в децентрализованной сети;
|
- `GetServerInfo` — запрос базовой публичной информации о сервере для выбора узла в децентрализованной сети;
|
||||||
- `ListBlockchainHeads` — краткая сводка по всем локальным блокчейнам сервера для межсерверной синхронизации;
|
- `ListBlockchainHeads` — краткая сводка по всем локальным блокчейнам сервера для межсерверной синхронизации;
|
||||||
|
- `GetSyncUserProfile` — межсерверный профиль пользователя для создания локальной цепочки без Solana RPC;
|
||||||
- `GetCallIceConfig` — выдача STUN/TURN конфигурации для звонков;
|
- `GetCallIceConfig` — выдача STUN/TURN конфигурации для звонков;
|
||||||
- `ClientErrorLog` — отправка клиентской ошибки в серверный лог;
|
- `ClientErrorLog` — отправка клиентской ошибки в серверный лог;
|
||||||
- `ClientDebugLog` — отправка клиентского debug-события в серверный буфер;
|
- `ClientDebugLog` — отправка клиентского debug-события в серверный буфер;
|
||||||
@ -17,6 +18,7 @@
|
|||||||
- `Ping` нужен для регулярной проверки, что соединение всё ещё живо;
|
- `Ping` нужен для регулярной проверки, что соединение всё ещё живо;
|
||||||
- `GetServerInfo` нужен до авторизации и до работы с данными, чтобы клиент понял, что сервер доступен, и показал пользователю краткую карточку этого узла.
|
- `GetServerInfo` нужен до авторизации и до работы с данными, чтобы клиент понял, что сервер доступен, и показал пользователю краткую карточку этого узла.
|
||||||
- `ListBlockchainHeads` нужен для сервер-сервер сверки: партнёр получает список heads по всем цепочкам, сравнивает его со своим состоянием и затем добирает недостающие блоки по диапазону.
|
- `ListBlockchainHeads` нужен для сервер-сервер сверки: партнёр получает список heads по всем цепочкам, сравнивает его со своим состоянием и затем добирает недостающие блоки по диапазону.
|
||||||
|
- `GetSyncUserProfile` нужен для server-to-server режима, когда принимающий сервер хочет создать у себя локальные `solana_users + blockchain_state` без прямого обращения в Solana. Это используется как временный обход ограничений внешнего Solana RPC.
|
||||||
|
|
||||||
Ниже сначала описаны назначение методов, затем точные форматы запросов и ответов.
|
Ниже сначала описаны назначение методов, затем точные форматы запросов и ответов.
|
||||||
|
|
||||||
@ -197,7 +199,90 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. `GetCallIceConfig`
|
## 4. `GetSyncUserProfile`
|
||||||
|
|
||||||
|
### Назначение
|
||||||
|
|
||||||
|
Запрос минимального профиля пользователя для межсерверной синхронизации.
|
||||||
|
|
||||||
|
Нужен в сценарии, когда сервер во время periodic sync увидел чужой блокчейн, которого у него локально ещё нет. Вместо обращения в Solana PDA он может запросить у партнёра:
|
||||||
|
|
||||||
|
- `login`
|
||||||
|
- `blockchainName`
|
||||||
|
- `solanaKey`
|
||||||
|
- `blockchainKey`
|
||||||
|
- `clientKey`
|
||||||
|
- `blockchainSizeLimitBytes`
|
||||||
|
|
||||||
|
После этого принимающий сервер может локально создать записи в `solana_users` и `blockchain_state`, а затем уже докачивать блоки через `GetBlockchainBlock`.
|
||||||
|
|
||||||
|
Этот запрос доступен без авторизации и предназначен именно для server-to-server sync.
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "GetSyncUserProfile",
|
||||||
|
"requestId": "sync-user-001",
|
||||||
|
"payload": {
|
||||||
|
"login": "alice"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ: пользователь не найден
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "GetSyncUserProfile",
|
||||||
|
"requestId": "sync-user-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"exists": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ: пользователь найден
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "GetSyncUserProfile",
|
||||||
|
"requestId": "sync-user-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"exists": true,
|
||||||
|
"login": "alice",
|
||||||
|
"blockchainName": "alice-001",
|
||||||
|
"solanaKey": "BASE64_32",
|
||||||
|
"blockchainKey": "BASE64_32",
|
||||||
|
"clientKey": "BASE64_32",
|
||||||
|
"blockchainSizeLimitBytes": 100000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Поля ответа
|
||||||
|
|
||||||
|
- `exists` — найден ли пользователь на сервере-партнёре.
|
||||||
|
- `login` — канонический login из БД сервера-партнёра.
|
||||||
|
- `blockchainName` — имя основной цепочки пользователя.
|
||||||
|
- `solanaKey` — публичный ключ логина.
|
||||||
|
- `blockchainKey` — публичный ключ блокчейна.
|
||||||
|
- `clientKey` — публичный клиентский ключ, который в текущей модели используется при создании локальной записи.
|
||||||
|
- `blockchainSizeLimitBytes` — лимит размера файла блокчейна, который будет записан в локальный `blockchain_state`.
|
||||||
|
|
||||||
|
### Специфические коды ошибок `GetSyncUserProfile`
|
||||||
|
|
||||||
|
- `400 / BAD_FIELDS` — пустой или некорректный `login`.
|
||||||
|
- `404 / BLOCKCHAIN_STATE_NOT_FOUND` — пользователь найден, но на сервере-партнёре отсутствует `blockchain_state` для его цепочки.
|
||||||
|
- При непредвиденной ошибке сервер вернёт общую ошибку из раздела `00`, обычно `500 / INTERNAL_ERROR`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. `GetCallIceConfig`
|
||||||
|
|
||||||
Доступно только после успешной авторизации.
|
Доступно только после успешной авторизации.
|
||||||
|
|
||||||
@ -247,7 +332,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. `ClientErrorLog`
|
## 6. `ClientErrorLog`
|
||||||
|
|
||||||
### Запрос
|
### Запрос
|
||||||
|
|
||||||
@ -294,7 +379,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. `ClientDebugLog`
|
## 7. `ClientDebugLog`
|
||||||
|
|
||||||
### Запрос
|
### Запрос
|
||||||
|
|
||||||
@ -332,7 +417,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. `CallDeliveryReport`
|
## 8. `CallDeliveryReport`
|
||||||
|
|
||||||
### Запрос
|
### Запрос
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,7 @@
|
|||||||
| `Ping` | `05_Technical_Requests_API.md` | keep-alive |
|
| `Ping` | `05_Technical_Requests_API.md` | keep-alive |
|
||||||
| `GetServerInfo` | `05_Technical_Requests_API.md` | публичная информация о сервере |
|
| `GetServerInfo` | `05_Technical_Requests_API.md` | публичная информация о сервере |
|
||||||
| `ListBlockchainHeads` | `05_Technical_Requests_API.md` | список heads всех локальных блокчейнов |
|
| `ListBlockchainHeads` | `05_Technical_Requests_API.md` | список heads всех локальных блокчейнов |
|
||||||
|
| `GetSyncUserProfile` | `05_Technical_Requests_API.md` | межсерверный профиль пользователя для синхронизации |
|
||||||
| `GetCallIceConfig` | `05_Technical_Requests_API.md` | STUN/TURN конфигурация звонков |
|
| `GetCallIceConfig` | `05_Technical_Requests_API.md` | STUN/TURN конфигурация звонков |
|
||||||
| `ClientErrorLog` | `05_Technical_Requests_API.md` | логирование клиентской ошибки |
|
| `ClientErrorLog` | `05_Technical_Requests_API.md` | логирование клиентской ошибки |
|
||||||
| `ClientDebugLog` | `05_Technical_Requests_API.md` | клиентский debug-лог |
|
| `ClientDebugLog` | `05_Technical_Requests_API.md` | клиентский debug-лог |
|
||||||
|
|||||||
@ -39,33 +39,94 @@
|
|||||||
- Порядок блоков сохраняется (по глобальному номеру блока и хэшу).
|
- Порядок блоков сохраняется (по глобальному номеру блока и хэшу).
|
||||||
- Дедупликация по глобальному номеру блока и хэшу.
|
- Дедупликация по глобальному номеру блока и хэшу.
|
||||||
|
|
||||||
## 4. Протокол синхронизации (целевой, не реализован)
|
## 4. Текущая реализованная схема
|
||||||
|
|
||||||
### 4.1 Межсерверное соединение
|
На текущем этапе сервер уже умеет базовую межсерверную синхронизацию пользовательских блокчейнов.
|
||||||
|
|
||||||
|
### 4.1 Что уже сделано
|
||||||
|
|
||||||
|
1. При старте сервер читает свой `server.SHiNE.login`.
|
||||||
|
2. По этому логину он загружает из Solana свою server PDA.
|
||||||
|
3. Из неё вытаскивает список `sync_servers`.
|
||||||
|
4. Для каждого логина партнёра сервер читает его PDA и сохраняет локально:
|
||||||
|
- `login`
|
||||||
|
- `server_address`
|
||||||
|
5. После этого:
|
||||||
|
- новые локальные `AddBlock` рассылаются партнёрам в фоне;
|
||||||
|
- при старте запускается periodic sync;
|
||||||
|
- periodic sync повторяется каждые `12` часов после старта.
|
||||||
|
|
||||||
|
### 4.2 Какие server-to-server API уже используются
|
||||||
|
|
||||||
|
- `ListBlockchainHeads` — список heads всех локальных цепочек партнёра;
|
||||||
|
- `GetBlockchainBlock` — чтение одного конкретного блока партнёра;
|
||||||
|
- `GetSyncUserProfile` — минимальный профиль пользователя для локального создания `solana_users + blockchain_state` без обращения в Solana RPC.
|
||||||
|
|
||||||
|
### 4.3 Как сейчас работает periodic sync
|
||||||
|
|
||||||
|
Для каждого сервера из локальной таблицы `sync_servers`:
|
||||||
|
|
||||||
|
1. запрашивается `ListBlockchainHeads`;
|
||||||
|
2. для каждой удалённой цепочки сравниваются:
|
||||||
|
- `lastBlockNumber`
|
||||||
|
- `lastBlockHash`
|
||||||
|
- локальное состояние;
|
||||||
|
3. если локальная цепочка слабее, сервер по одному блоку вызывает `GetBlockchainBlock`;
|
||||||
|
4. каждый скачанный блок локально применяется через существующий `AddBlock`;
|
||||||
|
5. если у сервера ещё нет локальной записи пользователя/цепочки, перед этим подготавливается локальный `solana_users + blockchain_state`.
|
||||||
|
|
||||||
|
### 4.4 Зачем понадобился `GetSyncUserProfile`
|
||||||
|
|
||||||
|
Изначально подготовка локальной цепочки делалась через Solana:
|
||||||
|
|
||||||
|
- из `blockchainName` извлекался `login`;
|
||||||
|
- сервер вызывал import пользователя из Solana PDA;
|
||||||
|
- по данным PDA локально создавались `solana_users + blockchain_state`.
|
||||||
|
|
||||||
|
На практике это упёрлось в ограничение внешнего Solana RPC: при чистом старте и массовой подтяжке чужих цепочек сервер мог получать `HTTP 429`.
|
||||||
|
|
||||||
|
Поэтому добавлен отдельный обходной режим:
|
||||||
|
|
||||||
|
- настройка `sync.importUserProfileFromPartner.enabled=true`
|
||||||
|
- в этом режиме сервер **не ходит в Solana RPC** для создания локальной цепочки во время sync;
|
||||||
|
- вместо этого он запрашивает у сервера-партнёра `GetSyncUserProfile` и создаёт локальную запись по данным партнёра.
|
||||||
|
|
||||||
|
Это временная практическая заплатка, чтобы clean-start sync не зависел от rate limit внешнего Solana endpoint.
|
||||||
|
|
||||||
|
### 4.5 Что делает настройка `sync.importUserProfileFromPartner.enabled`
|
||||||
|
|
||||||
|
- `false` — стандартный режим, подготовка локального пользователя идёт через Solana PDA;
|
||||||
|
- `true` — sync-режим обхода Solana, локальный пользователь создаётся по server-to-server `GetSyncUserProfile`.
|
||||||
|
|
||||||
|
Настройка влияет именно на этап подготовки отсутствующей локальной цепочки во время periodic sync.
|
||||||
|
|
||||||
|
## 5. Целевой протокол следующего этапа
|
||||||
|
|
||||||
|
### 5.1 Межсерверное соединение
|
||||||
|
|
||||||
- Серверы устанавливают постоянное WebSocket-соединение друг с другом.
|
- Серверы устанавливают постоянное WebSocket-соединение друг с другом.
|
||||||
- Адрес партнёра определяется по `server_address` из его Solana PDA.
|
- Адрес партнёра определяется по `server_address` из его Solana PDA.
|
||||||
- Аутентификация: подпись Ed25519 корневым ключом сервера (`root_key` из PDA).
|
- Аутентификация: подпись Ed25519 корневым ключом сервера (`root_key` из PDA).
|
||||||
- При разрыве — переподключение с экспоненциальным backoff.
|
- При разрыве — переподключение с экспоненциальным backoff.
|
||||||
|
|
||||||
### 4.2 Доставка новых данных (push)
|
### 5.2 Доставка новых данных (push)
|
||||||
|
|
||||||
- При получении нового блока или DM сервер немедленно пушит его всем подключённым партнёрам.
|
- При получении нового блока или DM сервер немедленно пушит его всем подключённым партнёрам.
|
||||||
- Партнёр подтверждает приём (ACK). Без ACK — повтор с backoff.
|
- Партнёр подтверждает приём (ACK). Без ACK — повтор с backoff.
|
||||||
|
|
||||||
### 4.3 Начальная синхронизация (backfill)
|
### 5.3 Начальная синхронизация (backfill)
|
||||||
|
|
||||||
- При первом подключении к партнёру серверы обмениваются «курсорами» состояния:
|
- При первом подключении к партнёру серверы обмениваются «курсорами» состояния:
|
||||||
последний глобальный номер блока, последний известный DM-ключ.
|
последний глобальный номер блока, последний известный DM-ключ.
|
||||||
- Сервер с более полной историей досылает недостающее партнёру.
|
- Сервер с более полной историей досылает недостающее партнёру.
|
||||||
|
|
||||||
### 4.4 Разрешение конфликтов
|
### 5.4 Разрешение конфликтов
|
||||||
|
|
||||||
- Блоки пользовательского блокчейна: порядок определяется глобальным номером блока.
|
- Блоки пользовательского блокчейна: порядок определяется глобальным номером блока.
|
||||||
Конфликтующие ветки (fork) разрешаются по правилам `AddBlock` (см. `Dev_Docs/Blockchain/README.md`).
|
Конфликтующие ветки (fork) разрешаются по правилам `AddBlock` (см. `Dev_Docs/Blockchain/README.md`).
|
||||||
- DM: конфликтов нет, `message_key` уникален.
|
- DM: конфликтов нет, `message_key` уникален.
|
||||||
|
|
||||||
## 5. Маршрутизация DM между серверами
|
## 6. Маршрутизация DM между серверами
|
||||||
|
|
||||||
При отправке DM от пользователя A к пользователю B:
|
При отправке DM от пользователя A к пользователю B:
|
||||||
|
|
||||||
@ -78,23 +139,30 @@
|
|||||||
|
|
||||||
Кэш адресов серверов: обновляется раз в сессию (при ошибке соединения).
|
Кэш адресов серверов: обновляется раз в сессию (при ошибке соединения).
|
||||||
|
|
||||||
## 6. Безопасность
|
## 7. Безопасность
|
||||||
|
|
||||||
- Все блоки подписаны ключами пользователя на клиенте — сервер не может подделать содержимое.
|
- Все блоки подписаны ключами пользователя на клиенте — сервер не может подделать содержимое.
|
||||||
- Серверы не расшифровывают DM-контент (шифрование — задача следующего этапа).
|
- Серверы не расшифровывают DM-контент (шифрование — задача следующего этапа).
|
||||||
- При синхронизации каждый блок проходит валидацию подписи на принимающем сервере.
|
- При синхронизации каждый блок проходит валидацию подписи на принимающем сервере.
|
||||||
|
|
||||||
## 7. Статус реализации
|
## 8. Статус реализации
|
||||||
|
|
||||||
| Компонент | Статус |
|
| Компонент | Статус |
|
||||||
|-----------|--------|
|
|-----------|--------|
|
||||||
| Регистрация серверной PDA в Solana | ✅ Реализовано |
|
| Регистрация серверной PDA в Solana | ✅ Реализовано |
|
||||||
| Чтение `sync_servers` из PDA | ✅ Реализовано |
|
| Чтение `sync_servers` из PDA | ✅ Реализовано |
|
||||||
| Межсерверный WebSocket-канал | Нужна реализация |
|
| Локальная таблица `sync_servers` | ✅ Реализовано |
|
||||||
|
| Публичный `ListBlockchainHeads` | ✅ Реализовано |
|
||||||
|
| Публичный `GetBlockchainBlock` | ✅ Реализовано |
|
||||||
|
| Публичный `GetSyncUserProfile` | ✅ Реализовано |
|
||||||
|
| Плановый blockchain sync при старте + каждые 12 часов | ✅ Реализовано |
|
||||||
|
| Обход Solana RPC через `sync.importUserProfileFromPartner.enabled` | ✅ Реализовано |
|
||||||
|
| Межсерверный постоянный WebSocket-канал | Нужна реализация |
|
||||||
| Push новых DM партнёрам | Нужна реализация |
|
| Push новых DM партнёрам | Нужна реализация |
|
||||||
| Push блоков блокчейна партнёрам | ✅ Реализована базовая one-shot версия |
|
| Push блоков блокчейна партнёрам | ✅ Реализована базовая one-shot версия |
|
||||||
| Backfill при первом подключении | Нужна реализация |
|
| Periodic backfill отсутствующего хвоста | ✅ Реализовано |
|
||||||
|
| Разрешение рассинхрона / divergence | Нужна реализация |
|
||||||
| Маршрутизация DM через access_servers | Нужна реализация (заглушка) |
|
| Маршрутизация DM через access_servers | Нужна реализация (заглушка) |
|
||||||
|
|
||||||
Текущая версия сервера работает без межсерверной синхронизации.
|
Текущая версия сервера уже умеет базовую синхронизацию блокчейнов между партнёрами.
|
||||||
Синхронизация — задача следующего этапа разработки.
|
Не реализованы ещё DM-sync, постоянные server-to-server соединения и автоматическое исправление рассинхрона цепочек.
|
||||||
|
|||||||
@ -1,21 +1,28 @@
|
|||||||
# Периодическая межсерверная синхронизация блокчейнов
|
# Периодическая межсерверная синхронизация блокчейнов
|
||||||
|
|
||||||
- Краткое описание:
|
- Краткое описание:
|
||||||
|
- При старте сервер подтягивает `sync_servers` из server PDA и сохраняет адреса партнёров в локальную таблицу.
|
||||||
|
- После успешного локального `AddBlock` работает фоновая one-shot отправка нового блока партнёрам.
|
||||||
- Добавлен публичный `GetBlockchainBlock` для чтения одного блока.
|
- Добавлен публичный `GetBlockchainBlock` для чтения одного блока.
|
||||||
|
- Добавлен публичный `GetSyncUserProfile` для подготовки отсутствующей локальной цепочки без прямого Solana RPC.
|
||||||
- Добавлен плановый sync блокчейнов при старте сервера и затем каждые `12` часов.
|
- Добавлен плановый sync блокчейнов при старте сервера и затем каждые `12` часов.
|
||||||
- Синхронизация пока умеет только докачивать отсутствующий хвост цепочки.
|
- Синхронизация пока умеет только докачивать отсутствующий хвост цепочки.
|
||||||
|
- Добавлена настройка `sync.importUserProfileFromPartner.enabled`, которая включает создание локального `solana_users + blockchain_state` по ответу сервера-партнёра вместо Solana PDA.
|
||||||
- Случай рассинхрона цепочек пока не исправляется автоматически: он только логируется как не реализованный сценарий.
|
- Случай рассинхрона цепочек пока не исправляется автоматически: он только логируется как не реализованный сценарий.
|
||||||
|
|
||||||
- Что именно проверять:
|
- Что именно проверять:
|
||||||
- После старта сервера в логах появляется запуск периодического sync.
|
- После старта сервера в логах появляется запуск периодического sync.
|
||||||
- Сервер может запросить у партнёра `ListBlockchainHeads`.
|
- Сервер может запросить у партнёра `ListBlockchainHeads`.
|
||||||
|
- Сервер может запросить у партнёра `GetSyncUserProfile`, если включён `sync.importUserProfileFromPartner.enabled=true`.
|
||||||
- Сервер может запросить у партнёра `GetBlockchainBlock` и локально применить блок через существующий `AddBlock`.
|
- Сервер может запросить у партнёра `GetBlockchainBlock` и локально применить блок через существующий `AddBlock`.
|
||||||
- На чистом тестовом сервере после удаления БД и файлов блокчейнов сервер сам подтягивает блоки при старте.
|
- На чистом тестовом сервере после удаления БД и файлов блокчейнов сервер сам подтягивает блоки при старте.
|
||||||
|
- При включённом режиме обхода Solana сервер восстанавливает локальные цепочки без запросов в Solana RPC.
|
||||||
- После первичного старта новые блоки продолжают догоняться без ручного вмешательства.
|
- После первичного старта новые блоки продолжают догоняться без ручного вмешательства.
|
||||||
- При рассинхроне цепочек в логах появляется явное сообщение, что reconciliation пока не реализован.
|
- При рассинхроне цепочек в логах появляется явное сообщение, что reconciliation пока не реализован.
|
||||||
|
|
||||||
- Ожидаемый результат:
|
- Ожидаемый результат:
|
||||||
- Чистый сервер после старта сам восстанавливает локальные цепочки от партнёра синхронизации.
|
- Чистый сервер после старта сам восстанавливает локальные цепочки от партнёра синхронизации.
|
||||||
|
- В режиме обхода Solana чистый сервер не упирается в `Solana RPC 429` при создании локальных chain-state для уже существующих на партнёре пользователей.
|
||||||
- Периодический sync не мешает обычной работе сервера и не ломает локальный `AddBlock`.
|
- Периодический sync не мешает обычной работе сервера и не ломает локальный `AddBlock`.
|
||||||
- Нереализованный случай рассинхрона не приводит к падению сервера и явно отражается в логах.
|
- Нереализованный случай рассинхрона не приводит к падению сервера и явно отражается в логах.
|
||||||
|
|
||||||
|
|||||||
@ -106,6 +106,7 @@ import server.logic.ws_protocol.JSON.messages.entyties.Net_UpsertPushToken_Reque
|
|||||||
// --- NEW: Ping ---
|
// --- NEW: Ping ---
|
||||||
import server.logic.ws_protocol.JSON.handlers.system.Net_GetServerInfo_Handler;
|
import server.logic.ws_protocol.JSON.handlers.system.Net_GetServerInfo_Handler;
|
||||||
import server.logic.ws_protocol.JSON.handlers.system.Net_GetCallIceConfig_Handler;
|
import server.logic.ws_protocol.JSON.handlers.system.Net_GetCallIceConfig_Handler;
|
||||||
|
import server.logic.ws_protocol.JSON.handlers.system.Net_GetSyncUserProfile_Handler;
|
||||||
import server.logic.ws_protocol.JSON.handlers.system.Net_ClientErrorLog_Handler;
|
import server.logic.ws_protocol.JSON.handlers.system.Net_ClientErrorLog_Handler;
|
||||||
import server.logic.ws_protocol.JSON.handlers.system.Net_ClientDebugLog_Handler;
|
import server.logic.ws_protocol.JSON.handlers.system.Net_ClientDebugLog_Handler;
|
||||||
import server.logic.ws_protocol.JSON.handlers.system.Net_ListBlockchainHeads_Handler;
|
import server.logic.ws_protocol.JSON.handlers.system.Net_ListBlockchainHeads_Handler;
|
||||||
@ -116,6 +117,7 @@ import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_ClientErrorLog
|
|||||||
import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_ClientDebugLog_Request;
|
import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_ClientDebugLog_Request;
|
||||||
import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_GetCallIceConfig_Request;
|
import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_GetCallIceConfig_Request;
|
||||||
import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_GetServerInfo_Request;
|
import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_GetServerInfo_Request;
|
||||||
|
import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_GetSyncUserProfile_Request;
|
||||||
import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_ListBlockchainHeads_Request;
|
import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_ListBlockchainHeads_Request;
|
||||||
import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_Ping_Request;
|
import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_Ping_Request;
|
||||||
|
|
||||||
@ -199,6 +201,7 @@ public final class JsonHandlerRegistry {
|
|||||||
Map.entry("Ping", new Net_Ping_Handler()),
|
Map.entry("Ping", new Net_Ping_Handler()),
|
||||||
Map.entry("GetServerInfo", new Net_GetServerInfo_Handler()),
|
Map.entry("GetServerInfo", new Net_GetServerInfo_Handler()),
|
||||||
Map.entry("ListBlockchainHeads", new Net_ListBlockchainHeads_Handler()),
|
Map.entry("ListBlockchainHeads", new Net_ListBlockchainHeads_Handler()),
|
||||||
|
Map.entry("GetSyncUserProfile", new Net_GetSyncUserProfile_Handler()),
|
||||||
Map.entry("GetCallIceConfig", new Net_GetCallIceConfig_Handler()),
|
Map.entry("GetCallIceConfig", new Net_GetCallIceConfig_Handler()),
|
||||||
Map.entry("ClientErrorLog", new Net_ClientErrorLog_Handler()),
|
Map.entry("ClientErrorLog", new Net_ClientErrorLog_Handler()),
|
||||||
Map.entry("ClientDebugLog", new Net_ClientDebugLog_Handler()),
|
Map.entry("ClientDebugLog", new Net_ClientDebugLog_Handler()),
|
||||||
@ -276,6 +279,7 @@ public final class JsonHandlerRegistry {
|
|||||||
Map.entry("Ping", Net_Ping_Request.class),
|
Map.entry("Ping", Net_Ping_Request.class),
|
||||||
Map.entry("GetServerInfo", Net_GetServerInfo_Request.class),
|
Map.entry("GetServerInfo", Net_GetServerInfo_Request.class),
|
||||||
Map.entry("ListBlockchainHeads", Net_ListBlockchainHeads_Request.class),
|
Map.entry("ListBlockchainHeads", Net_ListBlockchainHeads_Request.class),
|
||||||
|
Map.entry("GetSyncUserProfile", Net_GetSyncUserProfile_Request.class),
|
||||||
Map.entry("GetCallIceConfig", Net_GetCallIceConfig_Request.class),
|
Map.entry("GetCallIceConfig", Net_GetCallIceConfig_Request.class),
|
||||||
Map.entry("ClientErrorLog", Net_ClientErrorLog_Request.class),
|
Map.entry("ClientErrorLog", Net_ClientErrorLog_Request.class),
|
||||||
Map.entry("ClientDebugLog", Net_ClientDebugLog_Request.class),
|
Map.entry("ClientDebugLog", Net_ClientDebugLog_Request.class),
|
||||||
|
|||||||
@ -0,0 +1,86 @@
|
|||||||
|
package server.logic.ws_protocol.JSON.handlers.system;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||||
|
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||||
|
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||||
|
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||||
|
import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_GetSyncUserProfile_Request;
|
||||||
|
import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_GetSyncUserProfile_Response;
|
||||||
|
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||||
|
import server.logic.ws_protocol.WireCodes;
|
||||||
|
import shine.db.dao.BlockchainStateDAO;
|
||||||
|
import shine.db.dao.SolanaUsersDAO;
|
||||||
|
import shine.db.entities.BlockchainStateEntry;
|
||||||
|
import shine.db.entities.SolanaUserEntry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetSyncUserProfile — server-to-server профиль пользователя для межсерверной синхронизации.
|
||||||
|
* Нужен, чтобы принимающий сервер мог создать локальные solana_users + blockchain_state
|
||||||
|
* без прямого запроса в Solana RPC.
|
||||||
|
*/
|
||||||
|
public final class Net_GetSyncUserProfile_Handler implements JsonMessageHandler {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(Net_GetSyncUserProfile_Handler.class);
|
||||||
|
private final SolanaUsersDAO usersDAO = SolanaUsersDAO.getInstance();
|
||||||
|
private final BlockchainStateDAO stateDAO = BlockchainStateDAO.getInstance();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) {
|
||||||
|
Net_GetSyncUserProfile_Request req = (Net_GetSyncUserProfile_Request) baseRequest;
|
||||||
|
|
||||||
|
String login = req.getLogin() == null ? "" : req.getLogin().trim();
|
||||||
|
if (login.isEmpty()) {
|
||||||
|
return NetExceptionResponseFactory.error(
|
||||||
|
req,
|
||||||
|
WireCodes.Status.BAD_REQUEST,
|
||||||
|
"BAD_FIELDS",
|
||||||
|
"Некорректные поля: login"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
SolanaUserEntry user = usersDAO.getByLogin(login);
|
||||||
|
|
||||||
|
Net_GetSyncUserProfile_Response resp = new Net_GetSyncUserProfile_Response();
|
||||||
|
resp.setOp(req.getOp());
|
||||||
|
resp.setRequestId(req.getRequestId());
|
||||||
|
resp.setStatus(WireCodes.Status.OK);
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
resp.setExists(false);
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockchainStateEntry state = stateDAO.getByBlockchainName(user.getBlockchainName());
|
||||||
|
if (state == null) {
|
||||||
|
log.warn("GetSyncUserProfile: blockchain_state not found for login={} blockchainName={}",
|
||||||
|
user.getLogin(), user.getBlockchainName());
|
||||||
|
return NetExceptionResponseFactory.error(
|
||||||
|
req,
|
||||||
|
WireCodes.Status.NOT_FOUND,
|
||||||
|
"BLOCKCHAIN_STATE_NOT_FOUND",
|
||||||
|
"Состояние блокчейна пользователя не найдено"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.setExists(true);
|
||||||
|
resp.setLogin(user.getLogin());
|
||||||
|
resp.setBlockchainName(user.getBlockchainName());
|
||||||
|
resp.setSolanaKey(user.getSolanaKey());
|
||||||
|
resp.setBlockchainKey(user.getBlockchainKey());
|
||||||
|
resp.setClientKey(user.getClientKey());
|
||||||
|
resp.setBlockchainSizeLimitBytes(state.getSizeLimit());
|
||||||
|
return resp;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("❌ Internal error GetSyncUserProfile login={}", login, e);
|
||||||
|
return NetExceptionResponseFactory.error(
|
||||||
|
req,
|
||||||
|
WireCodes.Status.INTERNAL_ERROR,
|
||||||
|
"INTERNAL_ERROR",
|
||||||
|
NetExceptionResponseFactory.detailedMessage("Внутренняя ошибка сервера при GetSyncUserProfile", e)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package server.logic.ws_protocol.JSON.handlers.system.entyties;
|
||||||
|
|
||||||
|
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запрос межсерверного профиля пользователя для синхронизации.
|
||||||
|
*/
|
||||||
|
public class Net_GetSyncUserProfile_Request extends Net_Request {
|
||||||
|
|
||||||
|
private String login;
|
||||||
|
|
||||||
|
public String getLogin() { return login; }
|
||||||
|
public void setLogin(String login) { this.login = login; }
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package server.logic.ws_protocol.JSON.handlers.system.entyties;
|
||||||
|
|
||||||
|
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ответ межсерверного профиля пользователя для синхронизации.
|
||||||
|
*/
|
||||||
|
public class Net_GetSyncUserProfile_Response extends Net_Response {
|
||||||
|
|
||||||
|
private Boolean exists;
|
||||||
|
private String login;
|
||||||
|
private String blockchainName;
|
||||||
|
private String solanaKey;
|
||||||
|
private String blockchainKey;
|
||||||
|
private String clientKey;
|
||||||
|
private Long blockchainSizeLimitBytes;
|
||||||
|
|
||||||
|
public Boolean getExists() { return exists; }
|
||||||
|
public void setExists(Boolean exists) { this.exists = exists; }
|
||||||
|
|
||||||
|
public String getLogin() { return login; }
|
||||||
|
public void setLogin(String login) { this.login = login; }
|
||||||
|
|
||||||
|
public String getBlockchainName() { return blockchainName; }
|
||||||
|
public void setBlockchainName(String blockchainName) { this.blockchainName = blockchainName; }
|
||||||
|
|
||||||
|
public String getSolanaKey() { return solanaKey; }
|
||||||
|
public void setSolanaKey(String solanaKey) { this.solanaKey = solanaKey; }
|
||||||
|
|
||||||
|
public String getBlockchainKey() { return blockchainKey; }
|
||||||
|
public void setBlockchainKey(String blockchainKey) { this.blockchainKey = blockchainKey; }
|
||||||
|
|
||||||
|
public String getClientKey() { return clientKey; }
|
||||||
|
public void setClientKey(String clientKey) { this.clientKey = clientKey; }
|
||||||
|
|
||||||
|
public Long getBlockchainSizeLimitBytes() { return blockchainSizeLimitBytes; }
|
||||||
|
public void setBlockchainSizeLimitBytes(Long blockchainSizeLimitBytes) { this.blockchainSizeLimitBytes = blockchainSizeLimitBytes; }
|
||||||
|
}
|
||||||
@ -61,6 +61,41 @@ public final class RemoteBlockchainSyncClient {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RemoteSyncUserProfile getSyncUserProfile(String serverAddressRaw, String login) throws Exception {
|
||||||
|
String safeLogin = MAPPER.writeValueAsString(login);
|
||||||
|
JsonNode response = send(serverAddressRaw, """
|
||||||
|
{
|
||||||
|
"op":"GetSyncUserProfile",
|
||||||
|
"requestId":%s,
|
||||||
|
"payload":{
|
||||||
|
"login":%s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".formatted("%s", safeLogin));
|
||||||
|
|
||||||
|
int status = response.path("status").asInt(500);
|
||||||
|
if (status == 404) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (status < 200 || status >= 300) {
|
||||||
|
throw new IllegalStateException("GetSyncUserProfile failed: status=" + status + " code=" + errorCode(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode payload = response.path("payload");
|
||||||
|
if (!payload.path("exists").asBoolean(false)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RemoteSyncUserProfile(
|
||||||
|
payload.path("login").asText(login),
|
||||||
|
payload.path("blockchainName").asText(""),
|
||||||
|
payload.path("solanaKey").asText(""),
|
||||||
|
payload.path("blockchainKey").asText(""),
|
||||||
|
payload.path("clientKey").asText(""),
|
||||||
|
payload.path("blockchainSizeLimitBytes").asLong(0L)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public RemoteBlockchainBlock getBlockchainBlock(String serverAddressRaw, String blockchainName, int blockNumber) throws Exception {
|
public RemoteBlockchainBlock getBlockchainBlock(String serverAddressRaw, String blockchainName, int blockNumber) throws Exception {
|
||||||
String safeBlockchainName = MAPPER.writeValueAsString(blockchainName);
|
String safeBlockchainName = MAPPER.writeValueAsString(blockchainName);
|
||||||
JsonNode response = send(serverAddressRaw, """
|
JsonNode response = send(serverAddressRaw, """
|
||||||
@ -176,6 +211,15 @@ public final class RemoteBlockchainSyncClient {
|
|||||||
String blockBytesB64
|
String blockBytesB64
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public record RemoteSyncUserProfile(
|
||||||
|
String login,
|
||||||
|
String blockchainName,
|
||||||
|
String solanaKey,
|
||||||
|
String blockchainKey,
|
||||||
|
String clientKey,
|
||||||
|
long blockchainSizeLimitBytes
|
||||||
|
) {}
|
||||||
|
|
||||||
private static final class SyncWsListener implements WebSocket.Listener {
|
private static final class SyncWsListener implements WebSocket.Listener {
|
||||||
private final CompletableFuture<String> responseFuture;
|
private final CompletableFuture<String> responseFuture;
|
||||||
private final CountDownLatch openLatch;
|
private final CountDownLatch openLatch;
|
||||||
|
|||||||
@ -9,9 +9,11 @@ import server.logic.ws_protocol.JSON.handlers.blockchain.Net_AddBlock_Handler;
|
|||||||
import server.logic.ws_protocol.JSON.handlers.blockchain.entyties.Net_AddBlock_Request;
|
import server.logic.ws_protocol.JSON.handlers.blockchain.entyties.Net_AddBlock_Request;
|
||||||
import shine.db.dao.BlockchainStateDAO;
|
import shine.db.dao.BlockchainStateDAO;
|
||||||
import shine.db.dao.SyncServersDAO;
|
import shine.db.dao.SyncServersDAO;
|
||||||
|
import shine.db.dao.UserCreateDAO;
|
||||||
import shine.db.entities.BlockchainStateEntry;
|
import shine.db.entities.BlockchainStateEntry;
|
||||||
import shine.db.entities.SyncServerEntry;
|
import shine.db.entities.SyncServerEntry;
|
||||||
import utils.blockchain.BlockchainNameUtil;
|
import utils.blockchain.BlockchainNameUtil;
|
||||||
|
import utils.config.AppConfig;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -44,6 +46,8 @@ public final class PeriodicBlockchainSyncService {
|
|||||||
private static final Net_AddBlock_Handler ADD_BLOCK_HANDLER = new Net_AddBlock_Handler();
|
private static final Net_AddBlock_Handler ADD_BLOCK_HANDLER = new Net_AddBlock_Handler();
|
||||||
private static final BlockchainStateDAO STATE_DAO = BlockchainStateDAO.getInstance();
|
private static final BlockchainStateDAO STATE_DAO = BlockchainStateDAO.getInstance();
|
||||||
private static final SyncServersDAO SYNC_SERVERS_DAO = SyncServersDAO.getInstance();
|
private static final SyncServersDAO SYNC_SERVERS_DAO = SyncServersDAO.getInstance();
|
||||||
|
private static final UserCreateDAO USER_CREATE_DAO = UserCreateDAO.getInstance();
|
||||||
|
private static final String CONFIG_IMPORT_PROFILE_FROM_PARTNER = "sync.importUserProfileFromPartner.enabled";
|
||||||
|
|
||||||
private PeriodicBlockchainSyncService() {}
|
private PeriodicBlockchainSyncService() {}
|
||||||
|
|
||||||
@ -140,7 +144,7 @@ public final class PeriodicBlockchainSyncService {
|
|||||||
String localHash
|
String localHash
|
||||||
) throws Exception {
|
) throws Exception {
|
||||||
String partnerLogin = normalize(partner.getLogin());
|
String partnerLogin = normalize(partner.getLogin());
|
||||||
if (!ensureLocalChainExists(remoteHead.blockchainName())) {
|
if (!ensureLocalChainExists(partner, remoteHead.blockchainName())) {
|
||||||
log.warn("Periodic blockchain sync: cannot prepare local chain. partner={} blockchainName={}",
|
log.warn("Periodic blockchain sync: cannot prepare local chain. partner={} blockchainName={}",
|
||||||
partnerLogin, remoteHead.blockchainName());
|
partnerLogin, remoteHead.blockchainName());
|
||||||
return;
|
return;
|
||||||
@ -198,7 +202,7 @@ public final class PeriodicBlockchainSyncService {
|
|||||||
return new LocalAddBlockApplyResult(false, error.getCode(), error.getMessage(), "");
|
return new LocalAddBlockApplyResult(false, error.getCode(), error.getMessage(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean ensureLocalChainExists(String blockchainName) {
|
private static boolean ensureLocalChainExists(SyncServerEntry partner, String blockchainName) {
|
||||||
try {
|
try {
|
||||||
if (STATE_DAO.getByBlockchainName(blockchainName) != null) {
|
if (STATE_DAO.getByBlockchainName(blockchainName) != null) {
|
||||||
return true;
|
return true;
|
||||||
@ -207,6 +211,9 @@ public final class PeriodicBlockchainSyncService {
|
|||||||
if (login == null || login.isBlank()) {
|
if (login == null || login.isBlank()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (AppConfig.getInstance().getBoolean(CONFIG_IMPORT_PROFILE_FROM_PARTNER, false)) {
|
||||||
|
return importUserProfileFromPartner(partner, login);
|
||||||
|
}
|
||||||
SolanaUserPdaImportService.findOrImportByLogin(login);
|
SolanaUserPdaImportService.findOrImportByLogin(login);
|
||||||
return STATE_DAO.getByBlockchainName(blockchainName) != null;
|
return STATE_DAO.getByBlockchainName(blockchainName) != null;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -216,6 +223,37 @@ public final class PeriodicBlockchainSyncService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean importUserProfileFromPartner(SyncServerEntry partner, String login) throws Exception {
|
||||||
|
if (partner == null || partner.getServerAddress() == null || partner.getServerAddress().isBlank()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteBlockchainSyncClient.RemoteSyncUserProfile profile =
|
||||||
|
REMOTE.getSyncUserProfile(partner.getServerAddress(), login);
|
||||||
|
if (profile == null) {
|
||||||
|
log.warn("Periodic blockchain sync: partner has no sync profile for login={} partner={}",
|
||||||
|
login, normalize(partner.getLogin()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
long sizeLimit = profile.blockchainSizeLimitBytes() > 0 ? profile.blockchainSizeLimitBytes() : 100_000L;
|
||||||
|
boolean inserted = USER_CREATE_DAO.insertUserWithBlockchain(
|
||||||
|
profile.login(),
|
||||||
|
profile.blockchainName(),
|
||||||
|
profile.solanaKey(),
|
||||||
|
profile.blockchainKey(),
|
||||||
|
profile.clientKey(),
|
||||||
|
sizeLimit,
|
||||||
|
now
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!inserted) {
|
||||||
|
return STATE_DAO.getByBlockchainName(profile.blockchainName()) != null;
|
||||||
|
}
|
||||||
|
return STATE_DAO.getByBlockchainName(profile.blockchainName()) != null;
|
||||||
|
}
|
||||||
|
|
||||||
private static String normalize(String value) {
|
private static String normalize(String value) {
|
||||||
if (value == null) return null;
|
if (value == null) return null;
|
||||||
String s = value.trim().toLowerCase(Locale.ROOT);
|
String s = value.trim().toLowerCase(Locale.ROOT);
|
||||||
|
|||||||
@ -2,6 +2,19 @@ server.1port=7070
|
|||||||
db.path=data/shine.sqlite
|
db.path=data/shine.sqlite
|
||||||
server.SHiNE.login=shineupme
|
server.SHiNE.login=shineupme
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# Межсерверная синхронизация: как создавать локальную запись пользователя,
|
||||||
|
# если во время sync пришла чужая цепочка, а у нас такого login ещё нет.
|
||||||
|
# false - брать профиль пользователя напрямую из Solana PDA (обычный режим).
|
||||||
|
# true - не ходить в Solana RPC, а запрашивать у сервера-партнёра специальный
|
||||||
|
# sync-профиль пользователя и по нему локально создавать
|
||||||
|
# solana_users + blockchain_state.
|
||||||
|
# Эта настройка нужна как временный обход лимитов Solana RPC (например 429),
|
||||||
|
# чтобы чистый сервер мог восстановить цепочки от партнёра без зависимости
|
||||||
|
# от внешнего Solana endpoint.
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
sync.importUserProfileFromPartner.enabled=false
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
# Server public info
|
# Server public info
|
||||||
# Эти поля используются JSON-операцией GetServerInfo.
|
# Эти поля используются JSON-операцией GetServerInfo.
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.272
|
client.version=1.2.273
|
||||||
server.version=1.2.252
|
server.version=1.2.253
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
PROD_HOST="${PROD_HOST:-player@shineup.me}"
|
|
||||||
TARGET_HOST="${TARGET_HOST:-player@193.8.215.70}"
|
TARGET_HOST="${TARGET_HOST:-player@193.8.215.70}"
|
||||||
TARGET_DOMAIN="${TARGET_DOMAIN:-t.shineup.me}"
|
TARGET_DOMAIN="${TARGET_DOMAIN:-t.shineup.me}"
|
||||||
REMOTE_BASE="${REMOTE_BASE:-/home/player/SHiNE}"
|
REMOTE_BASE="${REMOTE_BASE:-/home/player/SHiNE}"
|
||||||
@ -11,8 +10,6 @@ REMOTE_LOGS_DIR="${REMOTE_LOGS_DIR:-$REMOTE_SERVER_DIR/logs}"
|
|||||||
REMOTE_UI_DIR="${REMOTE_UI_DIR:-$REMOTE_BASE/shine-ui}"
|
REMOTE_UI_DIR="${REMOTE_UI_DIR:-$REMOTE_BASE/shine-ui}"
|
||||||
REMOTE_SERVICE_NAME="${REMOTE_SERVICE_NAME:-shine-server}"
|
REMOTE_SERVICE_NAME="${REMOTE_SERVICE_NAME:-shine-server}"
|
||||||
LOCAL_JAR="${LOCAL_JAR:-SHiNE-server/build/libs/shine-server.jar}"
|
LOCAL_JAR="${LOCAL_JAR:-SHiNE-server/build/libs/shine-server.jar}"
|
||||||
PROD_DATA_DIR="${PROD_DATA_DIR:-/home/player/SHiNE/shine-server/data}"
|
|
||||||
PROD_APP_PROPS="${PROD_APP_PROPS:-/home/player/SHiNE/shine-server/application.properties}"
|
|
||||||
|
|
||||||
TMP_DIR="$(mktemp -d)"
|
TMP_DIR="$(mktemp -d)"
|
||||||
cleanup() {
|
cleanup() {
|
||||||
@ -25,25 +22,10 @@ if [[ ! -f "$LOCAL_JAR" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ssh -o BatchMode=yes -o ConnectTimeout=20 "$PROD_HOST" "echo SSH OK" >/dev/null
|
|
||||||
ssh -o BatchMode=yes -o ConnectTimeout=20 "$TARGET_HOST" "echo SSH OK" >/dev/null
|
ssh -o BatchMode=yes -o ConnectTimeout=20 "$TARGET_HOST" "echo SSH OK" >/dev/null
|
||||||
ssh "$TARGET_HOST" "sudo -n true"
|
ssh "$TARGET_HOST" "sudo -n true"
|
||||||
ssh "$TARGET_HOST" "java -version >/dev/null 2>&1"
|
ssh "$TARGET_HOST" "java -version >/dev/null 2>&1"
|
||||||
|
|
||||||
mkdir -p "$TMP_DIR/data"
|
|
||||||
rsync -az --delete "$PROD_HOST:$PROD_DATA_DIR/" "$TMP_DIR/data/"
|
|
||||||
rsync -az "$PROD_HOST:$PROD_APP_PROPS" "$TMP_DIR/application.properties"
|
|
||||||
if grep -q '^server\.ui\.indexPath=' "$TMP_DIR/application.properties"; then
|
|
||||||
perl -0pi -e 's@^server\.ui\.indexPath=.*$@server.ui.indexPath=/home/player/SHiNE/shine-ui/index.html@m' "$TMP_DIR/application.properties"
|
|
||||||
else
|
|
||||||
printf '\nserver.ui.indexPath=/home/player/SHiNE/shine-ui/index.html\n' >>"$TMP_DIR/application.properties"
|
|
||||||
fi
|
|
||||||
if grep -q '^server\.SHiNE\.login=' "$TMP_DIR/application.properties"; then
|
|
||||||
perl -0pi -e 's@^server\.SHiNE\.login=.*$@server.SHiNE.login=tshineupme@m' "$TMP_DIR/application.properties"
|
|
||||||
else
|
|
||||||
printf '\nserver.SHiNE.login=tshineupme\n' >>"$TMP_DIR/application.properties"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat >"$TMP_DIR/shine-server.service" <<EOF
|
cat >"$TMP_DIR/shine-server.service" <<EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=SHiNE Server
|
Description=SHiNE Server
|
||||||
@ -68,9 +50,7 @@ TARGET_HOST="$TARGET_HOST" TARGET_DOMAIN="$TARGET_DOMAIN" REMOTE_UI_DIR="$REMOTE
|
|||||||
bash "$(dirname "$0")/scripts/install_test2_caddyfile.sh"
|
bash "$(dirname "$0")/scripts/install_test2_caddyfile.sh"
|
||||||
|
|
||||||
ssh "$TARGET_HOST" "mkdir -p '$REMOTE_SERVER_DIR' '$REMOTE_DATA_DIR' '$REMOTE_LOGS_DIR' '$REMOTE_UI_DIR'"
|
ssh "$TARGET_HOST" "mkdir -p '$REMOTE_SERVER_DIR' '$REMOTE_DATA_DIR' '$REMOTE_LOGS_DIR' '$REMOTE_UI_DIR'"
|
||||||
rsync -az --delete "$TMP_DIR/data/" "$TARGET_HOST:$REMOTE_DATA_DIR/"
|
|
||||||
rsync -az --timeout=120 "$LOCAL_JAR" "$TARGET_HOST:$REMOTE_SERVER_DIR/shine-server.jar"
|
rsync -az --timeout=120 "$LOCAL_JAR" "$TARGET_HOST:$REMOTE_SERVER_DIR/shine-server.jar"
|
||||||
rsync -az "$TMP_DIR/application.properties" "$TARGET_HOST:$REMOTE_SERVER_DIR/application.properties"
|
|
||||||
rsync -az "$TMP_DIR/shine-server.service" "$TARGET_HOST:/tmp/shine-server.service"
|
rsync -az "$TMP_DIR/shine-server.service" "$TARGET_HOST:/tmp/shine-server.service"
|
||||||
|
|
||||||
ssh "$TARGET_HOST" "set -euo pipefail; \
|
ssh "$TARGET_HOST" "set -euo pipefail; \
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user