Compare commits
No commits in common. "pixel-связи" and "master" have entirely different histories.
pixel-связ
...
master
54
.gitignore
vendored
54
.gitignore
vendored
@ -1,11 +1,4 @@
|
||||
## папки с данными создавайемыми при работе сервера
|
||||
data/
|
||||
logs/
|
||||
logs
|
||||
.understand-anything/
|
||||
|
||||
.gradle
|
||||
.gradle-home/
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
@ -47,49 +40,4 @@ bin/
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
|
||||
# временный debug token
|
||||
.debug-token
|
||||
|
||||
# Локальные артефакты и секреты Solana-модуля
|
||||
shine-solana/.git/
|
||||
shine-solana/.git-local-backup/
|
||||
shine-solana/.idea/
|
||||
shine-solana/shine/.idea/
|
||||
shine-solana/shine/.gradle/
|
||||
shine-solana/shine/.anchor/
|
||||
shine-solana/shine/.yarn/
|
||||
shine-solana/shine/.vendor/
|
||||
shine-solana/shine/node_modules/
|
||||
shine-solana/shine/target/
|
||||
shine-solana/shine/test-ledger/
|
||||
shine-solana/shine/old_vers/
|
||||
shine-solana/shine/program-keypair.json
|
||||
shine-solana/shine/keys/
|
||||
shine-solana/shine/validator.log
|
||||
shine-solana/shine/doc/КОШЕЛЬКИ_DEVNET_ТЕСТ.md
|
||||
shine-solana/shine/scripts/del/
|
||||
shine-solana/shine/scripts/**/keypairs/
|
||||
shine-solana/shine/scripts/**/runs/
|
||||
shine-solana/shine/scripts/**/*.env
|
||||
shine-solana/shine/scripts/**/TEMP_*.md
|
||||
|
||||
# Локальные артефакты и внешние материалы ESP32-подпроекта
|
||||
ESP32/**/.git/
|
||||
ESP32/**/.idea/
|
||||
ESP32/**/.arduino-build/
|
||||
ESP32/**/official-demo/
|
||||
ESP32/**/original-firmware/*.bin
|
||||
ESP32/**/original-firmware/*.bin.sha256
|
||||
ESP32/**/*.elf
|
||||
ESP32/**/*.map
|
||||
ESP32/**/*.merged.bin
|
||||
ESP32/**/*.uf2
|
||||
ESP32/**/*.o
|
||||
ESP32/**/*.d
|
||||
ESP32/**/*.a
|
||||
|
||||
# Полные серверные бэкапы (тяжёлые архивы, не коммитим)
|
||||
server-backup/archive/**
|
||||
!server-backup/archive/.gitkeep
|
||||
.DS_Store
|
||||
1
.idea/.name
generated
1
.idea/.name
generated
@ -1 +0,0 @@
|
||||
shine-server-server
|
||||
2
.idea/gradle.xml
generated
2
.idea/gradle.xml
generated
@ -13,8 +13,6 @@
|
||||
<option value="$PROJECT_DIR$/shine-server-config" />
|
||||
<option value="$PROJECT_DIR$/shine-server-crypto" />
|
||||
<option value="$PROJECT_DIR$/shine-server-db" />
|
||||
<option value="$PROJECT_DIR$/shine-server-geo" />
|
||||
<option value="$PROJECT_DIR$/shine-server-log" />
|
||||
<option value="$PROJECT_DIR$/shine-server-net-protocol" />
|
||||
<option value="$PROJECT_DIR$/shine-server-net-server" />
|
||||
</set>
|
||||
|
||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@ -1,8 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/tools/understand-anything-lab/upstream" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
137
AGENTS.md
137
AGENTS.md
@ -1,137 +0,0 @@
|
||||
# AGENTS
|
||||
|
||||
## Язык проекта
|
||||
- По умолчанию использовать русский язык во всех пользовательских текстах и технических пояснениях.
|
||||
- Пояснения к коммитам, PR и merge-запросам писать на русском языке.
|
||||
- Комментарии в коде, встроенные справки, документацию и инструкции писать по возможности на русском языке.
|
||||
|
||||
## Примечание
|
||||
- Если внешний инструмент/интеграция требует английский формат, допускается английский, но рядом желательно дать краткое пояснение на русском.
|
||||
|
||||
## Структура проекта (кратко)
|
||||
- Серверный код SHiNE находится в папке `SHiNE-server/`.
|
||||
- Код клиентского UI SHiNE находится в папке `shine-UI/`.
|
||||
- Веб-панель администратора сервера (управление Solana PDA сервера) находится в `shine-UI/`:
|
||||
- точка входа `shine-UI/server-ui.html`;
|
||||
- остальные файлы серверного UI — в `shine-UI/server-ui/`.
|
||||
- Локальный Telegram-бот агента-кодера находится в папке `SHiNE-agent-bot-coder/` и не является кодом основного серверного приложения.
|
||||
- Solana/Anchor-модуль находится в папке `shine-solana/shine/` и ведётся отдельно от основного server/UI деплоя.
|
||||
|
||||
## Сервис агента-кодера
|
||||
- В проекте есть локальный Telegram-бот-сервис агента-кодера в папке `SHiNE-agent-bot-coder/`.
|
||||
- Сервис принимает сообщения из Telegram, ведёт историю диалога, ставит задачи в очередь и вызывает Codex CLI для обработки запросов по проекту.
|
||||
- Автоматически читаемые инструкции для Codex внутри сервиса держать в `SHiNE-agent-bot-coder/AGENTS.md`.
|
||||
- Подробные служебные правила Telegram-обработчика, его очередь, история, systemd-запуск и особенности ответов описывать в `SHiNE-agent-bot-coder/AGENT.md`.
|
||||
- Если в сообщениях пользователя встречается «агент MD» или похожая формулировка про файл инструкций Codex, считать, что имеется в виду автоматически читаемый `AGENTS.md`.
|
||||
|
||||
## Solana-модуль
|
||||
- В проекте есть отдельный Solana/Anchor-модуль в папке `shine-solana/shine/`.
|
||||
- Модуль логически связан с SHiNE, но не должен автоматически подключаться к сборке или деплою основного сервера без отдельного решения.
|
||||
- В Solana-модуле действуют локальные инструкции `shine-solana/shine/AGENTS.md`; при изменениях внутри модуля сначала читать их.
|
||||
- В git добавлять исходники, lock-файлы, настройки проекта и документацию Solana-модуля, но не добавлять локальные ключи, `.git`, `.idea`, `.gradle`, `target`, `node_modules`, `test-ledger`, логи, временные run-отчёты и `.env`-конфиги.
|
||||
- Для Solana deploy/push использовать правила из локального `shine-solana/shine/AGENTS.md`; не смешивать deploy Solana-модуля с `deployServer`/`deployUI` основного проекта.
|
||||
- Для регистрации пользователей в Solana (программа `shine_users`) единая актуальная инструкция по деплою/инициализации, адресам программ, и куда их прописывать в UI/сервере находится в:
|
||||
- `Dev_Docs/Инициализация_Solana_регистрации/README.md`
|
||||
- Этот файл считать основной справкой (single source of truth) по деплою и первичной инициализации Solana-регистрации в текущем проекте.
|
||||
- Актуальная архитектурная справка по устройству Solana-программ, PDA-счетам, ролям DAO и движению средств находится в:
|
||||
- `Dev_Docs/Solana_Architecture/README.md`
|
||||
- Документ формата пользовательской PDA-записи `shine_users` находится в:
|
||||
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md`
|
||||
|
||||
## Документация блокчейна
|
||||
- Актуальная документация по форматам блокчейна находится в `Dev_Docs/Blockchain/README.md`.
|
||||
- Это точка входа (оглавление), рядом расположены детальные файлы по форматам, типам каналов и командным сообщениям.
|
||||
- При любом изменении кода, связанного с блокчейном (формат блока, типы каналов, правила чтения/записи, команды), обязательно обновлять соответствующие документы в `Dev_Docs/Blockchain/`.
|
||||
- Дополнительно обязательно вести `Dev_Docs/Blockchain/CHANGELOG.md`: дописывать изменения построчно с указанием даты/времени и хэша коммита, после которого внесено изменение.
|
||||
- Перед любым изменением формата блокчейна обязательно заранее предупреждать пользователя, что формат будет изменён.
|
||||
- Изменять формат блокчейна можно только после явного подтверждения пользователя (без подтверждения формат не менять).
|
||||
- Добавление любых данных в блокчейн выполнять только через операцию `AddBlock`.
|
||||
- Перед каждым `AddBlock` обязательно проверять/актуализировать текущее состояние вершины блокчейна (`last global number/hash`) и использовать его при формировании блока.
|
||||
|
||||
## Документация личных сообщений (DM)
|
||||
- Актуальная документация по логике личных сообщений находится в `Dev_Docs/Personal_Messages/README.md`.
|
||||
- При любом изменении кода, связанного с личными сообщениями (формат подписанного DM-блока, типы DM-сообщений, правила доставки/ACK/read-receipt, роутинг по сессиям, UI-логика чатов), обязательно обновлять `Dev_Docs/Personal_Messages/README.md`.
|
||||
- Логика личных сообщений в коде должна всегда соответствовать `Dev_Docs/Personal_Messages/README.md`.
|
||||
- Документ по личным сообщениям обязан поддерживаться в актуальном состоянии.
|
||||
|
||||
## Документация API сервера
|
||||
- Актуальная документация по публичному JSON/WebSocket API сервера находится в `Dev_Docs/API/`.
|
||||
- При любом изменении серверного API/эндпоинтов/операций `op` обязательно обновлять соответствующие документы в `Dev_Docs/API/`.
|
||||
- Перед изменением самого серверного API обязательно явно предупредить пользователя, какие операции, поля запросов/ответов или коды ошибок будут изменены, и запросить отдельное подтверждение.
|
||||
- Без явного подтверждения пользователя формат серверного API не менять; допускается только приведение документации в соответствие уже существующему коду.
|
||||
- Если добавляется новая операция `op`, нужно обновить общий список операций в `Dev_Docs/API/09_Operations_Index.md` или создать его, если файла ещё нет.
|
||||
|
||||
## Известная проблема (временная пометка)
|
||||
- Мнения по связям пользователя (`known_person`, `shine_confirmed`, `shine_seen`) в UI могут отображаться нестабильно.
|
||||
- Требуется отдельная доработка и финальная проверка end-to-end: запись мнения в блокчейн → обновление `connections_state` → ответ `GetUserConnectionsGraph` → отображение в UI.
|
||||
- До фикса считать эту часть функционала незавершённой и обязательно перепроверять вручную после каждого деплоя.
|
||||
|
||||
## Версионирование
|
||||
- Единый файл версий проекта: `VERSION.properties` (в корне репозитория).
|
||||
- Перед каждым новым коммитом обязательно увеличивать версии в `VERSION.properties`:
|
||||
- `client.version` — версия клиентского UI.
|
||||
- `server.version` — версия серверной части.
|
||||
- Базовое правило инкремента: `+1` по последнему числовому сегменту (patch), если не оговорено иное.
|
||||
- Обычные коммиты делать стандартным `git commit`; переменная `$GITEA_TOKEN` для коммитов не нужна и не используется.
|
||||
|
||||
## Deploy
|
||||
- Все документы и заметки по деплою хранить в папке `Dev_Docs/deploy/`.
|
||||
- Базовый целевой хост для деплоя по умолчанию: `player@93.170.12.154` (`shineup.me`).
|
||||
- Базовый путь на сервере для SHiNE: `/home/player` (проекты SHiNE размещать в `/home/player/SHiNE/...`).
|
||||
- По возможности все справки, комментарии и примечания в конфигах/документах писать на русском языке.
|
||||
- Для операций `git push` при необходимости использовать токен из переменной окружения `$GITEA_TOKEN`.
|
||||
- Для серверного деплоя использовать один gradle task: `./gradlew deployServer`.
|
||||
- Для UI деплоя использовать один gradle task: `./gradlew deployUI`.
|
||||
- Для локального запуска использовать `./gradlew startLocal` (или `startLocalWithBuild`).
|
||||
- Сначала предлагать локальную проверку, а деплой на сервер выполнять по запросу пользователя.
|
||||
|
||||
## Логи звонков (установка соединения)
|
||||
- Специальный поток диагностики установки звонков идёт через `CallDeliveryReport` (клиент → сервер).
|
||||
- На проде специальный файл для звонков:
|
||||
- `/home/player/SHiNE/shine-server/logs/call-delivery-events.log`
|
||||
- Общий серверный лог (и ротации) на проде:
|
||||
- `/home/player/SHiNE/shine-server/logs/app.log`
|
||||
- `/home/player/SHiNE/shine-server/logs/app.YYYY-MM-DD.log`
|
||||
- Для анализа причин недозвона в первую очередь фильтровать записи по ключам:
|
||||
- `CallDeliveryReport`
|
||||
- `call_connected`
|
||||
- `outgoing_failed`
|
||||
- `incoming_failed`
|
||||
- `call_busy`
|
||||
- `call_declined`
|
||||
- `unknown_error`
|
||||
- В этих записях искать поля `reason`, `failureStage`, `pcConnectionState`, `pcIceConnectionState`, `routeLabel`, `configuredTurnHosts*`, `reachableTurnHosts*`.
|
||||
|
||||
## Недопроверенные фичи (обязательно)
|
||||
- Папка для учёта недопроверенных фич: `Dev_Docs/Pending_Features/`.
|
||||
- По каждой новой доработке, которая требует ручной проверки, добавлять отдельный markdown-файл в `Dev_Docs/Pending_Features/`.
|
||||
- Рекомендуемый формат имени файла: `YYYY-MM-DD_HHMM_<short-feature-name>.md`.
|
||||
- Имена новых файлов и краткие описания фич по возможности писать на русском языке.
|
||||
- Внутри файла обязательно указывать:
|
||||
- краткое описание фичи;
|
||||
- что именно проверять;
|
||||
- ожидаемый результат;
|
||||
- статус (например: `pending`, `in_progress`, `done`).
|
||||
- После подтверждения, что фича проверена и работает корректно, соответствующий файл удалять.
|
||||
- В `Dev_Docs/Pending_Features/README.md` вести краткий регламент и поддерживать актуальность.
|
||||
|
||||
## Будущие фичи
|
||||
- Папка для задач, сознательно отложенных на будущее: `Dev_Docs/Future_Features/`.
|
||||
- Точка входа по планам: `Dev_Docs/Future_Features/README.md`.
|
||||
- Внутри планы разделены по горизонтам: `near/`, `medium/`, `far/`.
|
||||
- Если пользователь спрашивает, какие есть планы или что можно продолжить, сначала читать `Dev_Docs/Future_Features/README.md`, затем при необходимости конкретные файлы из горизонтов.
|
||||
- Файлы из этой папки не считать активными задачами и не начинать реализацию без явной просьбы пользователя.
|
||||
- Если часть кода временно отключена или закомментирована, в файле будущей фичи подробно описывать:
|
||||
- какие файлы и участки отключены;
|
||||
- что осталось в коде как заготовка;
|
||||
- какие документы нужно обновить при возврате;
|
||||
- с какого сценария продолжать разработку.
|
||||
|
||||
## Коммуникация по новым задачам (обязательно)
|
||||
- При получении нового задания сначала кратко пересказать задачу своими словами.
|
||||
- До начала реализации задать недостающие уточняющие вопросы (если они есть).
|
||||
- Если есть уместные идеи/улучшения — кратко предложить их; если полезных идей нет, ничего дополнительно не предлагать.
|
||||
- Добавлять краткую оценку фичи (насколько это полезно/удачно по мнению исполнителя).
|
||||
- После этого обязательно запросить подтверждение от пользователя, что задача понята верно, и только после подтверждения переходить к реализации.
|
||||
- Если вопросов нет, явно написать в формате: «Я всё понял, начинаю делать?» и ждать подтверждения.
|
||||
- Без подтверждения пользователя реализацию не начинать.
|
||||
@ -1,18 +0,0 @@
|
||||
# Runbook для агента: тест сетевого соединения
|
||||
|
||||
## Быстрый цикл
|
||||
1. Убедись, что сервер запущен.
|
||||
2. Скажи пользователю: «Запусти двух клиентов и напиши “продолжай”».
|
||||
3. По команде «продолжай»:
|
||||
- вызови `GET /debug/ws/clients`,
|
||||
- выбери 2 активные сессии (предпочтительно разных логинов),
|
||||
- вызови `POST /debug/ws/connect`,
|
||||
- получи `runId`.
|
||||
4. Читай `GET /debug/ws/logs?limit=200&runId=<runId>` и сообщай прогресс.
|
||||
5. Если неуспех — покажи ошибки, предложи перезапуск 2 клиентов и повтори.
|
||||
|
||||
## Формат взаимодействия с пользователем
|
||||
- Старт: «Сервер готов. Запусти двух клиентов и скажи “продолжай”.»
|
||||
- После старта run: «Тест запущен, runId=..., проверяю логи.»
|
||||
- Успех: «Соединение установлено, вижу connected.»
|
||||
- Неуспех: «Соединение не поднялось, причины: ... Предлагаю перезапустить клиентов и повторить.»
|
||||
11
CLAUDE.md
11
CLAUDE.md
@ -1,11 +0,0 @@
|
||||
@AGENTS.md
|
||||
@AGENT_DEBUG_RUNBOOK.md
|
||||
|
||||
## Обязательно читать при работе с UI
|
||||
@shine-UI/AGENTS.md
|
||||
|
||||
## Справка по подпроектам
|
||||
- При работе внутри `SHiNE-agent-bot-coder/` — читать `SHiNE-agent-bot-coder/AGENTS.md` и `SHiNE-agent-bot-coder/AGENT.md`.
|
||||
- При работе внутри `shine-solana/shine/` — читать `shine-solana/shine/AGENTS.md`.
|
||||
- При работе внутри `shine-UI/server-ui/` — читать `shine-UI/AGENTS.md`.
|
||||
- При работе внутри `SHiNE-server/` — читать `SHiNE-server/AGENTS.md`.
|
||||
@ -1,255 +0,0 @@
|
||||
# DAO_запуск
|
||||
|
||||
Рабочий документ по тому, что ещё нужно сделать для первого запуска DAO-сценария SHiNE.
|
||||
|
||||
Логика документа:
|
||||
|
||||
- `этап1` — то, без чего нельзя считать сценарий первого запуска собранным даже в тестовом виде;
|
||||
- `этап2` — то, что полезно и, вероятно, потребуется дальше, но это можно делать после старта `этап1` или параллельно без блокировки первого результата.
|
||||
|
||||
Базовая среда первого прохода:
|
||||
|
||||
- сеть `Solana devnet`;
|
||||
- модель синхронизации: `server-to-server`;
|
||||
- `Solana + Arweave` используются как якорь и архив;
|
||||
- DAO понимается как стандартный governance/smart-contract контур, который управляет отдельными программами SHiNE, приносящими деньги.
|
||||
|
||||
## Краткий вывод
|
||||
|
||||
Для первого запуска DAO в тестовом виде текущего списка в целом хватает, но только если понимать запуск как:
|
||||
|
||||
- можно развернуть и проверить базовый DAO-контур;
|
||||
- можно зарегистрировать пользователей и ключевые сущности;
|
||||
- можно провести тестовую покупку билета через smart contract;
|
||||
- можно завести тестовый денежный поток в программы, управляемые DAO;
|
||||
- можно проверить опорную межсерверную синхронизацию и фиксацию состояния в архивный слой.
|
||||
|
||||
Если же под "запуском" понимать уже полностью устойчивую production-схему с ротацией ключей, восстановлением любого сервера из архива, железными устройствами подписи и полным циклом администрирования, то текущий список нужно будет ещё расширять.
|
||||
|
||||
## Этап1
|
||||
|
||||
Цель этапа: собрать минимально жизнеспособный DAO-сценарий в `devnet`, который можно пройти руками от регистрации до базовой экономики и проверки архитектуры.
|
||||
|
||||
### 1. Переписать и стабилизировать регистрацию пользователей без Anchor
|
||||
|
||||
Что сделать:
|
||||
|
||||
- довести `shine_users` в чистом Rust/Solana SDK до рабочего и проверенного состояния;
|
||||
- убедиться, что `shine_login_guard` и связанный сценарий регистрации совместимы с новым ABI;
|
||||
- проверить создание и чтение `user_pda`;
|
||||
- проверить update пользовательской записи и связанные экономические параметры;
|
||||
- синхронизировать сервер, UI и lazy-import с новым форматом и seed'ами.
|
||||
|
||||
Почему это в `этап1`:
|
||||
|
||||
- без стабильной пользовательской регистрации дальше нельзя строить ни DAO-сценарий, ни привязку устройств, ни платёжные сценарии.
|
||||
|
||||
### 2. Проверить полный сценарий регистрации и базовой Solana-интеграции
|
||||
|
||||
Что сделать:
|
||||
|
||||
- руками прогнать регистрацию нового пользователя;
|
||||
- руками прогнать создание и update server PDA там, где это требуется текущему сценарию;
|
||||
- убедиться, что сервер читает новые PDA без anchor-зависимостей и без старых discriminator'ов;
|
||||
- зафиксировать, какие именно части сценария уже подтверждены руками, а какие ещё нет.
|
||||
|
||||
Почему это в `этап1`:
|
||||
|
||||
- сейчас в проекте уже есть признаки перехода на pure Rust, но без ручной проверки это нельзя считать завершённым.
|
||||
|
||||
### 3. Создать стандартный DAO smart contract / governance-контур
|
||||
|
||||
Что сделать:
|
||||
|
||||
- определить и реализовать стандартный DAO-контур, который будет управлять программами SHiNE;
|
||||
- зафиксировать, какие права сразу передаются DAO, а какие временно остаются на отдельных ключах;
|
||||
- подготовить тестовую DAO-структуру в `devnet`.
|
||||
|
||||
Минимум для первого запуска:
|
||||
|
||||
- DAO существует как управляемая сущность;
|
||||
- DAO может владеть или контролировать ключевые права управления денежными программами;
|
||||
- есть понятный путь, как DAO влияет на доходные программы SHiNE.
|
||||
|
||||
Почему это в `этап1`:
|
||||
|
||||
- без этого "DAO-запуск" будет только запуском отдельных Solana-программ, но не запуском управляемой DAO-системы.
|
||||
|
||||
### 4. Доработать смарт-контракт выплат с третьей очередью
|
||||
|
||||
Что сделать:
|
||||
|
||||
- добавить в `shine_payments` третью очередь, о которой уже принято решение;
|
||||
- проверить совместимость с текущей моделью тикетов, выплат и DAO-управления;
|
||||
- убедиться, что логика очередей соответствует ожидаемой экономике проекта.
|
||||
|
||||
Почему это в `этап1`:
|
||||
|
||||
- по текущей постановке это нужно именно для сценария регистрации DAO и дальнейшей экономики.
|
||||
|
||||
### 5. Сделать UI для покупки билетов и просмотра очереди
|
||||
|
||||
Что сделать:
|
||||
|
||||
- добавить UI-сценарий покупки билетов через smart contract;
|
||||
- показать пользователю, сколько перед ним человек в очереди;
|
||||
- убедиться, что UI отражает актуальное состояние контрактной логики, а не локальные предположения.
|
||||
|
||||
Почему это в `этап1`:
|
||||
|
||||
- покупка билетов у тебя обозначена как часть DAO-сценария, а не как побочная функция;
|
||||
- без UI можно тестировать контракт вручную, но нельзя считать сценарий запуска достаточно собранным для нормальной проверки.
|
||||
|
||||
### 6. Реализовать базовую синхронизацию серверов
|
||||
|
||||
Что сделать:
|
||||
|
||||
- сделать обмен состоянием между серверами по модели `server-to-server`;
|
||||
- определить минимальный набор данных, который обязан синхронизироваться;
|
||||
- предусмотреть фиксацию синхронизированного состояния в `Arweave`, а `Solana` использовать как якорь и ссылочный слой;
|
||||
- описать, какой сервер считается источником истины в спорных случаях или как решается конфликт.
|
||||
|
||||
Почему это в `этап1`:
|
||||
|
||||
- без межсерверной синхронизации трудно обосновать архитектуру сети как воспроизводимую и переносимую;
|
||||
- это напрямую связано с идеей, что любой сможет поднять свой сервер.
|
||||
|
||||
### 7. Подготовить базовый сценарий архивирования и восстановления
|
||||
|
||||
Что сделать:
|
||||
|
||||
- описать и частично реализовать схему: серверы синхронизируются между собой, архив состояния уходит в `Arweave`, ссылка/якорь фиксируется через `Solana`;
|
||||
- определить минимальный сценарий восстановления блоков или состояния из архивного слоя;
|
||||
- подтвердить, что новый сервер может получить достаточно данных для старта.
|
||||
|
||||
Почему это в `этап1`:
|
||||
|
||||
- это один из ключевых признаков независимой и воспроизводимой DAO-инфраструктуры.
|
||||
|
||||
## Этап2
|
||||
|
||||
Цель этапа: усилить безопасность, автономность и удобство системы после того, как минимальный DAO-сценарий уже запустился и проверен в `devnet`.
|
||||
|
||||
### 1. Смена ключей цифровой подписи
|
||||
|
||||
Что сделать:
|
||||
|
||||
- продумать и реализовать смену `root key`, `device key`, `blockchain key`;
|
||||
- описать ограничения, кто и в каком сценарии может менять каждый тип ключа;
|
||||
- продумать, как не потерять доступ и как обновлять доверие к новым ключам.
|
||||
|
||||
Почему это в `этап2`:
|
||||
|
||||
- для production это очень важно;
|
||||
- для первого тестового запуска можно временно использовать фиксированный набор ключей.
|
||||
|
||||
### 2. Полная повторная перепроверка всех сценариев
|
||||
|
||||
Что сделать:
|
||||
|
||||
- повторно прогнать регистрацию, DAO, выплаты, билеты, синхронизацию и архивирование после стабилизации `этап1`;
|
||||
- оформить итоговый чек-лист ручной проверки;
|
||||
- отдельно проверить пограничные сценарии и восстановление после ошибок.
|
||||
|
||||
Почему это в `этап2`:
|
||||
|
||||
- это обязательный шаг перед переходом от "собрали" к "доверяем".
|
||||
|
||||
### 3. Устройство на ESP32 как сабсервер с ключами
|
||||
|
||||
Что сделать:
|
||||
|
||||
- дописать прошивку, чтобы устройство могло выступать сабсервером с ключами;
|
||||
- дать ему возможность регистрироваться и подключаться к серверу;
|
||||
- определить, какие операции устройство подписывает и где хранит ключевой материал.
|
||||
|
||||
Почему это в `этап2`:
|
||||
|
||||
- это очень сильное развитие архитектуры, но оно не должно блокировать первый DAO-запуск.
|
||||
|
||||
### 4. Логин и подпись через коробочки / устройства
|
||||
|
||||
Что сделать:
|
||||
|
||||
- реализовать сценарий входа через устройство или хотя бы сценарий подписи сообщений и ключей через устройство;
|
||||
- определить, как это встраивается в регистрацию DAO и подтверждение действий;
|
||||
- проверить, можно ли через это безопасно регистрировать DAO или подписывать критичные команды.
|
||||
|
||||
Почему это в `этап2`:
|
||||
|
||||
- это следующий уровень безопасности и UX, но не минимальный блокер первого старта.
|
||||
|
||||
### 5. Создание тестового DAO с использованием устройств подписи
|
||||
|
||||
Что сделать:
|
||||
|
||||
- после готовности устройств собрать тестовый DAO-сценарий уже с аппаратным участием;
|
||||
- проверить, где устройство достаточно, а где всё ещё нужен обычный кошелёк или управляющий ключ.
|
||||
|
||||
Почему это в `этап2`:
|
||||
|
||||
- это проверка усиленной модели, а не базового старта.
|
||||
|
||||
### 6. Расписание синхронизации серверов
|
||||
|
||||
Что сделать:
|
||||
|
||||
- определить периодичность и правила фоновой синхронизации;
|
||||
- продумать ручной и автоматический режим;
|
||||
- решить, как часто публиковать архивные снимки и якоря.
|
||||
|
||||
Почему это в `этап2`:
|
||||
|
||||
- сначала важнее добиться самой работающей синхронизации, а потом уже делать её регулярной и автономной.
|
||||
|
||||
### 7. Полное восстановление блоков из Solana/Arweave
|
||||
|
||||
Что сделать:
|
||||
|
||||
- довести процедуру восстановления до сценария "любой может поднять свой сервер";
|
||||
- определить минимальный bootstrap-набор;
|
||||
- проверить восстановление на чистом окружении.
|
||||
|
||||
Почему это в `этап2`:
|
||||
|
||||
- для концепции сети это критично, но как полноценная задача обычно идёт после появления базового архива и первичной синхронизации.
|
||||
|
||||
## Что блокирует первый запуск сильнее всего
|
||||
|
||||
Если расставить приоритет внутри `этап1`, то самый жёсткий порядок сейчас выглядит так:
|
||||
|
||||
1. pure Rust регистрация пользователей и ручная проверка сценария;
|
||||
2. DAO/gov-контур и его права управления;
|
||||
3. доработка выплат с третьей очередью;
|
||||
4. покупка билетов через smart contract и UI-проверка очереди;
|
||||
5. межсерверная синхронизация;
|
||||
6. архивирование в `Arweave` с якорем в `Solana`;
|
||||
7. минимальное восстановление состояния новым сервером.
|
||||
|
||||
## Что уже частично похоже на готовое
|
||||
|
||||
По текущим документам и следам в проекте уже видно, что:
|
||||
|
||||
- переход `shine_users` и `shine_login_guard` на pure Rust уже начат и в значительной степени сделан;
|
||||
- архитектура DAO, `shine_users` и `shine_payments` уже описана;
|
||||
- часть Solana-структуры и PDA-форматов уже формализована;
|
||||
- тема ESP32 уже отдельно присутствует в проекте как направление.
|
||||
|
||||
Это хорошо, потому что документ получается не "с нуля", а как сборка того, что уже назрело в коде и планах.
|
||||
|
||||
## Вопросы, которые всё ещё стоит уточнить
|
||||
|
||||
1. Какой именно стандарт DAO планируется использовать в первом проходе: готовый governance-стек Solana или собственная минимальная обвязка вокруг управляющих кошельков?
|
||||
2. Третья очередь в `shine_payments` уже точно определена по смыслу, или пока есть только решение "она нужна", но без финальной экономики?
|
||||
3. Что именно считается единицей синхронизации между серверами: блоки SHiNE, агрегированные снапшоты, PDA-состояния, или смесь этих вариантов?
|
||||
4. Нужен ли для `этап1` уже полноценный автоматический recovery нового сервера, или достаточно доказать это в полу-ручном сценарии?
|
||||
5. Покупка билетов должна в первом проходе работать только через web/UI, или также нужен отдельный сценарий из серверного UI или скриптов?
|
||||
|
||||
## Рекомендуемый следующий практический шаг
|
||||
|
||||
Если идти без распыления, то следующим рабочим фокусом стоит считать:
|
||||
|
||||
1. закрыть ручную проверку pure Rust регистрации;
|
||||
2. после этого формализовать минимальный DAO-контур;
|
||||
3. затем переходить к третьей очереди выплат и к UI покупки билетов;
|
||||
4. после этого делать синхронизацию, архив и восстановление.
|
||||
@ -1,92 +0,0 @@
|
||||
# DEBUG: тестирование сетевого соединения между двумя клиентами
|
||||
|
||||
Документ описывает временный debug-контур для проверки WebRTC соединения между двумя активными WS-сессиями.
|
||||
|
||||
## 1) Подготовка
|
||||
|
||||
|
||||
0. Убедись, что в `application.properties` включен параметр:
|
||||
`debug.tempApi.enabled=true`
|
||||
1. Создай файл `.debug-token` в корне проекта на основе `debug-token.example`.
|
||||
2. В `.debug-token` должна быть одна строка: секретный токен.
|
||||
3. Перезапусти сервер.
|
||||
|
||||
## 2) API debug
|
||||
|
||||
Базовый заголовок для всех запросов:
|
||||
|
||||
```bash
|
||||
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>"
|
||||
```
|
||||
|
||||
### 2.1 Получить список живых клиентов
|
||||
|
||||
```bash
|
||||
curl -s \
|
||||
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>" \
|
||||
http://localhost:7070/debug/ws/clients | jq
|
||||
```
|
||||
|
||||
Ответ содержит `sessionId`, `login`, `ip`, `userAgent`, и клиентскую информацию.
|
||||
|
||||
### 2.2 Запустить debug-соединение между двумя сессиями
|
||||
|
||||
```bash
|
||||
curl -s -X POST \
|
||||
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"initiatorSessionId": "SESSION_ID_A",
|
||||
"responderSessionId": "SESSION_ID_B",
|
||||
"clearDebugLog": false
|
||||
}' \
|
||||
http://localhost:7070/debug/ws/connect | jq
|
||||
```
|
||||
|
||||
В ответе придёт `runId`. Его используй для фильтра логов.
|
||||
|
||||
### 2.3 Читать последние N debug-логов
|
||||
|
||||
```bash
|
||||
curl -s \
|
||||
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>" \
|
||||
"http://localhost:7070/debug/ws/logs?limit=200&runId=<RUN_ID>" | jq
|
||||
```
|
||||
|
||||
## 3) Операционный сценарий “Codex + пользователь”
|
||||
|
||||
1. Codex поднимает сервер и сообщает пользователю ссылку на UI.
|
||||
2. Codex пишет пользователю: **«Запусти двух клиентов и скажи “продолжай”»**.
|
||||
3. Пользователь запускает два клиента (лучше под разными логинами).
|
||||
4. Пользователь пишет: **«продолжай»**.
|
||||
5. Codex:
|
||||
- вызывает `/debug/ws/clients`,
|
||||
- выбирает 2 сессии,
|
||||
- вызывает `/debug/ws/connect`,
|
||||
- получает `runId`,
|
||||
- читает `/debug/ws/logs?runId=...` и сообщает прогресс.
|
||||
6. Если соединение не удалось:
|
||||
- Codex сообщает ошибки по логам,
|
||||
- при необходимости просит перезапустить 2 клиента,
|
||||
- повторяет запуск debug-run.
|
||||
|
||||
## 4) Какие сообщения считать успехом
|
||||
|
||||
- `peer_connection_connected`
|
||||
- `debug_connection_success`
|
||||
- `signal_sent_200/210/220` без ошибок
|
||||
|
||||
## 5) Что говорить пользователю в ходе прогона (через «колонку»/чат)
|
||||
|
||||
Рекомендуемые фразы:
|
||||
|
||||
- «Сервер запущен. Запусти двух клиентов и напиши “продолжай”.»
|
||||
- «Вижу 2 активные сессии, запускаю тест соединения.»
|
||||
- «Тест запущен, runId=... Сейчас проверяю логи.»
|
||||
- «Соединение установлено / не установлено. Ниже причины и следующий шаг.»
|
||||
|
||||
## 6) Ограничения
|
||||
|
||||
- Механизм временный, не для production-эксплуатации.
|
||||
- Доступ к debug API имеет любой, кто знает токен.
|
||||
- Рекомендуется тестить между разными логинами.
|
||||
@ -1,62 +0,0 @@
|
||||
0. ПЕРЕДЕЛАТЬ ВСЁ НА НОВЫЙ ФОРМАТ!!
|
||||
|
||||
ВЫНЕСТИ ЭТИ ТРИ ВЕЩИ В ОБЩИЙ ПАРСЕР
|
||||
* [2] type - тип соощения
|
||||
* [2] Sиbtype - субтип сообщения
|
||||
* [2] version - версия формата соощения
|
||||
|
||||
А ОСТАЛЬНОЕ В РЕАЛИЗАЦИЮ
|
||||
|
||||
|
||||
ПЕРЕДЕЛАЕМ БД
|
||||
|
||||
1. СДЕЛАЕМ ЛИНИЮ ТОЛЬКО ДЛЯ ТЕХ ТИПОВ КОМУ НАДО (ЛАЙКАМ И ОТВЕТАМ НЕ НАДО)
|
||||
(НОМЕР СООБЩЕНИЯ В ЛИНИИ ХРАНИТЬ В БЛОКАХ ВРОДЕ И НЕ НАДО ТЕМ БОЛЕЕ ЕГО ПОТОМ ПЕРЕПРОВЕРЯТЬ ВСЁ РАВНО)
|
||||
А МОЖЕТ И НАДО ТК КАК ПО ОДНОМУ БЛОКУ ( ИЛИ ЧАСТИ БЛОКОВ ПОНЯТЬ КАКАЯ ЭТО ЧАСТЬ ПЕРЕПИСКИ - ВЕДЬ ГЛОБАЛ НОМЕР ВООБЩЕ НЕ ПОКАЗАТЕЛЬ)
|
||||
|
||||
В БД ПОМЕЧАТЬ ЧТО БЛОК ИЗ ЭТОЙ ЛИНИИ (ДЛЯ БЫСТРОГО ПОИСКА)
|
||||
|
||||
А УНИКАЛЬНЫЙ НОМЕР ЛИНИИ ЭТО ПО СУТИ НОМЕР СООБЩЕНИЯ СОЗДАВШЕГО ЛИНИЮ КАНАЛ (НУ И ФОРМАТ СООБЩЕНИЯ НАЧАЛА ЛИНИИ - КАНАЛА)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
3. СООТВЕТСТВЕННО удалить НАПИСАТЬ/ПЕРОВЕРИТЬ НОРМАЛЬНЫЙ SubscriptionsDAO - ТК СТАРЫЙ РАБОТАЛ НО НА ДРУГОМ ФОРМАТЕ И ТИПО КРИВО
|
||||
|
||||
и дальше:
|
||||
ЗДЕЛАТЬ ТРИ ЗАПРОСА:
|
||||
СПИСОК КАНАЛОВ НА КОГО ПОДПИСАН И ПО СКОЛЬКО СООБЩЕНИЙ И ПОСЛДНИЙ ТЕКСТ
|
||||
ДОДЕЛАТЬ И СВЯЗ ПОДПИСАН УЖЕ НЕ ТОЛЬКО НА ЧЕЛА НО И НА КАНАЛ. (И ПОЛУЧАЕТСЯ ЕСТЬ ОБЩИЙ КАНАЛЛ ПОСТОВ (НО НЕКОТОРЫЕ ПОСТЫ В НИКУДА-
|
||||
А НЕКОТОРЫЕ ПОСТЫ ОБЪЯВЛЕНИЕ КАНАЛА
|
||||
|
||||
СПИСОК СООБЩЕНИЙ В КАНАЛЕ
|
||||
|
||||
ОПСИСАНИЕ ОДНОГО СОООБЩЕНИЯ (С ИСТОРИЕЙ ДО НАЧАЛА ВЕТКИ И СО ВСЕМИ ОТВЕТАМИ НА НЕГО)
|
||||
|
||||
(НУ И В БУДУЩЕМ четвёртый ИСТОРИЮ сообщения ПО ЕДИТУ)
|
||||
|
||||
|
||||
И ПОМЯТКА
|
||||
ВСЕГДА СЧИТАЕМ ПО ПОСЛЕДНЕМУ БЛОКЧЕЙНУ ДОСТУПНОМУ ПОЛЬЗОВАТЕЛЮ
|
||||
ХОТЯ ССЫЛКА ПО НОМЕРУ БЛОКЧЕЙНА КУДА ДОБАВИЛИ
|
||||
|
||||
ЛАЙКИ И ОТВЕТЫ ПИШЕМ НА НОМЕР СООБЩЕНИЯ ЕДИТА
|
||||
(СЧИТАЕМ ТРИГЕРОМ И НА ОРИГИНАЛЬНЫЙ СУМАРНОЕ И ОТДЕЛЬНО НА НЕГО, И НА КАЖДЫЙ ЕДИТ ОТДЕЛЬНО)
|
||||
|
||||
ОТВЕТЫ ПОКАЗЫВАЕМ ВСЕ ВРАЗ
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
Сделать возможность убрать свой лайк. (пока не надо а сложность что надо больше проверок) - хотя можно и без проверки, просто за двойной лайк или за снятие двойное лайка. Будет двойное проникновение :)) тому кто изменил код клиента и убрал проверку на клиенте - и блокчейн заблокируется и всё.
|
||||
поэтому просто на каждую реакцию добавиться убрать эту ракцию .
|
||||
- это просто
|
||||
|
||||
сделатьпотом что бы в солану_юзерс хранилось имя текущего блокчейна пользователя. Что бы потом можно было грузить именно актуальный ТО ЕСТЬ потом можно будет менять блокченый!
|
||||
|
||||
|
||||
|
||||
|
||||
сделать сессион пасворд тоже ключём подписи устройства!!
|
||||
@ -1,22 +0,0 @@
|
||||
Перечень библиотек и их краткое описание
|
||||
|
||||
shine-server-log
|
||||
Статический “сиренный” метод для максимально заметного критического лога администратору
|
||||
|
||||
shine-server-config
|
||||
Минимальный конфиг-лоадер, который один раз читает application.properties и даёт доступ к параметрам.
|
||||
|
||||
shine-server-geo
|
||||
Утилиты, которые вытаскивают IP/язык/UA из Jetty WebSocket и (опционально) резолвят гео по IP с кэшем в БД.
|
||||
|
||||
shine-server-crypto
|
||||
Базовые крипто-утилиты для SHA-256 и Ed25519 (BouncyCastle) + проверка подписи/хэша для .bch сущностей и маленький self-test.
|
||||
|
||||
shine-server-bd
|
||||
Библиотека реалезующая всю работу с БД:
|
||||
|
||||
shine-server-blockchain
|
||||
Библиотека, которая задаёт единый бинарный формат блоков (RAW+signature+hash), парсит/валидирует “тело” блока по type/version, и проверяет целостность/подпись цепочки через SHA-256 + Ed25519 с привязкой к login и предыдущим хэшам.
|
||||
|
||||
shine-server-protocol
|
||||
Библиотека JSON-протокол поверх WebSocket для взаимодействия с клиентами.
|
||||
@ -1,17 +0,0 @@
|
||||
shine-server-bd — это библиотека реалезующая всю работу с БД:
|
||||
|
||||
хранит пользователей/сессии/параметры/кэш IP→гео и данные блокчейна (состояние + блоки), предоставляя единый SqliteDbController для соединений, набор DAO под каждую таблицу (Singleton, методы с Connection для транзакций и без Connection — сами открывают/закрывают), и простые entity-модели как контейнеры данных для маппинга ResultSet↔Java.
|
||||
|
||||
Логика структуры классов (в двух словах):
|
||||
|
||||
shine.db.SqliteDbController — один вход в БД: читает db.path, при отсутствии файла создаёт БД, выдаёт новые Connection и настраивает PRAGMA.
|
||||
shine.db.DatabaseInitializer — разовая сборка схемы (таблицы + индексы).
|
||||
|
||||
|
||||
shine.db.entities.* — POJO-модели строк таблиц (без логики, только поля/геттеры/сеттеры + иногда удобные методы вроде getDeviceKeyByte()).
|
||||
shine.db.dao.* — DAO по таблицам: ActiveSessionsDAO, SolanaUsersDAO, UserParamsDAO, IpGeoCacheDAO, BlockchainStateDAO, BlocksDAO; плюс “сервисные” DAO:
|
||||
|
||||
UserCreateDAO — атомарная регистрация пользователя в транзакции (BEGIN IMMEDIATE + rollback/commit).
|
||||
// Временное решение позволяющее регистрировать новых пользователей
|
||||
// атомарно и добавляет запись и в solana_users и в BlockchainState
|
||||
|
||||
@ -1,85 +0,0 @@
|
||||
shine-server-blockchain — это библиотека, которая задаёт формат блока, правила парсинга/валидации тела, крипто-проверку (hash+Ed25519) и безопасную работу с файлами блокчейна (data/<name>.bch через временный .tmp_bch).
|
||||
|
||||
Как устроена структура и логика работы
|
||||
|
||||
1) “Блок” как центральный объект (ядро)
|
||||
BchBlockEntry — единая модель блока “как лежит на диске/в сети”:
|
||||
читает/собирает байты в формате RAW + signature64 + hash32
|
||||
сразу парсит body через BodyRecordParser
|
||||
сразу проверяет что lineIndex совпадает с тем, что ожидает конкретный тип body (expectedLineIndex())
|
||||
То есть: всё, что считается “блоком”, обязано быть самодостаточно валидным уже на этапе создания объекта.
|
||||
|
||||
2) “Body” как плагины по типам (расширяемая часть) ! <-- Новые типы записей добавлть сюда !
|
||||
BodyRecord — интерфейс контракта для всех тел:
|
||||
type/version — идентификаторы формата
|
||||
expectedLineIndex() — жёсткое правило “в какой линии может жить”
|
||||
check() — логическая валидация содержимого
|
||||
toBytes() — сериализация обратно в бинарь
|
||||
|
||||
BodyRecordParser — диспетчер: читает первые 4 байта (type+ver) и выбирает нужный класс:
|
||||
HeaderBody (lineIndex=0)
|
||||
TextBody (lineIndex=1)
|
||||
ReactionBody (lineIndex=2)
|
||||
|
||||
Добавление нового типа = добавить новый класс XxxBody + кейс в BodyRecordParser.
|
||||
|
||||
3) Криптография как отдельный слой проверки
|
||||
BchCryptoVerifier отвечает за “как получить хэш и как проверить подпись”:
|
||||
строит preimage = "SHiNE" + login + prevGlobalHash32 + prevLineHash32 + rawBytes
|
||||
считает sha256(preimage) и сравнивает с hash32 внутри блока
|
||||
проверяет Ed25519 подпись над hash32
|
||||
Важно: BchBlockEntry не проверяет подпись — он проверяет структуру блока и правильность body/lineIndex, а криптопроверка вынесена отдельно.
|
||||
|
||||
4) Утилиты вокруг имени и файлов
|
||||
|
||||
BlockchainNameUtil — извлекает login из blockchainName (отрезает 3 символа суффикса).
|
||||
FileStoreUtil — безопасное файловое хранилище:
|
||||
|
||||
|
||||
5) Объяснение структуры работы
|
||||
|
||||
Типичный сценарий: пришёл блок → проверить → принять
|
||||
Шаг 0. Контекст (что у нас уже есть снаружи)
|
||||
|
||||
Снаружи библиотеки (в сервере) у тебя уже известны:
|
||||
userLogin — владелец блокчейна
|
||||
publicKey32 — публичный ключ пользователя
|
||||
prevGlobalHash32 — хэш предыдущего блока по глобальной цепи
|
||||
prevLineHash32 — хэш предыдущего блока по текущей линии
|
||||
|
||||
Библиотека не хранит это сама, она ожидает, что сервер это передаст.
|
||||
|
||||
Шаг 1. Парсинг блока (структура + логика)
|
||||
BchBlockEntry block = new BchBlockEntry(fullBytes);
|
||||
|
||||
|
||||
Что происходит здесь автоматически:
|
||||
проверяется длина блока
|
||||
проверяется recordSize
|
||||
парсится RAW-заголовок
|
||||
парсится body через BodyRecordParser
|
||||
проверяется, что lineIndex соответствует типу body
|
||||
(HEADER → line 0, TEXT → line 1, REACTION → line 2 и т.д.)
|
||||
❗ На этом шаге никакой криптографии ещё нет — только структура и логика формата.
|
||||
|
||||
Если тут не упало исключение → блок структурно корректен.
|
||||
|
||||
Шаг 2. Подготовка данных для криптопроверки (они получаются просто из частей байтов полного блокас подписью)
|
||||
byte[] rawBytes = block.getRawBytes();
|
||||
byte[] signature64 = block.getSignature64();
|
||||
byte[] hash32FromTail = block.getHash32();
|
||||
|
||||
Важно:
|
||||
rawBytes — это ровно те байты, которые участвовали в хэшировании
|
||||
hash32FromTail — это то, что автор блока положил внутрь блока
|
||||
|
||||
Шаг 3. Криптографическая проверка (ключевой вызов)
|
||||
boolean ok = BchCryptoVerifier.verifyAll(
|
||||
userLogin,
|
||||
prevGlobalHash32,
|
||||
prevLineHash32,
|
||||
rawBytes,
|
||||
signature64,
|
||||
publicKey32,
|
||||
hash32FromTail
|
||||
);
|
||||
@ -1,48 +0,0 @@
|
||||
Общая структура блока
|
||||
|
||||
Блок — это бинарная запись фиксированного формата:
|
||||
|
||||
[ RAW ][ signature64 ][ hash32 ]
|
||||
|
||||
RAW — данные блока (участвуют в хэшировании и подписи)
|
||||
signature64 — Ed25519-подпись над hash32
|
||||
hash32 — SHA-256 от preimage (привязан к цепочке и владельцу)
|
||||
|
||||
RAW-часть (BigEndian)
|
||||
recordSize int32 — размер RAW (без signature+hash)
|
||||
recordNumber int32 — глобальный номер блока
|
||||
timestamp int64 — unix time (seconds)
|
||||
lineIndex int16 — индекс линии
|
||||
lineNumber int32 — номер блока внутри линии
|
||||
bodyBytes bytes — тело блока (type+version+payload)
|
||||
|
||||
Общая структура блокчейна
|
||||
|
||||
Блокчейн — это:
|
||||
линейная цепочка блоков (по recordNumber)
|
||||
внутри неё — параллельные логические линии (lineIndex)
|
||||
каждая линия имеет собственную нумерацию (lineNumber) и prevLineHash
|
||||
вся цепочка связана ещё и prevGlobalHash
|
||||
|
||||
👉 Таким образом, каждый блок:
|
||||
связан с предыдущим глобальным блоком
|
||||
и с предыдущим блоком своей линии
|
||||
|
||||
Это даёт:
|
||||
строгий порядок всей истории
|
||||
и независимую валидацию логических потоков
|
||||
|
||||
Криптографический смысл блока
|
||||
|
||||
Хэш блока считается от:
|
||||
"SHiNE" +
|
||||
login +
|
||||
prevGlobalHash32 +
|
||||
prevLineHash32 +
|
||||
RAW
|
||||
|
||||
Это означает:
|
||||
блок жёстко привязан к владельцу (login)
|
||||
блок невозможно перенести в другую цепочку
|
||||
подмена предыдущего блока ломает всю цепь
|
||||
Подпись Ed25519 делается над этим хэшем.
|
||||
@ -1,36 +0,0 @@
|
||||
Формат и смысл существующих Body
|
||||
|
||||
1) HeaderBody (type=0, ver=1)
|
||||
|
||||
Линия: lineIndex = 0
|
||||
Смысл:
|
||||
Генезис-блок блокчейна, объявляет формат и владельца.
|
||||
|
||||
Содержит:
|
||||
сигнатуру формата "SHiNE"
|
||||
login владельца блокчейна
|
||||
👉 Всегда первый блок, всегда в линии 0.
|
||||
|
||||
2) TextBody (type=1, ver=1)
|
||||
|
||||
Линия: lineIndex = 1
|
||||
Смысл:
|
||||
Основной контент — текстовые записи (посты, сообщения, дневник).
|
||||
|
||||
Содержит:
|
||||
UTF-8 текст произвольной длины
|
||||
👉 Это “основная история” блокчейна пользователя.
|
||||
|
||||
3) ReactionBody (type=2, ver=1)
|
||||
|
||||
Линия: lineIndex = 2
|
||||
Смысл:
|
||||
Связь с другим блокчейном или блоком (реакция, ответ, лайк, ссылка).
|
||||
|
||||
Содержит:
|
||||
код реакции
|
||||
имя целевого блокчейна
|
||||
globalNumber целевого блока
|
||||
hash32 целевого блока
|
||||
👉 Это механизм межблокчейн-связей без изменения чужих цепочек.
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
shine-server-config
|
||||
|
||||
Минимальная библиотека конфигурации, предоставляющая потокобезопасный singleton-доступ к параметрам из application.properties.
|
||||
|
||||
Настройки:
|
||||
server.port=7070 — порт запуска сервера
|
||||
db.path=data/shine.sqlite — путь к SQLite базе данных
|
||||
@ -1,8 +0,0 @@
|
||||
shine-server-crypto
|
||||
|
||||
О чём: базовые крипто-утилиты для SHA-256 и Ed25519 (BouncyCastle) + проверка подписи/хэша для .bch сущностей и маленький self-test.
|
||||
Внешние методы, которые вызываются:
|
||||
BchCryptoVerifier.verifyAll(), BchCryptoVerifier.buildPreimage(), Ed25519Util.generatePrivateKey(), Ed25519Util.generatePrivateKeyFromString(), Ed25519Util.derivePublicKey(), Ed25519Util.sign(), Ed25519Util.verify(), Ed25519Util.keyToBase64(), Ed25519Util.keyFromBase64(),
|
||||
HashSHA256Util.sha256()
|
||||
|
||||
HashSHA256Util.loginToLoginId(), HashSHA256Util.loginIdFromLogin(),
|
||||
@ -1,19 +0,0 @@
|
||||
shine-server-geo
|
||||
|
||||
Назначение: утилиты для получения “кто подключился” (IP/UA/язык) и геолокации по IP (с опциональным кэшем в БД).
|
||||
|
||||
Классы:
|
||||
|
||||
ClientInfoService — собирает строку UA/ch-ua/platform/mobile/remoteIP, вытаскивает реальный IP (приоритет: X-Forwarded-For → X-Real-IP → remoteAddress), парсит первый Accept-Language.
|
||||
GeoLookupService — геолокация по IP через внешний API (ip-api.com), умеет вариант без кэша и с кэшем в таблице ip_geo_cache через IpGeoCacheDAO (пишет даже unknown), плюс метод получения внешнего IP через api.ipify.org.
|
||||
GeoLookupTestMain — консольный тест: берёт IP из аргумента или определяет внешний, вызывает геолокацию и печатает результат + время.
|
||||
|
||||
|
||||
Внешние (публично используемые) методы:
|
||||
|
||||
ClientInfoService.buildClientInfoString(Session) — формирует строку с User-Agent, client-hints и реальным IP клиента
|
||||
ClientInfoService.extractClientIp(Session) — извлекает реальный IP (X-Forwarded-For / X-Real-IP / remoteAddress)
|
||||
ClientInfoService.extractPreferredLanguageTag(Session) — возвращает основной язык клиента из Accept-Language
|
||||
GeoLookupService.resolveCountryCityOrIp(String ip) — геолокация по IP без кэша (Country, City или unknown)
|
||||
GeoLookupService.resolveCountryCityOrIpWithCache(String ip) — геолокация по IP с кэшированием в БД
|
||||
GeoLookupService.fetchPublicIpOrDefault(String fallbackIp) — получение внешнего IP текущей машины
|
||||
@ -1,10 +0,0 @@
|
||||
shine-log (BlockchainAdminNotifier)
|
||||
|
||||
Суть (1 предложение): единая точка для “красного” оповещения админа о критических проблемах консистентности, сейчас — через максимально заметный log.error, позже — через Telegram/email/webhook и т.п.
|
||||
|
||||
Структура (очень кратко):
|
||||
|
||||
BlockchainAdminNotifier (final utility)
|
||||
|
||||
BlockchainAdminNotifier.critical(String message)
|
||||
BlockchainAdminNotifier.critical(String message, Throwable t)
|
||||
@ -1,52 +0,0 @@
|
||||
shine-server-protocol
|
||||
Библиотека JSON-протокол поверх WebSocket для взаимодействия с клиентами.
|
||||
|
||||
|
||||
|
||||
Всё общение — JSON поверх WebSocket
|
||||
Формат всегда один:
|
||||
|
||||
request: op + requestId + payload
|
||||
response: op + requestId + status + payload
|
||||
|
||||
|
||||
Net_Event / Net_Request / Net_Response
|
||||
Базовые классы протокола.
|
||||
requestId связывает запрос и ответ, status = результат.
|
||||
|
||||
Хэндлер = логика операции
|
||||
Каждый op обрабатывается своим JsonMessageHandler.
|
||||
|
||||
Entities (Request / Response)
|
||||
DTO-классы для Jackson:
|
||||
|
||||
Net_Xxx_Request — что приходит от клиента
|
||||
|
||||
Net_Xxx_Response — что уходит клиенту
|
||||
|
||||
JsonHandlerRegistry
|
||||
Связывает:
|
||||
|
||||
op → RequestClass
|
||||
op → Handler
|
||||
|
||||
|
||||
JsonInboundProcessor
|
||||
Единая точка входа:
|
||||
парсит JSON → маппит payload → вызывает handler → собирает ответ JSON.
|
||||
|
||||
Папки по темам
|
||||
|
||||
auth/ — авторизация и сессии
|
||||
(AuthChallenge → CreateAuthSession → Refresh / List / Close)
|
||||
|
||||
blockchain/ — AddBlock
|
||||
|
||||
tempToTest/ — AddUser (временный, потом уйдёт в блокчейн-логику)
|
||||
|
||||
ConnectionContext
|
||||
Состояние одного WebSocket-подключения (login, session, authStatus).
|
||||
|
||||
ActiveConnectionsRegistry
|
||||
Глобальный реестр активных авторизованных соединений
|
||||
(нужно для закрытия других сессий).
|
||||
@ -1,209 +0,0 @@
|
||||
SHiNE — структура БД (актуальная версия)
|
||||
|
||||
Перечень таблиц и назначение
|
||||
|
||||
solana_users
|
||||
Справочник пользователей: логин + ключ устройства + (опционально) Solana-ключ.
|
||||
Базовая таблица, используется как FK почти везде.
|
||||
|
||||
active_sessions
|
||||
Активные сессии авторизации/работы клиента: секреты, тайминги, WebPush-данные, IP и информация о клиенте.
|
||||
|
||||
users_params
|
||||
Хранилище актуальных параметров пользователя.
|
||||
Для каждой пары (login, param) хранится только самая новая версия по time_ms.
|
||||
|
||||
ip_geo_cache
|
||||
Кеш геолокации по IP для снижения нагрузки на внешние сервисы.
|
||||
|
||||
blockchain_state
|
||||
Агрегированное состояние блокчейна по blockchain_name:
|
||||
лимиты, текущий размер, последний глобальный блок и состояние линий 0..7.
|
||||
|
||||
blocks
|
||||
Журнал всех блоков и сообщений.
|
||||
Содержит историю событий: тексты, реакции, ответы, связи.
|
||||
PRIMARY KEY намеренно отсутствует.
|
||||
|
||||
connections_state ⭐
|
||||
Актуальное состояние связей между пользователями
|
||||
(друг / контакт / подписка).
|
||||
Обновляется автоматически на основе событий из blocks.
|
||||
|
||||
message_stats ⭐
|
||||
Агрегированные счётчики лайков и ответов на конкретные сообщения.
|
||||
Поддерживается триггерами из blocks.
|
||||
|
||||
Таблицы подробно
|
||||
|
||||
solana_users
|
||||
login — TEXT PK — уникальный логин пользователя
|
||||
device_key — TEXT NOT NULL — публичный ключ устройства (Base64(32) / HEX(64))
|
||||
solana_key — TEXT NULL — публичный ключ Solana-аккаунта
|
||||
|
||||
active_sessions
|
||||
session_id — TEXT PK — идентификатор сессии
|
||||
login — TEXT NOT NULL, FK → solana_users(login)
|
||||
session_pwd — TEXT NOT NULL — секрет сессии
|
||||
storage_pwd — TEXT NOT NULL — секрет storage
|
||||
session_created_at_ms — INTEGER NOT NULL
|
||||
last_authirificated_at_ms — INTEGER NOT NULL
|
||||
push_endpoint — TEXT NULL
|
||||
push_p256dh_key — TEXT NULL
|
||||
push_auth_key — TEXT NULL
|
||||
client_ip — TEXT NULL
|
||||
client_info_from_client — TEXT NULL
|
||||
client_info_from_request — TEXT NULL
|
||||
user_language — TEXT NULL
|
||||
|
||||
users_params
|
||||
login — TEXT NOT NULL, FK → solana_users(login)
|
||||
param — TEXT NOT NULL
|
||||
time_ms — INTEGER NOT NULL
|
||||
value — TEXT NOT NULL
|
||||
device_key — TEXT NULL
|
||||
signature — TEXT NULL
|
||||
|
||||
Ограничение:
|
||||
UNIQUE(login, param)
|
||||
|
||||
Логика:
|
||||
обновление принимается только если excluded.time_ms > users_params.time_ms
|
||||
|
||||
ip_geo_cache
|
||||
ip — TEXT PK
|
||||
geo — TEXT NULL
|
||||
updated_at_ms — INTEGER NOT NULL
|
||||
|
||||
blockchain_state
|
||||
blockchain_name — TEXT PK
|
||||
login — TEXT NOT NULL, FK → solana_users(login)
|
||||
blockchain_key — TEXT NOT NULL
|
||||
size_limit — INTEGER NOT NULL
|
||||
file_size_bytes — INTEGER NOT NULL
|
||||
last_global_number — INTEGER NOT NULL (-1 = genesis)
|
||||
last_global_hash — TEXT NOT NULL
|
||||
updated_at_ms — INTEGER NOT NULL
|
||||
|
||||
Линии 0..7:
|
||||
для каждой линии:
|
||||
lineX_last_number
|
||||
lineX_last_hash
|
||||
|
||||
blocks
|
||||
login — TEXT NOT NULL
|
||||
bch_name — TEXT NOT NULL
|
||||
block_global_number — INTEGER NOT NULL
|
||||
block_global_pre_hash — TEXT NOT NULL
|
||||
block_line_index — INTEGER NOT NULL
|
||||
block_line_number — INTEGER NOT NULL
|
||||
block_line_pre_hash — TEXT NOT NULL
|
||||
msg_type — INTEGER NOT NULL
|
||||
msg_sub_type — INTEGER NOT NULL
|
||||
block_bytes — BLOB NULL
|
||||
|
||||
Ссылка на другой блок (nullable):
|
||||
to_login
|
||||
to_bch_name
|
||||
to_block_global_number
|
||||
to_block_hash
|
||||
|
||||
connections_state ⭐
|
||||
Текущее агрегированное состояние связей.
|
||||
|
||||
login — TEXT NOT NULL
|
||||
rel_type — INTEGER NOT NULL
|
||||
10 = FRIEND
|
||||
20 = CONTACT
|
||||
30 = FOLLOW
|
||||
to_login — TEXT NOT NULL
|
||||
to_bch_name — TEXT NOT NULL
|
||||
to_block_global_number — INTEGER NULL
|
||||
to_block_hash — TEXT NULL
|
||||
|
||||
Ограничение:
|
||||
UNIQUE(login, rel_type, to_login)
|
||||
|
||||
message_stats ⭐
|
||||
Счётчики активности по целевому сообщению.
|
||||
|
||||
to_login — TEXT NOT NULL
|
||||
to_bch_name — TEXT NOT NULL
|
||||
to_block_global_number — INTEGER NOT NULL
|
||||
to_block_hash — TEXT NOT NULL
|
||||
likes_count — INTEGER NOT NULL DEFAULT 0
|
||||
replies_count — INTEGER NOT NULL DEFAULT 0
|
||||
|
||||
UNIQUE:
|
||||
(to_login, to_bch_name, to_block_global_number, to_block_hash)
|
||||
|
||||
Триггеры БД (полная логика)
|
||||
|
||||
3.1 Связи пользователей
|
||||
trg_blocks_connection_state_ai
|
||||
AFTER INSERT ON blocks
|
||||
|
||||
Условие:
|
||||
msg_type = 3 (connection)
|
||||
|
||||
Добавление / обновление связи
|
||||
msg_sub_type IN (10,20,30)
|
||||
выполняется UPSERT в connections_state
|
||||
|
||||
Удаление связи
|
||||
msg_sub_type IN (11,21,31)
|
||||
удаляется соответствующая связь:
|
||||
11 → 10
|
||||
21 → 20
|
||||
31 → 30
|
||||
|
||||
Итог:
|
||||
blocks — журнал событий
|
||||
connections_state — всегда актуальное состояние
|
||||
|
||||
3.2 Подсчёт лайков ⭐
|
||||
trg_blocks_message_stats_like_ai
|
||||
AFTER INSERT ON blocks
|
||||
|
||||
Условие:
|
||||
msg_type = 2 (reaction)
|
||||
msg_sub_type = 1 (like)
|
||||
|
||||
Действие:
|
||||
определяется цель по to_bch_name, to_block_global_number, to_block_hash
|
||||
to_login вычисляется как
|
||||
substr(to_bch_name, 1, length(to_bch_name) - 3)
|
||||
выполняется UPSERT в message_stats
|
||||
likes_count += 1
|
||||
|
||||
3.3 Подсчёт ответов ⭐
|
||||
trg_blocks_message_stats_reply_ai
|
||||
AFTER INSERT ON blocks
|
||||
|
||||
Условие:
|
||||
msg_type = 1 (text)
|
||||
msg_sub_type = 2 (reply)
|
||||
|
||||
Действие:
|
||||
цель определяется аналогично лайкам
|
||||
выполняется UPSERT в message_stats
|
||||
replies_count += 1
|
||||
|
||||
Индексы (смысл)
|
||||
|
||||
idx_solana_users_login — поиск пользователя
|
||||
idx_active_sessions_login — сессии пользователя
|
||||
idx_users_params_login — параметры пользователя
|
||||
idx_ip_geo_cache_updated_at — чистка кеша
|
||||
idx_blockchain_state_login — блокчейны пользователя
|
||||
idx_blockchain_state_updated_at — обслуживание
|
||||
idx_blocks_chain_global — чтение цепочки
|
||||
idx_blocks_to_target — реакции / ответы
|
||||
idx_message_stats_target — быстрый доступ к счётчикам
|
||||
|
||||
Итоговая модель мышления
|
||||
|
||||
blocks — неизменяемый журнал событий
|
||||
connections_state — проекция связей
|
||||
message_stats — проекция активности
|
||||
всё вычисляется детерминированно через триггеры
|
||||
@ -1,75 +0,0 @@
|
||||
# Протокол звонков (MVP)
|
||||
|
||||
Версия: browser-to-browser, runtime-only signaling.
|
||||
|
||||
## Цели
|
||||
- Технические сообщения звонка не сохраняются в БД direct_messages.
|
||||
- Первый INVITE рассылается всем активным сессиям получателя и дублируется web push.
|
||||
- Последующие сигналы идут только в конкретную sessionId и не дублируются в push.
|
||||
|
||||
## Операции API
|
||||
|
||||
### 1) CallInviteBroadcast
|
||||
Отправляет общий вызов пользователю.
|
||||
|
||||
Запрос payload:
|
||||
- `toLogin: string`
|
||||
- `callId: string`
|
||||
- `type: 100` (INVITE)
|
||||
|
||||
Поведение сервера:
|
||||
- Рассылает `IncomingCallInvite` во все активные WS-сессии `toLogin`.
|
||||
- В payload события передаёт:
|
||||
- `fromLogin`
|
||||
- `fromSessionId` (session инициатора)
|
||||
- `toLogin`
|
||||
- `callId`
|
||||
- `type=100`
|
||||
- `timeMs`
|
||||
- Отправляет web push уведомление о входящем вызове.
|
||||
|
||||
Ответ payload:
|
||||
- `callId`
|
||||
- `deliveredWsSessions`
|
||||
- `deliveredFcmSessions`
|
||||
|
||||
### 2) CallSignalToSession
|
||||
Отправляет технический сигнал в конкретную сессию.
|
||||
|
||||
Запрос payload:
|
||||
- `toLogin: string`
|
||||
- `targetSessionId: string`
|
||||
- `callId: string`
|
||||
- `type: int`
|
||||
- `data: string` (для SDP/ICE/служебных строк)
|
||||
|
||||
Поведение сервера:
|
||||
- Ищет только `targetSessionId`.
|
||||
- Проверяет, что сессия принадлежит `toLogin`.
|
||||
- Отправляет `IncomingCallSignal` только в эту сессию.
|
||||
- В БД ничего не сохраняет.
|
||||
- Push не отправляет.
|
||||
|
||||
Ответ payload:
|
||||
- `delivered: boolean`
|
||||
|
||||
## Коды type
|
||||
- `100` INVITE
|
||||
- `110` RINGING
|
||||
- `120` ACCEPT
|
||||
- `130` DECLINE_BUSY
|
||||
- `140` TIMEOUT
|
||||
- `150` HANGUP
|
||||
- `200` OFFER
|
||||
- `210` ANSWER
|
||||
- `220` ICE
|
||||
|
||||
## Правила UI/логики
|
||||
- Если уже есть активный звонок и пришел новый INVITE -> автоответ `DECLINE_BUSY` без UI.
|
||||
- После ACCEPT `callId` остаётся во всех OFFER/ANSWER/ICE сообщениях до конца звонка.
|
||||
- При параллельных звонках A<->B допускается детерминированное правило, кто создаёт OFFER.
|
||||
|
||||
## Тайминги MVP
|
||||
- Ожидание подтверждения/реакции после INVITE: до 5с (у инициатора).
|
||||
- Ожидание принятия у входящего звонка: 20с.
|
||||
- Общий лимит ожидания до соединения: 22с.
|
||||
@ -1,9 +0,0 @@
|
||||
|
||||
|
||||
Дальше делать:
|
||||
Описание форматов.
|
||||
Запросы клиент-сервер.
|
||||
Промт на клиента.
|
||||
|
||||
---
|
||||
Потом в сервак дописать синхронизацию серверов.
|
||||
@ -1,53 +0,0 @@
|
||||
# SHiNE Deployment Servers Inventory
|
||||
|
||||
## Scope
|
||||
This folder contains all deployment-related notes and server records for SHiNE.
|
||||
|
||||
## Legacy Production Server
|
||||
- Name: `VPS-02` (legacy)
|
||||
- Access: `root@194.87.0.247`
|
||||
- Current role: old production server
|
||||
- Confirmed services:
|
||||
- `coturn` is installed and active (`systemd: active/running`)
|
||||
- `caddy` is installed (reported by project context; verify version on host if needed)
|
||||
- TURN configuration observed on host:
|
||||
- `listening-port=3478`
|
||||
- `external-ip=194.87.0.247`
|
||||
- `relay-ip=194.87.0.247`
|
||||
- auth mode: `use-auth-secret` + `static-auth-secret`
|
||||
- SHiNE deployment note:
|
||||
- This host is used as current/legacy runtime for SHiNE.
|
||||
- Gradle-based deployment is used in this project (see repository deploy tasks and scripts).
|
||||
|
||||
## Target Production Server (Migration)
|
||||
- Name: `VPS-05` (new)
|
||||
- Access: `root@45.136.124.227`
|
||||
- Planned role: new primary production server for gradual migration
|
||||
- Baseline setup done:
|
||||
- `ripgrep` installed
|
||||
- user `player` created
|
||||
- user `player` added to `sudo` group
|
||||
- deployment directory created: `/home/player/SHiNE`
|
||||
- Rule:
|
||||
- All SHiNE-related runtime files and deployments on VPS-05 should be placed under `/home/player/SHiNE`.
|
||||
|
||||
## Additional TURN Node
|
||||
- Name: `promo-node-93`
|
||||
- Access: `ubuntu@93.170.12.154` (and `player` user for SHiNE operations)
|
||||
- Role: additional TURN node for SHiNE calls
|
||||
- TURN setup:
|
||||
- `coturn` installed and active
|
||||
- `listening-port=3478`
|
||||
- `tls-listening-port=5349`
|
||||
- `use-auth-secret` + shared `static-auth-secret`
|
||||
- relay UDP port range: `49152-50152`
|
||||
- Runtime files:
|
||||
- `/etc/turnserver.conf`
|
||||
- `/home/player/SHiNE/coturn/turnserver.conf`
|
||||
- Cleanup done:
|
||||
- Disabled old reverse SSH tunnel (`reverse-ssh.service`) that exposed `0.0.0.0:1200 -> localhost:22` to `194.87.0.247`.
|
||||
|
||||
## Next Migration Steps (recommended)
|
||||
1. Install and configure runtime dependencies (JDK, Caddy, DB, TURN if required).
|
||||
2. Mirror SHiNE deployment process from VPS-02 using existing Gradle deployment flow.
|
||||
3. Move traffic gradually and validate logs/metrics before final cutover.
|
||||
@ -1,140 +0,0 @@
|
||||
# API для разработчиков: Общий формат запросов и ответов
|
||||
|
||||
Этот файл описывает не конкретные операции, а общий wire-контракт всего API сервера.
|
||||
|
||||
Здесь зафиксировано:
|
||||
|
||||
- как выглядит любой запрос;
|
||||
- как выглядит любой успешный ответ;
|
||||
- как выглядит любой ответ с ошибкой;
|
||||
- какие поля являются обязательными для всех операций;
|
||||
- как клиент должен интерпретировать `status`, `ok` и `payload`.
|
||||
|
||||
Логика простая: сначала клиент и сервер договариваются о едином формате конверта, и только потом в остальных документах уже описываются конкретные методы и их поля.
|
||||
|
||||
## 1. Общий формат запроса
|
||||
|
||||
Все запросы по WebSocket используют один и тот же JSON-конверт:
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "OperationName",
|
||||
"requestId": "req-001",
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Поля
|
||||
|
||||
- `op` — имя операции.
|
||||
- `requestId` — клиентский идентификатор запроса.
|
||||
- `payload` — объект параметров операции.
|
||||
|
||||
---
|
||||
|
||||
## 2. Общий формат успешного ответа
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "OperationName",
|
||||
"requestId": "req-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Общий формат ответа с ошибкой
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "OperationName",
|
||||
"requestId": "req-001",
|
||||
"status": 400,
|
||||
"ok": false,
|
||||
"error": "BAD_REQUEST",
|
||||
"message": "Human readable description",
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Обязательные правила
|
||||
|
||||
- Сервер возвращает `op` в каждом ответе.
|
||||
- Сервер возвращает `requestId` в каждом ответе без изменений.
|
||||
- Сервер возвращает `status` в каждом ответе.
|
||||
- Сервер возвращает `ok` в каждом ответе.
|
||||
- Сервер всегда возвращает `payload` как объект.
|
||||
- Даже при отсутствии данных сервер возвращает `payload: {}`.
|
||||
- `ok` находится на верхнем уровне ответа, а не внутри `payload`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Правило интерпретации
|
||||
|
||||
Источник истины — `status`.
|
||||
|
||||
- если `status` в диапазоне `200..299`, то ответ успешный и `ok` должен быть `true`;
|
||||
- если `status` вне диапазона `200..299`, то ответ ошибочный и `ok` должен быть `false`.
|
||||
|
||||
Запрещённые состояния:
|
||||
|
||||
- `status = 200` и `ok = false`;
|
||||
- `status = 400` и `ok = true`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Общие правила формата
|
||||
|
||||
- Все строки подписи и challenge собираются в UTF-8.
|
||||
- Временные метки передаются как Unix time в миллисекундах.
|
||||
- Бинарные поля передаются строками Base64.
|
||||
- При ошибке `error` — это машинный код причины.
|
||||
- При ошибке `message` — человекочитаемое описание причины.
|
||||
|
||||
---
|
||||
|
||||
## 7. Общие коды ошибок
|
||||
|
||||
Ниже перечислены коды ошибок, которые не привязаны к одной конкретной операции и могут встречаться в разных местах API.
|
||||
|
||||
- `400 / EMPTY_JSON` — клиент отправил пустое или полностью отсутствующее JSON-сообщение.
|
||||
- `400 / NO_OP` — в корневом объекте не передано поле `op`.
|
||||
- `400 / UNKNOWN_OP` — сервер не знает такую операцию.
|
||||
- `400 / NO_PAYLOAD` — в корневом объекте отсутствует `payload`.
|
||||
- `400 / BAD_PAYLOAD` — `payload` передан, но это не JSON-объект.
|
||||
- `400 / BAD_REQUEST_FORMAT` — JSON-конверт формально валиден, но поля операции не удалось распарсить в ожидаемый формат.
|
||||
- `500 / INTERNAL_HANDLER_ERROR` — в handler конкретной операции случилась непредвиденная серверная ошибка.
|
||||
- `500 / INTERNAL_ERROR` — произошла внутренняя ошибка на уровне общего JSON-процессора или другого серверного слоя.
|
||||
|
||||
Общее правило для dev/test этапа:
|
||||
|
||||
- `message` в таких ошибках должен быть коротким, но полезным;
|
||||
- по возможности сервер добавляет тип исключения и краткую деталь причины;
|
||||
- это сделано для упрощения интеграционных тестов и отладки;
|
||||
- позже для production этот уровень детализации может быть уменьшен.
|
||||
|
||||
---
|
||||
|
||||
## 8. Источник истины по списку операций
|
||||
|
||||
Фактический список публичных WebSocket-операций берётся из:
|
||||
|
||||
- `shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java`.
|
||||
|
||||
Если операция зарегистрирована в `HANDLERS` и `REQUEST_TYPES`, она считается доступной через JSON/WebSocket API. Общий актуальный индекс таких операций поддерживается в `Dev_Docs/API/09_Operations_Index.md`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Короткое резюме
|
||||
|
||||
- Запросы всегда идут как `op + requestId + payload`.
|
||||
- Ответы всегда идут как `op + requestId + status + ok + payload`.
|
||||
- Ошибки всегда возвращают `ok: false`, `error`, `message`, `payload: {}`.
|
||||
@ -1,200 +0,0 @@
|
||||
# API для разработчиков: Регистрация пользователя
|
||||
|
||||
Этот файл описывает раздел API, связанный с проверкой наличия пользователя на сервере и dev/test операциями.
|
||||
|
||||
Сейчас здесь три метода:
|
||||
|
||||
- `AddUser` — операция отключена (регистрация только через Solana);
|
||||
- `GetUser` — временная серверная проверка существования пользователя и чтение его базовых данных;
|
||||
- `SearchUsers` — dev/test поиск логинов по префиксу.
|
||||
|
||||
Регистрация выполняется через Solana (`shine_users`). Сервер при входе может лениво импортировать пользователя из Solana PDA в локальную БД, если записи ещё нет.
|
||||
|
||||
## Статус документа
|
||||
|
||||
Это временная глава API.
|
||||
|
||||
Текущая регистрация пользователя и текущая проверка, существует пользователь или нет, пока реализованы как серверные dev/test операции. В будущем и регистрация, и проверка identity должны идти напрямую через Solana.
|
||||
|
||||
---
|
||||
|
||||
## 1. Операция `AddUser`
|
||||
|
||||
### Назначение
|
||||
|
||||
Операция отключена. Используется только как явный ответ клиентам старых версий.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "AddUser",
|
||||
"requestId": "reg-001",
|
||||
"payload": {
|
||||
"login": "anya",
|
||||
"blockchainName": "anya-001",
|
||||
"solanaKey": "BASE64_32_PUBLIC_KEY",
|
||||
"blockchainKey": "BASE64_32_PUBLIC_KEY",
|
||||
"deviceKey": "BASE64_32_PUBLIC_KEY",
|
||||
"bchLimit": 1000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Пример ответа
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "AddUser",
|
||||
"requestId": "reg-001",
|
||||
"status": 410,
|
||||
"ok": false,
|
||||
"error": "ADD_USER_DISABLED",
|
||||
"message": "Серверная регистрация AddUser отключена. Используйте регистрацию через Solana.",
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Специфические коды ошибок `AddUser`
|
||||
|
||||
- `410 / ADD_USER_DISABLED` — серверная регистрация отключена, используйте Solana-first flow.
|
||||
|
||||
---
|
||||
|
||||
## 2. Операция `GetUser`
|
||||
|
||||
### Назначение
|
||||
|
||||
Временная серверная проверка, существует пользователь или нет.
|
||||
|
||||
Важно:
|
||||
|
||||
- это server-side existence-check;
|
||||
- если пользователя нет в локальной БД, он может быть импортирован при авторизации из Solana PDA.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "GetUser",
|
||||
"requestId": "user-001",
|
||||
"payload": {
|
||||
"login": "anya"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ: пользователь существует
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "GetUser",
|
||||
"requestId": "user-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"exists": true,
|
||||
"login": "Anya",
|
||||
"blockchainName": "anya-001",
|
||||
"solanaKey": "BASE64_32_PUBLIC_KEY",
|
||||
"blockchainKey": "BASE64_32_PUBLIC_KEY",
|
||||
"deviceKey": "BASE64_32_PUBLIC_KEY",
|
||||
"serverLastGlobalNumber": 128,
|
||||
"serverLastGlobalHash": "4f...ab",
|
||||
"serverBlockchainSizeBytes": 45212,
|
||||
"serverBlockchainSizeLimitBytes": 100000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Дополнительные серверные поля в `GetUser`:
|
||||
|
||||
- `serverLastGlobalNumber` — номер последнего блока в пользовательском блокчейне на сервере;
|
||||
- `serverLastGlobalHash` — hash последнего блока (hex-строка 64 символа);
|
||||
- `serverBlockchainSizeBytes` — текущий размер пользовательского блокчейна на сервере в байтах;
|
||||
- `serverBlockchainSizeLimitBytes` — текущий лимит размера блокчейна на сервере в байтах;
|
||||
|
||||
### Успешный ответ: пользователя нет
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "GetUser",
|
||||
"requestId": "user-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"exists": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Пример ошибки
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "GetUser",
|
||||
"requestId": "user-001",
|
||||
"status": 400,
|
||||
"ok": false,
|
||||
"error": "BAD_FIELDS",
|
||||
"message": "Некорректные поля: login",
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Специфические коды ошибок `GetUser`
|
||||
|
||||
- `400 / BAD_FIELDS` — не передан или пуст `login`.
|
||||
- `501 / DB_ERROR` — ошибка БД при поиске пользователя.
|
||||
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
||||
|
||||
---
|
||||
|
||||
## 3. Операция `SearchUsers`
|
||||
|
||||
### Назначение
|
||||
|
||||
Поиск пользователей по префиксу логина. Операция зарегистрирована в серверном API и используется как вспомогательная dev/test операция.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SearchUsers",
|
||||
"requestId": "search-001",
|
||||
"payload": {
|
||||
"prefix": "an"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SearchUsers",
|
||||
"requestId": "search-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"logins": ["anya", "andrey"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Специфические коды ошибок `SearchUsers`
|
||||
|
||||
- `400 / BAD_FIELDS` — некорректный или пустой `prefix`.
|
||||
- `501 / DB_ERROR` — ошибка БД при поиске.
|
||||
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
||||
|
||||
---
|
||||
|
||||
## 4. Короткое резюме
|
||||
|
||||
- `AddUser` — отключен (`410 / ADD_USER_DISABLED`).
|
||||
- `GetUser` — проверка существования пользователя на сервере.
|
||||
- `SearchUsers` — временный поиск пользователей по префиксу.
|
||||
- Регистрация выполняется только через Solana.
|
||||
@ -1,280 +0,0 @@
|
||||
# API для разработчиков: Авторизация
|
||||
|
||||
Этот файл описывает именно этапы авторизации клиента, то есть как создать новую сессию и как войти в уже существующую.
|
||||
|
||||
Здесь четыре метода:
|
||||
|
||||
- `AuthChallenge`
|
||||
- `CreateAuthSession`
|
||||
- `SessionChallenge`
|
||||
- `SessionLogin`
|
||||
|
||||
Логика раздела такая:
|
||||
|
||||
- сначала клиент либо начинает создание новой сессии через `deviceKey`;
|
||||
- либо начинает вход в уже созданную сессию через `sessionKey`;
|
||||
- сервер на первом шаге выдаёт challenge/nonce;
|
||||
- на втором шаге клиент присылает подписанный ответ;
|
||||
- сервер сверяет актуальные публичные ключи и только потом проверяет подпись.
|
||||
|
||||
Ниже в документе сначала описан сценарий, а потом зафиксированы точные форматы запросов и ответов.
|
||||
|
||||
## 1. Поток авторизации
|
||||
|
||||
Поддерживаются два сценария:
|
||||
|
||||
1. Создание новой сессии:
|
||||
`AuthChallenge` -> `CreateAuthSession`
|
||||
2. Вход в существующую сессию:
|
||||
`SessionChallenge` -> `SessionLogin`
|
||||
|
||||
`deviceKey` используется для создания новой сессии.
|
||||
|
||||
`sessionKey` используется для входа в уже созданную сессию.
|
||||
|
||||
`sessionKey` передаётся и хранится целиком одной строкой, например:
|
||||
|
||||
```text
|
||||
ed25519/BASE64_PUBLIC_KEY
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. `AuthChallenge`
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "AuthChallenge",
|
||||
"requestId": "auth-001",
|
||||
"payload": {
|
||||
"login": "alice"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "AuthChallenge",
|
||||
"requestId": "auth-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"authNonce": "8f2f0f71-0b1c-4ab2-8f5d-0bc5d6f6aa11"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Специфические коды ошибок `AuthChallenge`
|
||||
|
||||
- `400 / EMPTY_LOGIN` — пустой `login`.
|
||||
- `400 / ALREADY_AUTHED` — по текущему соединению уже выполнена авторизация.
|
||||
- `422 / UNKNOWN_USER` — пользователь с таким `login` не найден.
|
||||
- `501 / SOLANA_IMPORT_FAILED` — сервер не смог проверить/импортировать пользователя из Solana при lazy-import.
|
||||
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера, если появится вне штатного сценария.
|
||||
|
||||
---
|
||||
|
||||
## 3. `CreateAuthSession`
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "CreateAuthSession",
|
||||
"requestId": "create-001",
|
||||
"payload": {
|
||||
"login": "alice",
|
||||
"sessionKey": "ed25519/BASE64_PUBLIC_KEY",
|
||||
"storagePwd": "BASE64_OR_APP_SPECIFIC_SECRET",
|
||||
"timeMs": 1774600000123,
|
||||
"authNonce": "nonce",
|
||||
"deviceKey": "BASE64_DEVICE_PUBLIC_KEY",
|
||||
"signatureB64": "BASE64_SIGNATURE",
|
||||
"clientInfo": "Android 15; Pixel 9"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Строка для подписи
|
||||
|
||||
```text
|
||||
AUTH_CREATE_SESSION:{login}:{sessionKey}:{storagePwd}:{timeMs}:{authNonce}
|
||||
```
|
||||
|
||||
### Дополнительная проверка ключа
|
||||
|
||||
Перед проверкой подписи сервер должен:
|
||||
|
||||
1. взять актуальный `solana_users.device_key`;
|
||||
2. сравнить его с `payload.deviceKey`;
|
||||
3. только потом проверять подпись.
|
||||
|
||||
Если ключ не совпадает, сервер возвращает ошибку `DEVICE_KEY_NOT_ACTUAL`.
|
||||
|
||||
На будущее:
|
||||
|
||||
- для ротации `device_key` желательно добавить перепроверку через Solana.
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "CreateAuthSession",
|
||||
"requestId": "create-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"sessionId": "sess_7c5e5c4b"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Специфические коды ошибок `CreateAuthSession`
|
||||
|
||||
- `400 / NO_STEP1_CONTEXT` — для данного соединения не был корректно выполнен `AuthChallenge`.
|
||||
- `400 / EMPTY_LOGIN` — пустой `login`.
|
||||
- `400 / LOGIN_MISMATCH` — `login` не совпадает с тем, для кого был выдан `authNonce`.
|
||||
- `501 / DB_ERROR_USER_LOOKUP` — ошибка БД при повторном чтении пользователя.
|
||||
- `422 / USER_NOT_FOUND` — пользователь не найден.
|
||||
- `501 / NO_LOGIN` — у пользователя на сервере не заполнен `login`.
|
||||
- `400 / EMPTY_STORAGE_PWD` — пустой `storagePwd`.
|
||||
- `400 / EMPTY_SESSION_KEY` — пустой `sessionKey`.
|
||||
- `422 / UNSUPPORTED_KEY_ALGORITHM` — префикс алгоритма в `sessionKey` или `deviceKey` не поддерживается текущим сервером.
|
||||
- `400 / BAD_BASE64` — неверный Base64 в `sessionKey`, `deviceKey` или `signatureB64`.
|
||||
- `400 / EMPTY_SIGNATURE` — пустая подпись.
|
||||
- `400 / TIME_SKEW` — время клиента отличается от серверного больше допустимого окна.
|
||||
- `400 / NO_DEVICE_KEY` — у пользователя в БД отсутствует `deviceKey`.
|
||||
- `400 / EMPTY_AUTH_NONCE` — пустой `authNonce`.
|
||||
- `400 / AUTH_NONCE_MISMATCH` — `authNonce` не соответствует значению из `AuthChallenge`.
|
||||
- `400 / EMPTY_DEVICE_KEY` — в запросе не передан `deviceKey`.
|
||||
- `422 / DEVICE_KEY_NOT_ACTUAL` — `deviceKey` не совпадает с актуальной версией на сервере.
|
||||
- `422 / BAD_SIGNATURE` — подпись не прошла проверку.
|
||||
- `501 / DB_ERROR_SESSION_CREATE` — ошибка БД при создании записи активной сессии.
|
||||
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
||||
|
||||
---
|
||||
|
||||
## 4. `SessionChallenge`
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SessionChallenge",
|
||||
"requestId": "sch-001",
|
||||
"payload": {
|
||||
"sessionId": "sess_7c5e5c4b"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SessionChallenge",
|
||||
"requestId": "sch-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"nonce": "0e5bb0f4-c7d8-4efb-b44d-bf31a6126c66"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Специфические коды ошибок `SessionChallenge`
|
||||
|
||||
- `400 / EMPTY_SESSION_ID` — пустой `sessionId`.
|
||||
- `501 / DB_ERROR` — ошибка БД при чтении сессии.
|
||||
- `422 / SESSION_NOT_FOUND` — сессия не найдена.
|
||||
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
||||
|
||||
---
|
||||
|
||||
## 5. `SessionLogin`
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SessionLogin",
|
||||
"requestId": "slogin-001",
|
||||
"payload": {
|
||||
"sessionId": "sess_7c5e5c4b",
|
||||
"sessionKey": "ed25519/BASE64_PUBLIC_KEY",
|
||||
"timeMs": 1774600010456,
|
||||
"signatureB64": "BASE64_SIGNATURE",
|
||||
"clientInfo": "Android 15; Pixel 9"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Строка для подписи
|
||||
|
||||
```text
|
||||
SESSION_LOGIN:{sessionId}:{timeMs}:{nonce}
|
||||
```
|
||||
|
||||
### Дополнительная проверка ключа
|
||||
|
||||
Перед проверкой подписи сервер должен:
|
||||
|
||||
1. взять `active_sessions.session_key`;
|
||||
2. сравнить его с `payload.sessionKey`;
|
||||
3. только потом проверять подпись.
|
||||
|
||||
Если ключ не совпадает, сервер возвращает ошибку `SESSION_KEY_NOT_ACTUAL`.
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SessionLogin",
|
||||
"requestId": "slogin-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"storagePwd": "BASE64_OR_APP_SPECIFIC_SECRET"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Специфические коды ошибок `SessionLogin`
|
||||
|
||||
- `400 / EMPTY_SESSION_ID` — пустой `sessionId`.
|
||||
- `400 / NO_CHALLENGE` — перед `SessionLogin` не был успешно выполнен `SessionChallenge` либо nonce уже истёк.
|
||||
- `400 / SESSION_ID_MISMATCH` — nonce был выдан для другого `sessionId`.
|
||||
- `400 / TIME_SKEW` — время клиента отличается от серверного больше допустимого окна.
|
||||
- `400 / EMPTY_SIGNATURE` — пустая подпись.
|
||||
- `400 / EMPTY_SESSION_KEY` — пустой `sessionKey`.
|
||||
- `501 / DB_ERROR` — ошибка БД при чтении сессии.
|
||||
- `422 / SESSION_NOT_FOUND` — сессия не найдена.
|
||||
- `501 / NO_SESSION_KEY` — у сессии отсутствует `session_key`.
|
||||
- `422 / SESSION_KEY_NOT_ACTUAL` — переданный `sessionKey` не совпадает с актуальной версией на сервере.
|
||||
- `422 / UNSUPPORTED_KEY_ALGORITHM` — префикс алгоритма в `sessionKey` не поддерживается текущим сервером.
|
||||
- `400 / BAD_BASE64` — неверный Base64 в `sessionKey` или `signatureB64`.
|
||||
- `422 / BAD_SIGNATURE` — подпись не прошла проверку.
|
||||
- `501 / DB_ERROR_USER_LOOKUP` — ошибка БД при чтении пользователя для этой сессии.
|
||||
- `422 / USER_NOT_FOUND_FOR_SESSION` — пользователь, которому принадлежит сессия, не найден.
|
||||
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
||||
|
||||
---
|
||||
|
||||
## 6. Пример ошибки
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SessionLogin",
|
||||
"requestId": "slogin-001",
|
||||
"status": 403,
|
||||
"ok": false,
|
||||
"error": "SESSION_KEY_NOT_ACTUAL",
|
||||
"message": "session_key не соответствует актуальной версии",
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -1,136 +0,0 @@
|
||||
# API для разработчиков: Управление сессиями
|
||||
|
||||
Этот файл описывает методы, которые используются уже после успешной авторизации пользователя в сессию.
|
||||
|
||||
Здесь два метода:
|
||||
|
||||
- `ListSessions` — получить список активных сессий пользователя;
|
||||
- `CloseActiveSession` — закрыть одну из активных сессий.
|
||||
|
||||
Логика раздела такая:
|
||||
|
||||
- сначала пользователь проходит `SessionLogin`;
|
||||
- после этого сервер считает соединение авторизованным;
|
||||
- уже в этом состоянии клиент может читать список сессий и управлять ими.
|
||||
|
||||
То есть это не этап создания или входа в сессию, а этап последующего контроля уже существующих активных сессий.
|
||||
|
||||
## 1. `ListSessions`
|
||||
|
||||
Доступно только после успешного `SessionLogin`.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "ListSessions",
|
||||
"requestId": "list-001",
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "ListSessions",
|
||||
"requestId": "list-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"sessions": [
|
||||
{
|
||||
"sessionId": "sess_7c5e5c4b",
|
||||
"clientInfoFromClient": "Android 15; Pixel 9",
|
||||
"clientInfoFromRequest": "UA=Java-http-client/17.0.18; remote=127.0.0.1",
|
||||
"geo": "RU/Moscow",
|
||||
"lastAuthenticatedAtMs": 1774600010500
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Специфические коды ошибок `ListSessions`
|
||||
|
||||
- `422 / NOT_AUTHENTICATED` — запрос доступен только после успешного `SessionLogin`.
|
||||
- `501 / DB_ERROR_LIST_SESSIONS` — ошибка БД при чтении списка активных сессий.
|
||||
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
||||
|
||||
---
|
||||
|
||||
## 2. `CloseActiveSession`
|
||||
|
||||
Доступно только после успешного `SessionLogin`.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "CloseActiveSession",
|
||||
"requestId": "close-001",
|
||||
"payload": {
|
||||
"sessionId": "sess_7c5e5c4b"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "CloseActiveSession",
|
||||
"requestId": "close-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Специфические коды ошибок `CloseActiveSession`
|
||||
|
||||
- `422 / NOT_AUTHENTICATED` — запрос доступен только после успешного `SessionLogin`.
|
||||
- `400 / NO_SESSION_TO_CLOSE` — сервер не смог определить, какую сессию нужно закрыть.
|
||||
- `501 / DB_ERROR` — ошибка БД при поиске сессии или её удалении.
|
||||
- `422 / SESSION_NOT_FOUND` — целевая сессия не найдена.
|
||||
- `422 / SESSION_OF_ANOTHER_USER` — нельзя закрывать сессию другого пользователя.
|
||||
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
||||
|
||||
---
|
||||
|
||||
## 3. Пример ошибки
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "CloseActiveSession",
|
||||
"requestId": "close-001",
|
||||
"status": 403,
|
||||
"ok": false,
|
||||
"error": "NOT_AUTHENTICATED",
|
||||
"message": "Операция доступна только для авторизованных пользователей",
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 4. Формат `sessionId`
|
||||
|
||||
Текущее серверное значение `sessionId` генерируется как:
|
||||
|
||||
- случайные **32 байта** (`SecureRandom`),
|
||||
- кодирование в **стандартный Base64 RFC 4648** (алфавит `A-Z a-z 0-9 + /`),
|
||||
- **без padding** `=`.
|
||||
|
||||
Практически это строка длиной около **43 символов** (для 32 байт без `=`).
|
||||
|
||||
Пример реального формата:
|
||||
|
||||
```
|
||||
K9v3nQ4u8jYk0a2p7cD4mLx1zR0sT5wV6bN8eH3fQ1M
|
||||
```
|
||||
|
||||
Важно: это **не человеко-читаемое имя**, а непрозрачный идентификатор.
|
||||
Нужно передавать его как есть, без нормализации регистра и без URL-экранирования внутри JSON.
|
||||
@ -1,176 +0,0 @@
|
||||
# API для разработчиков: 04 — Добавление блока в блокчейн (AddBlock)
|
||||
|
||||
Документ описывает **текущий рабочий формат** сетевого вызова `AddBlock`, который используется для записи **любого** блока в блокчейн пользователя.
|
||||
|
||||
> Важный принцип: на уровне JSON API сейчас есть **один универсальный метод** записи — `AddBlock`.
|
||||
> Конкретный смысл записи задаётся типом самого бинарного блока (`type/subType/version` в заголовке блока).
|
||||
|
||||
## 1. Что делает `AddBlock`
|
||||
|
||||
`AddBlock`:
|
||||
- принимает имя блокчейна и base64 бинарного блока;
|
||||
- проверяет непрерывность цепочки (`blockNumber`, `prevHash`);
|
||||
- проверяет формат и подпись Ed25519;
|
||||
- валидирует `body` по правилам типа блока;
|
||||
- сохраняет блок и обновляет состояние цепочки.
|
||||
|
||||
## 2. JSON формат запроса
|
||||
|
||||
`op = "AddBlock"`.
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "AddBlock",
|
||||
"requestId": "req-1001",
|
||||
"payload": {
|
||||
"blockchainName": "alice-001",
|
||||
"blockNumber": 12,
|
||||
"prevBlockHash": "ab12...ff",
|
||||
"blockBytesB64": "AAAB..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Поля `payload`:
|
||||
- `blockchainName` — обязательно, формат `login-NNN`.
|
||||
- `blockNumber` — обязательно (временное legacy-поле для совместимости; должно совпасть с номером внутри бинарного блока).
|
||||
- `prevBlockHash` — legacy-поле, сейчас сервер использует `prevHash` из бинарного блока и состояние цепочки.
|
||||
- `blockBytesB64` — обязательно: **полный бинарный блок** (`preimage + sigMarker + signature`) в Base64.
|
||||
|
||||
## 3. Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "AddBlock",
|
||||
"requestId": "req-1001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"reasonCode": null,
|
||||
"serverLastGlobalNumber": 12,
|
||||
"serverLastGlobalHash": "9f0e...a1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Ошибка (единый формат)
|
||||
|
||||
При ошибках сервер отдаёт `Net_Exception_Response` со стандартными полями и дополнительно с состоянием сервера для ресинка:
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "AddBlock",
|
||||
"requestId": "req-1001",
|
||||
"status": 400,
|
||||
"ok": false,
|
||||
"error": "bad_prev_hash",
|
||||
"message": "Некорректный prevHash (цепочка не совпадает)",
|
||||
"payload": {
|
||||
"serverLastGlobalNumber": 11,
|
||||
"serverLastGlobalHash": "c3d4...98"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Основные `reasonCode`
|
||||
|
||||
- `empty_blockchain_name`, `bad_blockchain_name`
|
||||
- `blockchain_state_not_found`
|
||||
- `bad_block_base64`, `bad_block_format`, `bad_block_body`
|
||||
- `bad_block_number`, `req_global_mismatch`, `bad_prev_hash`
|
||||
- `bad_signature`, `signature_verify_failed`
|
||||
- `prev_line_block_not_found`, `bad_prev_line_hash`
|
||||
- `limit_exceeded`
|
||||
- `repost_disabled` — репосты временно отключены до будущей реализации
|
||||
- `internal_error`
|
||||
|
||||
## 5. Какие блоки реально можно добавлять через `AddBlock`
|
||||
|
||||
Через `AddBlock` можно писать поддержанные форматы, кроме явно отключённых временных фич:
|
||||
|
||||
1. **TECH (type=0)**
|
||||
- `HEADER_COMPAT (subType=0)`
|
||||
- `TECH_CREATE_CHANNEL (subType=1)`
|
||||
|
||||
2. **TEXT (type=1)**
|
||||
- `TEXT_POST (10)`
|
||||
- `TEXT_EDIT_POST (11)`
|
||||
- `TEXT_REPLY (20)`
|
||||
- `TEXT_EDIT_REPLY (21)`
|
||||
- `TEXT_REPOST (30)` — формат зарезервирован, но новые блоки временно отклоняются с `repost_disabled`
|
||||
|
||||
3. **REACTION (type=2)**
|
||||
- `REACTION_LIKE (1)`
|
||||
|
||||
4. **CONNECTION (type=3)**
|
||||
- `CONNECTION_FRIEND (10)`
|
||||
- `CONNECTION_UNFRIEND (11)`
|
||||
- `CONNECTION_CONTACT (20)`
|
||||
- `CONNECTION_UNCONTACT (21)`
|
||||
- `CONNECTION_FOLLOW (30)`
|
||||
- `CONNECTION_UNFOLLOW (31)`
|
||||
- `CONNECTION_SPOUSE (40)`
|
||||
- `CONNECTION_UNSPOUSE (41)`
|
||||
- `CONNECTION_PARENT (50)`
|
||||
- `CONNECTION_UNPARENT (51)`
|
||||
- `CONNECTION_CHILD (52)`
|
||||
- `CONNECTION_UNCHILD (53)`
|
||||
- `CONNECTION_SIBLING (54)`
|
||||
- `CONNECTION_UNSIBLING (55)`
|
||||
- `CONNECTION_KNOWN_PERSON (60)`
|
||||
- `CONNECTION_UNKNOWN_PERSON (61)`
|
||||
- `CONNECTION_SHINE_CONFIRMED (70)`
|
||||
- `CONNECTION_SHINE_UNCONFIRMED (71)`
|
||||
- `CONNECTION_SHINE_SEEN (74)`
|
||||
- `CONNECTION_SHINE_UNSEEN (75)`
|
||||
|
||||
5. **USER_PARAM (type=4)**
|
||||
- `USER_PARAM_TEXT_TEXT (1)`
|
||||
|
||||
## 6. Хватает ли функций сейчас
|
||||
|
||||
Коротко: **для записи событий в блокчейн — хватает**, для полноценного клиентского чтения — **пока не хватает**.
|
||||
|
||||
Что есть:
|
||||
- единый надёжный write-путь `AddBlock`;
|
||||
- есть `GetFriendsLists` и API по `UserParam`;
|
||||
- есть унифицированные коды ошибок и поля для ресинхронизации.
|
||||
|
||||
Что пока ограничивает продукт:
|
||||
- нет полноценного read API для каналов/постов/тредов;
|
||||
- нет API списка подписок с серверными счётчиками непрочитанного;
|
||||
- нет ленты событий (новые ответы/лайки/подписки) как отдельного RPC.
|
||||
|
||||
## 7. Рекомендации по клиенту при записи блоков
|
||||
|
||||
1. Перед отправкой держать локальный `lastNumber/lastHash`.
|
||||
2. При `bad_prev_hash` или `bad_block_number`:
|
||||
- взять `serverLastGlobalNumber/serverLastGlobalHash` из ошибки,
|
||||
- пересобрать следующий блок на актуальной вершине.
|
||||
3. Для edit-блоков всегда ссылаться на **оригинальный** блок, а не на предыдущий edit.
|
||||
4. Для связей/подписок использовать target на **root** (HEADER или CREATE_CHANNEL), а не на произвольный пост.
|
||||
|
||||
|
||||
## 8. USER_PARAM для «личных данных»
|
||||
|
||||
Да, на текущем API это можно добавить **без изменения серверного кода**:
|
||||
|
||||
- в `UserParam` поле `param` сейчас не ограничено фиксированным справочником;
|
||||
- сервер хранит пары `param -> value` как строки (при наличии корректной подписи и `time_ms`);
|
||||
- чтение уже есть через `GetUserParam` и `ListUserParams`.
|
||||
|
||||
Рекомендуемый стартовый набор ключей для профиля (MVP):
|
||||
|
||||
- `name`
|
||||
- `last_name`
|
||||
- `address_physical`
|
||||
- `address_web`
|
||||
- `phone`
|
||||
|
||||
Практическая рекомендация: заранее зафиксировать единый словарь ключей в клиенте/документации, чтобы избежать дублей вида `lastname` vs `last_name`, `site` vs `address_web` и т.д.
|
||||
|
||||
Ограничения, которые важно учесть:
|
||||
|
||||
- сейчас нет серверной ACL-политики чтения параметров (в MVP их может читать любой клиент, который знает `login`);
|
||||
- нет валидации формата значений для конкретных ключей (телефон, URL и т.д. проверяются только на стороне клиента);
|
||||
- нет отдельного индекса/поиска по этим полям — только точечное чтение и listing по `login`.
|
||||
@ -1,333 +0,0 @@
|
||||
# API для разработчиков: Технические запросы
|
||||
|
||||
Этот файл описывает технические WebSocket-запросы, которые нужны для служебной работы клиента с сервером. Часть операций доступна без авторизации, часть требует успешной авторизованной сессии.
|
||||
|
||||
Сейчас здесь шесть методов:
|
||||
|
||||
- `Ping` — keep-alive запрос для поддержания живого WebSocket-соединения;
|
||||
- `GetServerInfo` — запрос базовой публичной информации о сервере для выбора узла в децентрализованной сети;
|
||||
- `GetCallIceConfig` — выдача STUN/TURN конфигурации для звонков;
|
||||
- `ClientErrorLog` — отправка клиентской ошибки в серверный лог;
|
||||
- `ClientDebugLog` — отправка клиентского debug-события в серверный буфер;
|
||||
- `CallDeliveryReport` — диагностический отчёт клиента о доставке/установке звонка.
|
||||
|
||||
Логика раздела такая:
|
||||
|
||||
- `Ping` нужен для регулярной проверки, что соединение всё ещё живо;
|
||||
- `GetServerInfo` нужен до авторизации и до работы с данными, чтобы клиент понял, что сервер доступен, и показал пользователю краткую карточку этого узла.
|
||||
|
||||
Ниже сначала описаны назначение методов, затем точные форматы запросов и ответов.
|
||||
|
||||
## 1. `Ping`
|
||||
|
||||
### Назначение
|
||||
|
||||
Служебный keep-alive запрос.
|
||||
|
||||
Клиент может отправлять его периодически, чтобы:
|
||||
|
||||
- поддерживать активное WebSocket-соединение;
|
||||
- понимать, что сервер отвечает;
|
||||
- при необходимости получать текущее серверное время.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "Ping",
|
||||
"requestId": "ping-001",
|
||||
"payload": {
|
||||
"ts": 1774700000123
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Поле `ts` в запросе необязательно для логики сервера. Сервер его не валидирует и не использует для принятия решения.
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "Ping",
|
||||
"requestId": "ping-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"ts": 1774700000456
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Специфические коды ошибок `Ping`
|
||||
|
||||
- У `Ping` нет специальных прикладных ошибок.
|
||||
- Если произойдёт непредвиденная проблема, сервер вернёт общую ошибку из раздела `00`, обычно `500 / INTERNAL_ERROR`.
|
||||
|
||||
---
|
||||
|
||||
## 2. `GetServerInfo`
|
||||
|
||||
### Назначение
|
||||
|
||||
Запрос публичной информации о сервере.
|
||||
|
||||
Он нужен клиенту для выбора сервера в децентрализованной сети. По этому запросу клиент может:
|
||||
|
||||
- проверить, что сервер вообще доступен;
|
||||
- показать URL и версию сервера;
|
||||
- показать физический регион или адрес размещения;
|
||||
- показать описание сервера;
|
||||
- показать поле `origin` как комментарий о природе этого узла;
|
||||
- показать дополнительную текстовую информацию.
|
||||
|
||||
Этот запрос доступен без авторизации.
|
||||
|
||||
### Источник данных
|
||||
|
||||
- `version` берётся из Gradle build и подставляется в `application.properties`;
|
||||
- остальные поля читаются из настроек сервера;
|
||||
- если значение в конфиге не задано, сервер возвращает пустую строку.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "GetServerInfo",
|
||||
"requestId": "srv-001",
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "GetServerInfo",
|
||||
"requestId": "srv-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"url": "wss://node.example.org/ws",
|
||||
"version": "1.0",
|
||||
"physicalRegion": "Грузия, Тбилиси",
|
||||
"description": "Public community SHiNE node",
|
||||
"origin": "Community-operated node",
|
||||
"extraInfo": "IPv4 + IPv6; test federation enabled"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Поля ответа
|
||||
|
||||
- `url` — публичный URL сервера.
|
||||
- `version` — версия сервера из Gradle build.
|
||||
- `physicalRegion` — физический регион или адрес размещения сервера.
|
||||
- `description` — человекочитаемое описание сервера.
|
||||
- `origin` — комментарий о том, какой это сервер.
|
||||
- `extraInfo` — любая дополнительная информация о сервере.
|
||||
|
||||
### Специфические коды ошибок `GetServerInfo`
|
||||
|
||||
- У `GetServerInfo` нет специальных прикладных ошибок при штатной работе.
|
||||
- Если произойдёт непредвиденная проблема, сервер вернёт общую ошибку из раздела `00`, обычно `500 / INTERNAL_ERROR`.
|
||||
|
||||
---
|
||||
|
||||
## 3. `GetCallIceConfig`
|
||||
|
||||
Доступно только после успешной авторизации.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "GetCallIceConfig",
|
||||
"requestId": "ice-001",
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "GetCallIceConfig",
|
||||
"requestId": "ice-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"stunUrls": ["stun:stun.example.org:3478"],
|
||||
"turnUrls": ["turn:turn.example.org:3478?transport=udp"],
|
||||
"turnUsername": "user",
|
||||
"turnPassword": "password",
|
||||
"turnServers": [
|
||||
{
|
||||
"id": "primary",
|
||||
"urls": ["turn:turn.example.org:3478?transport=udp"],
|
||||
"username": "user",
|
||||
"password": "password"
|
||||
}
|
||||
],
|
||||
"turnEnabled": true,
|
||||
"generatedAtMs": 1774700000123,
|
||||
"expiresAtMs": 1774700300123,
|
||||
"ttlSec": 300
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Специфические коды ошибок `GetCallIceConfig`
|
||||
|
||||
- `422 / NOT_AUTHENTICATED` — требуется авторизация.
|
||||
|
||||
---
|
||||
|
||||
## 4. `ClientErrorLog`
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "ClientErrorLog",
|
||||
"requestId": "err-001",
|
||||
"payload": {
|
||||
"kind": "global_error",
|
||||
"message": "TypeError: failed",
|
||||
"stack": "...",
|
||||
"sourceUrl": "https://shineup.me/app.js",
|
||||
"lineNumber": 10,
|
||||
"columnNumber": 20,
|
||||
"route": "#/channel-view/own-0",
|
||||
"href": "https://shineup.me/#/channel-view/own-0",
|
||||
"userAgent": "...",
|
||||
"clientTs": 1774700000123,
|
||||
"requestOp": "GetChannelMessages",
|
||||
"requestIdRef": "GetChannelMessages-123",
|
||||
"contextJson": "{\"screen\":\"channels\"}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "ClientErrorLog",
|
||||
"requestId": "err-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"serverTs": 1774700000456,
|
||||
"accepted": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Специфические коды ошибок `ClientErrorLog`
|
||||
|
||||
- `400 / BAD_FIELDS` — обязательные поля ошибки не заполнены.
|
||||
|
||||
---
|
||||
|
||||
## 5. `ClientDebugLog`
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "ClientDebugLog",
|
||||
"requestId": "dbg-001",
|
||||
"payload": {
|
||||
"runId": "ui-run-1",
|
||||
"level": "info",
|
||||
"message": "opened channels tab",
|
||||
"details": "{\"route\":\"#/channels\"}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "ClientDebugLog",
|
||||
"requestId": "dbg-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"accepted": true,
|
||||
"serverTs": 1774700000456
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Специфические коды ошибок `ClientDebugLog`
|
||||
|
||||
- `400 / BAD_FIELDS` — поле `message` не заполнено.
|
||||
|
||||
---
|
||||
|
||||
## 6. `CallDeliveryReport`
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "CallDeliveryReport",
|
||||
"requestId": "call-report-001",
|
||||
"payload": {
|
||||
"type": "outgoing_failed",
|
||||
"value": "{\"reason\":\"ice_failed\",\"callId\":\"call-1\"}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "CallDeliveryReport",
|
||||
"requestId": "call-report-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"serverTs": 1774700000456,
|
||||
"accepted": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Специфические коды ошибок `CallDeliveryReport`
|
||||
|
||||
- `400 / BAD_FIELDS` — поле `type` не заполнено.
|
||||
|
||||
---
|
||||
|
||||
## 7. Короткое резюме
|
||||
|
||||
- `Ping` нужен для keep-alive и проверки, что WebSocket-соединение живо.
|
||||
- `GetServerInfo` нужен для выбора сервера в сети и показа публичной информации об узле.
|
||||
- `GetCallIceConfig` нужен для WebRTC-звонков и требует авторизации.
|
||||
- `ClientErrorLog`, `ClientDebugLog`, `CallDeliveryReport` используются для диагностики клиента и звонков.
|
||||
|
||||
|
||||
## 8. Прямое техническое сообщение в конкретную сессию
|
||||
|
||||
На текущий момент в публичном JSON API этого документа **нет отдельного RPC** для отправки произвольного технического сообщения в конкретную сессию пользователя (по `sessionId`).
|
||||
|
||||
Что уже есть в системе:
|
||||
|
||||
- сервер хранит `sessionId` активной сессии;
|
||||
- есть `ListSessions`, чтобы клиент получил список sessionId своего пользователя;
|
||||
- у сервера есть внутренний реестр активных WS-подключений по `sessionId`.
|
||||
|
||||
Чего не хватает для полноценной фичи «direct tech message by sessionId»:
|
||||
|
||||
1. отдельная API-операция (например, `SendSessionTechMessage`);
|
||||
2. правило авторизации (кто имеет право писать в чужую/свою сессию);
|
||||
3. унифицированный формат payload и события доставки;
|
||||
4. коды ошибок (`SESSION_OFFLINE`, `SESSION_NOT_FOUND`, `FORBIDDEN` и т.п.).
|
||||
|
||||
Итог: как инфраструктурная база это почти готово, но нужен отдельный RPC-слой и политика доступа.
|
||||
@ -1,341 +0,0 @@
|
||||
# 06. Channels Read API
|
||||
|
||||
## Человеко-читаемое объяснение
|
||||
Эти функции — это **чтение данных каналов** для UI:
|
||||
|
||||
1. `ListSubscriptionsFeed` — отдает данные для экрана списка каналов:
|
||||
- ваши каналы (личный + созданные вами),
|
||||
- каналы пользователей, на кого вы подписаны,
|
||||
- отдельные каналы, на которые вы подписаны напрямую.
|
||||
|
||||
2. `GetChannelMessages` — отдает полную ленту одного канала (пока без курсоров, загружается сразу целиком),
|
||||
включая версии сообщений, лайки и ответы.
|
||||
|
||||
3. `GetMessageThread` — отдает дерево обсуждения вокруг конкретного сообщения:
|
||||
предки, фокус-сообщение, потомки.
|
||||
|
||||
4. `GetChannelsCounters` — отдает счетчики разделов каналов для пользователя.
|
||||
|
||||
5. `ListGroupChats200` — отдает список групповых чатов типа `200`.
|
||||
|
||||
6. `GetGroupDialog` — отдает сообщения конкретного группового чата типа `200`.
|
||||
|
||||
> На первом этапе мы **не используем курсоры** (`nextCursor`) и загружаем полные списки.
|
||||
|
||||
---
|
||||
|
||||
## 1) ListSubscriptionsFeed
|
||||
|
||||
### Request
|
||||
```json
|
||||
{
|
||||
"op": "ListSubscriptionsFeed",
|
||||
"requestId": "req-1",
|
||||
"payload": {
|
||||
"login": "Alice",
|
||||
"limit": 200
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response (success)
|
||||
```json
|
||||
{
|
||||
"op": "ListSubscriptionsFeed",
|
||||
"requestId": "req-1",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"login": "Alice",
|
||||
"ownedChannels": [
|
||||
{
|
||||
"channel": {
|
||||
"ownerLogin": "Alice",
|
||||
"ownerBlockchainName": "alice-001",
|
||||
"channelName": "0",
|
||||
"personal": true,
|
||||
"channelRoot": { "blockNumber": 0, "blockHash": "..." }
|
||||
},
|
||||
"messagesCount": 120,
|
||||
"lastMessage": {
|
||||
"messageRef": { "blockNumber": 921, "blockHash": "..." },
|
||||
"text": "последняя версия текста",
|
||||
"createdAtMs": 1760000000000,
|
||||
"authorLogin": "Alice",
|
||||
"authorBlockchainName": "alice-001"
|
||||
}
|
||||
}
|
||||
],
|
||||
"followedUsersChannels": [
|
||||
{
|
||||
"channel": {
|
||||
"ownerLogin": "Bob",
|
||||
"ownerBlockchainName": "bob-001",
|
||||
"channelName": "0",
|
||||
"personal": true,
|
||||
"channelRoot": { "blockNumber": 0, "blockHash": "..." }
|
||||
},
|
||||
"messagesCount": 540,
|
||||
"lastMessage": {
|
||||
"messageRef": { "blockNumber": 922, "blockHash": "..." },
|
||||
"text": "последняя версия текста",
|
||||
"createdAtMs": 1760000100000,
|
||||
"authorLogin": "Bob",
|
||||
"authorBlockchainName": "bob-001"
|
||||
}
|
||||
}
|
||||
],
|
||||
"followedChannels": [
|
||||
{
|
||||
"channel": {
|
||||
"ownerLogin": "Carl",
|
||||
"ownerBlockchainName": "carl-001",
|
||||
"channelName": "market",
|
||||
"personal": false,
|
||||
"channelRoot": { "blockNumber": 456, "blockHash": "..." }
|
||||
},
|
||||
"messagesCount": 90,
|
||||
"lastMessage": {
|
||||
"messageRef": { "blockNumber": 1002, "blockHash": "..." },
|
||||
"text": "актуальный текст",
|
||||
"createdAtMs": 1760001000000,
|
||||
"authorLogin": "Carl",
|
||||
"authorBlockchainName": "carl-001"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2) GetChannelMessages
|
||||
|
||||
### Request
|
||||
```json
|
||||
{
|
||||
"op": "GetChannelMessages",
|
||||
"requestId": "req-2",
|
||||
"payload": {
|
||||
"channel": {
|
||||
"ownerBlockchainName": "bob-001",
|
||||
"channelRootBlockNumber": 123,
|
||||
"channelRootBlockHash": "..."
|
||||
},
|
||||
"limit": 200,
|
||||
"sort": "asc"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response (success)
|
||||
```json
|
||||
{
|
||||
"op": "GetChannelMessages",
|
||||
"requestId": "req-2",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"channel": {
|
||||
"ownerLogin": "Bob",
|
||||
"ownerBlockchainName": "bob-001",
|
||||
"channelName": "news",
|
||||
"channelRoot": { "blockNumber": 123, "blockHash": "..." }
|
||||
},
|
||||
"messages": [
|
||||
{
|
||||
"messageRef": { "blockNumber": 140, "blockHash": "..." },
|
||||
"authorLogin": "Bob",
|
||||
"authorBlockchainName": "bob-001",
|
||||
"createdAtMs": 1760000000000,
|
||||
"text": "текущая версия",
|
||||
"likesCount": 12,
|
||||
"repliesCount": 3,
|
||||
"versionsTotal": 4,
|
||||
"versions": [
|
||||
{ "versionIndex": 1, "blockNumber": 140, "blockHash": "...", "text": "v1", "createdAtMs": 1760000000000 },
|
||||
{ "versionIndex": 2, "blockNumber": 155, "blockHash": "...", "text": "v2", "createdAtMs": 1760001000000 },
|
||||
{ "versionIndex": 3, "blockNumber": 170, "blockHash": "...", "text": "v3", "createdAtMs": 1760002000000 },
|
||||
{ "versionIndex": 4, "blockNumber": 199, "blockHash": "...", "text": "v4", "createdAtMs": 1760003000000 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3) GetMessageThread
|
||||
|
||||
### Request
|
||||
```json
|
||||
{
|
||||
"op": "GetMessageThread",
|
||||
"requestId": "req-3",
|
||||
"payload": {
|
||||
"message": {
|
||||
"blockchainName": "bob-001",
|
||||
"blockNumber": 333,
|
||||
"blockHash": "..."
|
||||
},
|
||||
"depthUp": 20,
|
||||
"depthDown": 2,
|
||||
"limitChildrenPerNode": 50
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response (success)
|
||||
```json
|
||||
{
|
||||
"op": "GetMessageThread",
|
||||
"requestId": "req-3",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"ancestors": [MessageNode],
|
||||
"focus": MessageNode,
|
||||
"descendants": [MessageNodeTree]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### MessageNode (дополнение)
|
||||
- `MessageNode` расширяет формат сообщения из `GetChannelMessages` и дополнительно содержит:
|
||||
- `channelInfo` — мета-информация о канале (если применимо);
|
||||
- `rawBlockB64` — сырой `block_bytes` текущего блока в Base64.
|
||||
- Поле `rawBlockB64` присутствует у узлов во всех частях ответа `GetMessageThread`: `focus`, `ancestors[]`, `descendants[]`.
|
||||
- В `GetChannelMessages` поле `rawBlockB64` **не добавляется** (лента канала без сырого блока, чтобы не раздувать ответ).
|
||||
|
||||
---
|
||||
|
||||
## 4) GetChannelsCounters
|
||||
|
||||
### Request
|
||||
```json
|
||||
{
|
||||
"op": "GetChannelsCounters",
|
||||
"requestId": "req-4",
|
||||
"payload": {
|
||||
"login": "Alice"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response (success)
|
||||
```json
|
||||
{
|
||||
"op": "GetChannelsCounters",
|
||||
"requestId": "req-4",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"login": "Alice",
|
||||
"feedCount": 12,
|
||||
"dialogs100Count": 3,
|
||||
"groupChats200Count": 4,
|
||||
"myChannelsCount": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5) ListGroupChats200
|
||||
|
||||
### Request
|
||||
```json
|
||||
{
|
||||
"op": "ListGroupChats200",
|
||||
"requestId": "req-5",
|
||||
"payload": {
|
||||
"login": "Alice"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response (success)
|
||||
```json
|
||||
{
|
||||
"op": "ListGroupChats200",
|
||||
"requestId": "req-5",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"login": "Alice",
|
||||
"chats": [
|
||||
{
|
||||
"ownerLogin": "Alice",
|
||||
"ownerBlockchainName": "alice-001",
|
||||
"channelRootBlockNumber": 123,
|
||||
"channelRootBlockHash": "...",
|
||||
"channelName": "team",
|
||||
"chatTitle": "Team chat",
|
||||
"membersCount": 3,
|
||||
"updatedAtMs": 1760000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6) GetGroupDialog
|
||||
|
||||
### Request
|
||||
```json
|
||||
{
|
||||
"op": "GetGroupDialog",
|
||||
"requestId": "req-6",
|
||||
"payload": {
|
||||
"login": "Alice",
|
||||
"group": {
|
||||
"ownerBlockchainName": "alice-001",
|
||||
"channelRootBlockNumber": 123
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response (success)
|
||||
```json
|
||||
{
|
||||
"op": "GetGroupDialog",
|
||||
"requestId": "req-6",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"group": {
|
||||
"ownerLogin": "Alice",
|
||||
"ownerBlockchainName": "alice-001",
|
||||
"channelRootBlockNumber": 123,
|
||||
"channelName": "team",
|
||||
"chatTitle": "Team chat"
|
||||
},
|
||||
"messages": [
|
||||
{
|
||||
"authorLogin": "Bob",
|
||||
"authorBlockchainName": "bob-001",
|
||||
"blockNumber": 140,
|
||||
"blockHash": "...",
|
||||
"createdAtMs": 1760000000000,
|
||||
"text": "Привет"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reason codes
|
||||
- `bad_fields`
|
||||
- `user_not_found`
|
||||
- `channel_not_found`
|
||||
- `message_not_found`
|
||||
- `limit_too_large`
|
||||
- `channel_name_already_exists`
|
||||
- `internal_error`
|
||||
@ -1,145 +0,0 @@
|
||||
# 07. Channels Feature Runbook (человеческое описание + диагностика)
|
||||
|
||||
## 1) Что уже сделано простыми словами
|
||||
|
||||
Сейчас реализован полный минимальный контур для каналов:
|
||||
|
||||
1. **Серверные read API**:
|
||||
- `ListSubscriptionsFeed` — экран списка каналов.
|
||||
- `GetChannelMessages` — сообщения конкретного канала.
|
||||
- `GetMessageThread` — дерево обсуждения для сообщения.
|
||||
|
||||
2. **UI вкладки Каналы**:
|
||||
- при открытии пытается загрузить реальный feed с сервера;
|
||||
- если сервер недоступен — fallback на мок-данные;
|
||||
- группы каналов выводятся в нужном порядке;
|
||||
- есть кнопка «Добавить канал», модалки подписки, переход в канал.
|
||||
|
||||
3. **Проверка уникальности имени канала на сервере**
|
||||
- в `AddBlock` при `CreateChannelBody` добавлена проверка;
|
||||
- при дубле возвращается `409 channel_name_already_exists`.
|
||||
|
||||
---
|
||||
|
||||
## 2) Что тестировать в первую очередь (быстрый чеклист)
|
||||
|
||||
### Базовый smoke
|
||||
1. Авторизоваться в UI.
|
||||
2. Открыть вкладку «Каналы».
|
||||
3. Убедиться, что данные загрузились с сервера (или виден fallback-баннер).
|
||||
4. Нажать любой канал — должен открыться экран канала с сообщениями.
|
||||
|
||||
### API smoke
|
||||
1. Вызвать `ListSubscriptionsFeed`.
|
||||
2. Для канала `ownedChannels[0]` вызвать `GetChannelMessages`.
|
||||
3. Для первого `messages[0]` вызвать `GetMessageThread`.
|
||||
|
||||
### Ошибки
|
||||
1. `ListSubscriptionsFeed` с пустым login -> `bad_fields`.
|
||||
2. `GetChannelMessages` с битым channel payload -> `bad_fields`.
|
||||
3. `GetMessageThread` с несуществующим block -> `message_not_found`.
|
||||
4. `AddBlock(CreateChannel)` с уже существующим именем -> `channel_name_already_exists`.
|
||||
|
||||
---
|
||||
|
||||
## 3) Готовые JSON-запросы для ручной диагностики
|
||||
|
||||
## 3.1 ListSubscriptionsFeed
|
||||
```json
|
||||
{
|
||||
"op": "ListSubscriptionsFeed",
|
||||
"requestId": "debug-feed-1",
|
||||
"payload": {
|
||||
"login": "TestUser1",
|
||||
"limit": 200
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3.2 GetChannelMessages
|
||||
```json
|
||||
{
|
||||
"op": "GetChannelMessages",
|
||||
"requestId": "debug-ch-1",
|
||||
"payload": {
|
||||
"channel": {
|
||||
"ownerBlockchainName": "TestUser1-001",
|
||||
"channelRootBlockNumber": 0,
|
||||
"channelRootBlockHash": ""
|
||||
},
|
||||
"limit": 200,
|
||||
"sort": "asc"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3.3 GetMessageThread
|
||||
```json
|
||||
{
|
||||
"op": "GetMessageThread",
|
||||
"requestId": "debug-thread-1",
|
||||
"payload": {
|
||||
"message": {
|
||||
"blockchainName": "TestUser1-001",
|
||||
"blockNumber": 123,
|
||||
"blockHash": "<hash-from-GetChannelMessages>"
|
||||
},
|
||||
"depthUp": 20,
|
||||
"depthDown": 2,
|
||||
"limitChildrenPerNode": 50
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4) Что смотреть в ответах
|
||||
|
||||
### ListSubscriptionsFeed
|
||||
- `payload.login` — канонический login.
|
||||
- `ownedChannels / followedUsersChannels / followedChannels` — массивы.
|
||||
- у каждой записи есть:
|
||||
- `channel.channelRoot.blockNumber`,
|
||||
- `messagesCount`,
|
||||
- `lastMessage` (может быть null, если сообщений нет).
|
||||
|
||||
### GetChannelMessages
|
||||
- `payload.channel` заполнен;
|
||||
- `payload.messages[]` содержит:
|
||||
- `likesCount`, `repliesCount`,
|
||||
- `versionsTotal`, `versions[]`,
|
||||
- `text` должен быть текущей (последней) версией.
|
||||
|
||||
### GetMessageThread
|
||||
- `payload.ancestors[]`, `payload.focus`, `payload.descendants[]`.
|
||||
- у узлов должны быть версии и счетчики.
|
||||
- у каждого узла дополнительно может приходить `rawBlockB64` (Base64 сырого `block_bytes`).
|
||||
|
||||
### Важно по совместимости
|
||||
- `rawBlockB64` добавлен только в `GetMessageThread`.
|
||||
- `GetChannelMessages` не содержит `rawBlockB64` (без изменений формата ленты).
|
||||
|
||||
---
|
||||
|
||||
## 5) Частые проблемы и как быстро локализовать
|
||||
|
||||
1. **`status != 200`, code=bad_fields**
|
||||
- проверить вложенность payload и обязательные поля.
|
||||
|
||||
2. **`message_not_found` в GetMessageThread**
|
||||
- обычно передали blockNumber/hash не из `messageRef`.
|
||||
|
||||
3. **Пустой список сообщений в GetChannelMessages**
|
||||
- проверить `ownerBlockchainName` и `channelRootBlockNumber`.
|
||||
|
||||
4. **`channel_name_already_exists` при AddBlock**
|
||||
- это ожидаемо: в этой цепочке уже есть канал с таким именем.
|
||||
|
||||
---
|
||||
|
||||
## 6) Для будущей доработки
|
||||
|
||||
1. Добавить курсоры (пагинацию) для больших каналов.
|
||||
2. Перевести «Подписаться»/«Добавить канал» в UI с демо-заглушек на реальные write RPC.
|
||||
3. Добавить batch-агрегации для thread/versions (оптимизация).
|
||||
4. Добавить полноценные интеграционные тесты на негативные кейсы и нагрузку.
|
||||
@ -1,162 +0,0 @@
|
||||
# MCP: чтение и дозапись персонального публичного чата (type=100)
|
||||
|
||||
Документ для реализации MCP-инструмента, который:
|
||||
- читает переписку между двумя логинами (`from`, `to`);
|
||||
- добавляет новое сообщение от отправителя через серверный `AddBlock`.
|
||||
|
||||
Важно: речь про **персональные публичные** каналы (`channelTypeCode=100`), а не приватные DM.
|
||||
|
||||
## 1. Базовые предпосылки
|
||||
|
||||
1. У каждого пользователя свой блокчейн (`<login>-001`).
|
||||
2. Персональный публичный чат хранится как канал типа `100`:
|
||||
- у `A` канал с `channelName = B`;
|
||||
- у `B` зеркальный канал с `channelName = A`.
|
||||
3. Сообщения канала — `TEXT_POST` и `TEXT_REPOST` в линии `line_code = rootBlockNumber` канала.
|
||||
4. Запись блока возможна только при валидной подписи blockchain-ключом владельца цепочки.
|
||||
|
||||
## 2. Что должен уметь MCP-инструмент
|
||||
|
||||
Минимальный набор операций:
|
||||
|
||||
1. `read_personal_public_dialog(fromLogin, toLogin, limitPerSide=200)`
|
||||
2. `append_personal_public_message(fromLogin, toLogin, text)`
|
||||
|
||||
## 3. Алгоритм чтения переписки
|
||||
|
||||
### 3.1 Найти оба канала (прямой и зеркальный)
|
||||
|
||||
Для `fromLogin = A`, `toLogin = B`:
|
||||
|
||||
1. Запросить `ListSubscriptionsFeed` для `A` и найти owned-канал:
|
||||
- `ownerLogin == A`
|
||||
- `channelName == B`
|
||||
- `channelTypeCode == 100`
|
||||
2. Запросить `ListSubscriptionsFeed` для `B` и найти owned-канал:
|
||||
- `ownerLogin == B`
|
||||
- `channelName == A`
|
||||
- `channelTypeCode == 100`
|
||||
|
||||
Если какой-то из каналов не найден — вернуть частичный результат + флаг отсутствия зеркала.
|
||||
|
||||
### 3.2 Вычитать сообщения из каналов
|
||||
|
||||
Для каждого найденного канала вызвать `GetChannelMessages`:
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "GetChannelMessages",
|
||||
"payload": {
|
||||
"login": "<текущий-login-сессии>",
|
||||
"channel": {
|
||||
"ownerBlockchainName": "...",
|
||||
"channelRootBlockNumber": 123,
|
||||
"channelRootBlockHash": "..."
|
||||
},
|
||||
"limit": 200,
|
||||
"sort": "asc"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Склеить в единый диалог
|
||||
|
||||
1. Объединить массивы сообщений из `A->B` и `B->A`.
|
||||
2. Отсортировать по `createdAtMs`, при равенстве — по `messageRef.blockNumber`.
|
||||
3. Вернуть структуру:
|
||||
- `messages[]`
|
||||
- `directChannelFound` / `reverseChannelFound`
|
||||
- метаданные обоих каналов.
|
||||
|
||||
## 4. Алгоритм дозаписи сообщения
|
||||
|
||||
Цель: добавить сообщение **от имени `fromLogin`** в его канал `fromLogin -> toLogin`.
|
||||
|
||||
### 4.1 Найти канал отправителя
|
||||
|
||||
Через `ListSubscriptionsFeed(fromLogin)` найти owned-канал:
|
||||
- `channelName == toLogin`
|
||||
- `channelTypeCode == 100`
|
||||
|
||||
Если канал не найден — вернуть ошибку `channel_not_found`.
|
||||
|
||||
### 4.2 Отправить `AddBlock` с `TEXT_POST`
|
||||
|
||||
Использовать клиентский/серверный helper формирования `TEXT_POST` body:
|
||||
- `lineCode = channelRootBlockNumber`;
|
||||
- `prevLineNumber/prevLineHash` берутся из последнего сообщения линии;
|
||||
- подпись — blockchain private key пользователя `fromLogin`.
|
||||
|
||||
Если у вас в MCP нет приватного ключа пользователя, дозапись невозможна.
|
||||
|
||||
## 5. Контракт MCP (рекомендуемый)
|
||||
|
||||
## `read_personal_public_dialog`
|
||||
|
||||
Вход:
|
||||
```json
|
||||
{
|
||||
"fromLogin": "alice",
|
||||
"toLogin": "bob",
|
||||
"limitPerSide": 200
|
||||
}
|
||||
```
|
||||
|
||||
Выход:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"directChannelFound": true,
|
||||
"reverseChannelFound": true,
|
||||
"messages": [
|
||||
{
|
||||
"authorLogin": "alice",
|
||||
"text": "Привет",
|
||||
"createdAtMs": 1760000000000,
|
||||
"channelSide": "alice->bob",
|
||||
"messageRef": { "blockNumber": 11, "blockHash": "..." }
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## `append_personal_public_message`
|
||||
|
||||
Вход:
|
||||
```json
|
||||
{
|
||||
"fromLogin": "alice",
|
||||
"toLogin": "bob",
|
||||
"text": "Тест"
|
||||
}
|
||||
```
|
||||
|
||||
Выход:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"serverLastGlobalNumber": 321,
|
||||
"serverLastGlobalHash": "..."
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Ограничения и безопасность
|
||||
|
||||
1. Персональный канал типа `100` сейчас публичный по модели чтения (не E2E DM).
|
||||
2. Нельзя дозаписать блок в чужой блокчейн без приватного ключа владельца (проверка подписи сервером).
|
||||
3. Для прод-инструмента нужно:
|
||||
- строгая авторизация MCP-вызовов;
|
||||
- аудит, кто и от чьего имени запрашивал чтение/запись;
|
||||
- лимиты/квоты на запись.
|
||||
|
||||
## 7. Мини-чеклист для реализации MCP
|
||||
|
||||
1. Реализовать helper поиска канала `findOwnedPersonalChannel(ownerLogin, peerLogin)`.
|
||||
2. Реализовать чтение двух сторон и merge/sort.
|
||||
3. Реализовать отправку `TEXT_POST` в найденный канал отправителя.
|
||||
4. Добавить понятные ошибки:
|
||||
- `user_not_found`
|
||||
- `channel_not_found`
|
||||
- `reverse_channel_not_found`
|
||||
- `signature_required`
|
||||
- `add_block_failed`
|
||||
@ -1,58 +0,0 @@
|
||||
# API для разработчиков: индекс операций
|
||||
|
||||
Этот файл фиксирует полный список публичных JSON/WebSocket операций, зарегистрированных в коде сервера.
|
||||
|
||||
Источник истины на момент актуализации:
|
||||
|
||||
- `shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java`.
|
||||
|
||||
Если операция есть в `HANDLERS` и `REQUEST_TYPES`, клиент может отправлять её как `op` в общем JSON-конверте из `00_Common_API_Format.md`.
|
||||
|
||||
## Актуальные операции
|
||||
|
||||
| Операция | Раздел документации | Кратко |
|
||||
| --- | --- | --- |
|
||||
| `AddUser` | `01_User_Registration_API.md` | отключено (`410 / ADD_USER_DISABLED`) |
|
||||
| `GetUser` | `01_User_Registration_API.md` | чтение/проверка пользователя + server-состояние его блокчейна |
|
||||
| `SearchUsers` | `01_User_Registration_API.md` | поиск логинов по префиксу |
|
||||
| `AuthChallenge` | `02_Authentication_API.md` | challenge для создания новой сессии |
|
||||
| `CreateAuthSession` | `02_Authentication_API.md` | создание новой авторизованной сессии |
|
||||
| `SessionChallenge` | `02_Authentication_API.md` | challenge для входа в существующую сессию |
|
||||
| `SessionLogin` | `02_Authentication_API.md` | вход в существующую сессию |
|
||||
| `ListSessions` | `03_Session_Management_API.md` | список активных сессий |
|
||||
| `CloseActiveSession` | `03_Session_Management_API.md` | закрытие активной сессии |
|
||||
| `AddBlock` | `04_Add_Block_to_Blockchain_API.md` | добавление блока в блокчейн |
|
||||
| `Ping` | `05_Technical_Requests_API.md` | keep-alive |
|
||||
| `GetServerInfo` | `05_Technical_Requests_API.md` | публичная информация о сервере |
|
||||
| `GetCallIceConfig` | `05_Technical_Requests_API.md` | STUN/TURN конфигурация звонков |
|
||||
| `ClientErrorLog` | `05_Technical_Requests_API.md` | логирование клиентской ошибки |
|
||||
| `ClientDebugLog` | `05_Technical_Requests_API.md` | клиентский debug-лог |
|
||||
| `CallDeliveryReport` | `05_Technical_Requests_API.md` | диагностика доставки/установки звонков |
|
||||
| `ListSubscriptionsFeed` | `06_Channels_Read_API.md` | лента каналов/подписок |
|
||||
| `GetChannelMessages` | `06_Channels_Read_API.md` | сообщения канала |
|
||||
| `GetMessageThread` | `06_Channels_Read_API.md` | тред сообщения |
|
||||
| `GetChannelsCounters` | `06_Channels_Read_API.md` | счетчики разделов каналов |
|
||||
| `ListGroupChats200` | `06_Channels_Read_API.md` | список групповых чатов типа `200` |
|
||||
| `GetGroupDialog` | `06_Channels_Read_API.md` | сообщения группового чата типа `200` |
|
||||
| `UpsertUserParam` | `10_User_Params_API.md` | запись параметра пользователя |
|
||||
| `GetUserParam` | `10_User_Params_API.md` | чтение одного параметра пользователя |
|
||||
| `ListUserParams` | `10_User_Params_API.md` | список параметров пользователя |
|
||||
| `GetFriendsLists` | `11_Connections_API.md` | входящие/исходящие друзья |
|
||||
| `ListContacts` | `11_Connections_API.md` | контакты текущего пользователя |
|
||||
| `GetUserConnectionsGraph` | `11_Connections_API.md` | граф связей пользователя |
|
||||
| `AddCloseFriend` | `11_Connections_API.md` | добавить близкого друга |
|
||||
| `UpsertPushToken` | `12_Direct_Messages_Push_Calls_API.md` | регистрация WebPush-токена |
|
||||
| `SendTestWebPush` | `12_Direct_Messages_Push_Calls_API.md` | тестовая push-доставка |
|
||||
| `SendDirectMessage` | `12_Direct_Messages_Push_Calls_API.md` | отправка подписанного DM-пакета |
|
||||
| `SendMessagePair` | `12_Direct_Messages_Push_Calls_API.md` | отправка пары входящий/исходящий DM |
|
||||
| `ReceiveOutcomingMessage` | `12_Direct_Messages_Push_Calls_API.md` | алиас `SendMessagePair` |
|
||||
| `ReceiveIncomingMessage` | `12_Direct_Messages_Push_Calls_API.md` | прием входящего DM-блока |
|
||||
| `AckSessionDelivery` | `12_Direct_Messages_Push_Calls_API.md` | подтверждение доставки в сессию |
|
||||
| `CallInviteBroadcast` | `12_Direct_Messages_Push_Calls_API.md` | broadcast приглашения к звонку |
|
||||
| `CallSignalToSession` | `12_Direct_Messages_Push_Calls_API.md` | сигнал звонка в конкретную сессию |
|
||||
|
||||
## Важные замечания
|
||||
|
||||
- `ReceiveOutcomingMessage` сейчас зарегистрирован как алиас того же handler/request-класса, что и `SendMessagePair`.
|
||||
- Классы `Net_MarkChannelMessagesSeen_*` существуют в коде, но операция `MarkChannelMessagesSeen` не зарегистрирована в `JsonHandlerRegistry`, поэтому в публичный список API не входит.
|
||||
- HTTP debug endpoints из `src/main/java/server/debug/` не входят в этот индекс WebSocket `op`; они описаны отдельно в `13_HTTP_Debug_API.md`.
|
||||
@ -1,129 +0,0 @@
|
||||
# API для разработчиков: параметры пользователя
|
||||
|
||||
Документ описывает операции для записи и чтения пользовательских параметров.
|
||||
|
||||
Текущие операции:
|
||||
|
||||
- `UpsertUserParam`
|
||||
- `GetUserParam`
|
||||
- `ListUserParams`
|
||||
|
||||
## 1. `UpsertUserParam`
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "UpsertUserParam",
|
||||
"requestId": "param-upsert-001",
|
||||
"payload": {
|
||||
"login": "alice",
|
||||
"param": "display_name",
|
||||
"time_ms": 1774700000123,
|
||||
"value": "Alice",
|
||||
"device_key": "BASE64_DEVICE_PUBLIC_KEY",
|
||||
"signature": "BASE64_SIGNATURE"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "UpsertUserParam",
|
||||
"requestId": "param-upsert-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Типовые ошибки
|
||||
|
||||
- `400 / BAD_FIELDS` — некорректные обязательные поля.
|
||||
- `422 / BAD_SIGNATURE` — подпись не прошла проверку.
|
||||
- `501 / DB_ERROR` — ошибка БД.
|
||||
|
||||
---
|
||||
|
||||
## 2. `GetUserParam`
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "GetUserParam",
|
||||
"requestId": "param-get-001",
|
||||
"payload": {
|
||||
"login": "alice",
|
||||
"param": "display_name"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "GetUserParam",
|
||||
"requestId": "param-get-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"login": "alice",
|
||||
"param": "display_name",
|
||||
"time_ms": 1774700000123,
|
||||
"value": "Alice",
|
||||
"device_key": "BASE64_DEVICE_PUBLIC_KEY",
|
||||
"signature": "BASE64_SIGNATURE"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Если параметр не найден, сервер возвращает `404` с пустым `payload`; отдельный прикладной код ошибки текущий handler не задаёт.
|
||||
|
||||
---
|
||||
|
||||
## 3. `ListUserParams`
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "ListUserParams",
|
||||
"requestId": "param-list-001",
|
||||
"payload": {
|
||||
"login": "alice"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "ListUserParams",
|
||||
"requestId": "param-list-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"login": "alice",
|
||||
"params": [
|
||||
{
|
||||
"login": "alice",
|
||||
"param": "display_name",
|
||||
"time_ms": 1774700000123,
|
||||
"value": "Alice",
|
||||
"device_key": "BASE64_DEVICE_PUBLIC_KEY",
|
||||
"signature": "BASE64_SIGNATURE"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Примечание
|
||||
|
||||
Имена JSON-полей `time_ms` и `device_key` сейчас соответствуют Java-модели ответа/запроса и должны передаваться именно в таком виде.
|
||||
@ -1,174 +0,0 @@
|
||||
# API для разработчиков: связи пользователей
|
||||
|
||||
Документ описывает операции чтения и записи пользовательских связей.
|
||||
|
||||
Текущие операции:
|
||||
|
||||
- `GetFriendsLists`
|
||||
- `ListContacts`
|
||||
- `GetUserConnectionsGraph`
|
||||
- `AddCloseFriend`
|
||||
|
||||
## 1. `GetFriendsLists`
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "GetFriendsLists",
|
||||
"requestId": "friends-001",
|
||||
"payload": {
|
||||
"login": "alice"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "GetFriendsLists",
|
||||
"requestId": "friends-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"login": "Alice",
|
||||
"out_friends": ["Bob"],
|
||||
"in_friends": ["Kate"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. `ListContacts`
|
||||
|
||||
`ListContacts` использует текущую авторизованную сессию. В payload нет дополнительных полей.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "ListContacts",
|
||||
"requestId": "contacts-001",
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "ListContacts",
|
||||
"requestId": "contacts-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"login": "Alice",
|
||||
"contacts": ["Bob", "Kate"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. `GetUserConnectionsGraph`
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "GetUserConnectionsGraph",
|
||||
"requestId": "graph-001",
|
||||
"payload": {
|
||||
"login": "alice"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "GetUserConnectionsGraph",
|
||||
"requestId": "graph-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"login": "Alice",
|
||||
"outFriends": ["Bob"],
|
||||
"inFriends": ["Kate"],
|
||||
"outContacts": [],
|
||||
"inContacts": [],
|
||||
"outFollows": [],
|
||||
"inFollows": [],
|
||||
"outSpouses": [],
|
||||
"inSpouses": [],
|
||||
"outParents": [],
|
||||
"inParents": [],
|
||||
"outChildren": [],
|
||||
"inChildren": [],
|
||||
"outSiblings": [],
|
||||
"inSiblings": [],
|
||||
"outKnownPersons": [],
|
||||
"inKnownPersons": [],
|
||||
"outShineConfirmed": [],
|
||||
"inShineConfirmed": [],
|
||||
"outShineSeen": [],
|
||||
"inShineSeen": [],
|
||||
"parents": [],
|
||||
"children": [],
|
||||
"siblings": [],
|
||||
"spouses": [],
|
||||
"allUsers": [
|
||||
{
|
||||
"login": "Bob",
|
||||
"official": false,
|
||||
"shine": true,
|
||||
"officialLabel": "",
|
||||
"shineLabel": "shine",
|
||||
"avatar": { "ar": "..." }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Примечание
|
||||
|
||||
Поля `known_person`, `shine_confirmed`, `shine_seen` в UI считаются недопроверенной зоной проекта; при изменениях этой логики нужна ручная end-to-end проверка.
|
||||
|
||||
---
|
||||
|
||||
## 4. `AddCloseFriend`
|
||||
|
||||
`AddCloseFriend` использует текущую авторизованную сессию как источник `login`.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "AddCloseFriend",
|
||||
"requestId": "close-friend-001",
|
||||
"payload": {
|
||||
"toLogin": "bob"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "AddCloseFriend",
|
||||
"requestId": "close-friend-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"login": "Alice",
|
||||
"toLogin": "Bob",
|
||||
"relation": "close_friend"
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -1,306 +0,0 @@
|
||||
# API для разработчиков: DM, push и сигналы звонков
|
||||
|
||||
Документ описывает WebSocket-операции для подписанных личных сообщений, WebPush и realtime-сигналов звонков.
|
||||
|
||||
Логика личных сообщений дополнительно описана в `Dev_Docs/Personal_Messages/README.md`; этот файл фиксирует именно публичные `op`, поля запросов и поля ответов.
|
||||
|
||||
## 1. `UpsertPushToken`
|
||||
|
||||
Требует авторизации.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "UpsertPushToken",
|
||||
"requestId": "push-upsert-001",
|
||||
"payload": {
|
||||
"sessionId": "SESSION_ID",
|
||||
"endpoint": "https://push.example/...",
|
||||
"p256dhKey": "BASE64",
|
||||
"authKey": "BASE64",
|
||||
"platform": "web",
|
||||
"userAgent": "Mozilla/5.0 ..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "UpsertPushToken",
|
||||
"requestId": "push-upsert-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"tokenId": "token-1",
|
||||
"updatedAtMs": 1774700000123
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. `SendTestWebPush`
|
||||
|
||||
Требует авторизации. Если `login` передан, он должен совпадать с логином текущей сессии.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SendTestWebPush",
|
||||
"requestId": "push-test-001",
|
||||
"payload": {
|
||||
"login": "alice",
|
||||
"sessionId": "SESSION_ID",
|
||||
"title": "Test",
|
||||
"text": "Push body"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SendTestWebPush",
|
||||
"requestId": "push-test-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"targetLogin": "alice",
|
||||
"attemptedSessions": 1,
|
||||
"sessionsWithPushConfig": 1,
|
||||
"delivered": 1,
|
||||
"failed": 0,
|
||||
"sentAtMs": 1774700000123
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. `SendDirectMessage`
|
||||
|
||||
Отправляет один подписанный DM-пакет.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SendDirectMessage",
|
||||
"requestId": "dm-001",
|
||||
"payload": {
|
||||
"blobB64": "BASE64_SIGNED_DM_PACKET"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SendDirectMessage",
|
||||
"requestId": "dm-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"messageId": "dm-1",
|
||||
"deliveredWsSessions": 1,
|
||||
"deliveredWebPushSessions": 0,
|
||||
"sessionNotFound": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. `SendMessagePair` и `ReceiveOutcomingMessage`
|
||||
|
||||
`ReceiveOutcomingMessage` сейчас является алиасом `SendMessagePair` и использует тот же request/handler.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SendMessagePair",
|
||||
"requestId": "dm-pair-001",
|
||||
"payload": {
|
||||
"incomingBlobB64": "BASE64_INCOMING_SIGNED_BLOCK",
|
||||
"outgoingBlobB64": "BASE64_OUTGOING_SIGNED_BLOCK"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SendMessagePair",
|
||||
"requestId": "dm-pair-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"baseKey": "base-key",
|
||||
"incomingKey": "incoming-key",
|
||||
"outgoingKey": "outgoing-key",
|
||||
"deliveredWsSessions": 1,
|
||||
"deliveredWebPushSessions": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. `ReceiveIncomingMessage`
|
||||
|
||||
Принимает входящий подписанный DM-блок.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "ReceiveIncomingMessage",
|
||||
"requestId": "dm-in-001",
|
||||
"payload": {
|
||||
"incomingBlobB64": "BASE64_INCOMING_SIGNED_BLOCK"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "ReceiveIncomingMessage",
|
||||
"requestId": "dm-in-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"messageKey": "incoming-key",
|
||||
"baseKey": "base-key",
|
||||
"deliveredWsSessions": 1,
|
||||
"deliveredWebPushSessions": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. `AckSessionDelivery`
|
||||
|
||||
Требует авторизации. Подтверждает доставку сообщения в текущую сессию.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "AckSessionDelivery",
|
||||
"requestId": "ack-001",
|
||||
"payload": {
|
||||
"messageKey": "incoming-key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "AckSessionDelivery",
|
||||
"requestId": "ack-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"messageKey": "incoming-key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. `CallInviteBroadcast`
|
||||
|
||||
Требует авторизации. Отправляет приглашение к звонку на активные сессии пользователя `toLogin`.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "CallInviteBroadcast",
|
||||
"requestId": "call-invite-001",
|
||||
"payload": {
|
||||
"toLogin": "bob",
|
||||
"callId": "call-1",
|
||||
"type": 100
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "CallInviteBroadcast",
|
||||
"requestId": "call-invite-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"callId": "call-1",
|
||||
"deliveredWsSessions": 1,
|
||||
"deliveredFcmSessions": 0,
|
||||
"deliveredWebPushSessions": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. `CallSignalToSession`
|
||||
|
||||
Требует авторизации. Отправляет сигнал звонка в конкретную сессию получателя.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "CallSignalToSession",
|
||||
"requestId": "call-signal-001",
|
||||
"payload": {
|
||||
"toLogin": "bob",
|
||||
"targetSessionId": "SESSION_ID",
|
||||
"callId": "call-1",
|
||||
"type": 101,
|
||||
"data": "{\"sdp\":\"...\"}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "CallSignalToSession",
|
||||
"requestId": "call-signal-001",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"delivered": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Если целевая сессия не найдена или доставка не удалась, сервер может вернуть `404`.
|
||||
|
||||
## Типовые ошибки
|
||||
|
||||
- `422 / NOT_AUTHENTICATED` — требуется авторизация.
|
||||
- `400 / BAD_FIELDS` — не заполнены обязательные поля.
|
||||
- `404 / USER_NOT_FOUND` — пользователь не найден.
|
||||
- `404 / SESSION_NOT_FOUND` — сессия не найдена.
|
||||
- `422 / BAD_SIGNATURE` — подпись DM не прошла проверку.
|
||||
- `422 / BAD_DEVICE_KEY` — некорректный device key отправителя.
|
||||
- `422 / BAD_TIME_WINDOW` — время подписанного сообщения вне допустимого окна.
|
||||
- `422 / REPLAY` — повторное сообщение заблокировано.
|
||||
@ -1,190 +0,0 @@
|
||||
# API для разработчиков: HTTP debug endpoints
|
||||
|
||||
Этот файл описывает отдельный HTTP debug API сервера. Он не использует WebSocket-конверт `op/requestId/payload` и включается только настройкой:
|
||||
|
||||
- `debug.tempApi.enabled=true`
|
||||
|
||||
Источник истины:
|
||||
|
||||
- `src/main/java/server/debug/DebugApiConfigurator.java`
|
||||
- `src/main/java/server/debug/*Servlet.java`
|
||||
|
||||
Если `.debug-token` отсутствует или пуст, endpoints возвращают `503 / DEBUG_DISABLED`.
|
||||
|
||||
## Авторизация
|
||||
|
||||
Для большинства debug endpoints используется Bearer token из `.debug-token`:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
Для `POST /debug/ws/ui-reload-all` поддерживается заголовок:
|
||||
|
||||
```http
|
||||
X-Debug-Token: <token>
|
||||
```
|
||||
|
||||
При неверном токене сервер возвращает `401 / UNAUTHORIZED`.
|
||||
|
||||
## Формат успешного HTTP-ответа
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Формат HTTP-ошибки
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": false,
|
||||
"code": "BAD_JSON",
|
||||
"message": "Тело запроса должно быть JSON"
|
||||
}
|
||||
```
|
||||
|
||||
## 1. `GET /debug/ws/clients`
|
||||
|
||||
Возвращает список активных WebSocket-клиентов.
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"count": 1,
|
||||
"clients": [
|
||||
{
|
||||
"sessionId": "SESSION_ID",
|
||||
"login": "alice",
|
||||
"authStatus": 2,
|
||||
"wsOpen": true,
|
||||
"remoteAddress": "127.0.0.1",
|
||||
"ip": "127.0.0.1",
|
||||
"userAgent": "Mozilla/5.0 ...",
|
||||
"clientInfoFromClient": "...",
|
||||
"clientInfoFromRequest": "...",
|
||||
"userLanguage": "ru",
|
||||
"sessionCreatedAtMs": 1774700000123
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. `POST /debug/ws/connect`
|
||||
|
||||
Запускает debug-сценарий соединения двух активных WS-сессий и отправляет им события:
|
||||
|
||||
- `DebugConnectPrepareResponder`
|
||||
- `DebugConnectStartInitiator`
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"initiatorSessionId": "SESSION_ID_1",
|
||||
"responderSessionId": "SESSION_ID_2",
|
||||
"clearDebugLog": true
|
||||
}
|
||||
```
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"runId": "dbg-...",
|
||||
"callId": "debug-call-...",
|
||||
"accepted": true,
|
||||
"initiatorSessionId": "SESSION_ID_1",
|
||||
"responderSessionId": "SESSION_ID_2",
|
||||
"initiatorLogin": "alice",
|
||||
"responderLogin": "bob",
|
||||
"mode": "cross-login"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Ошибки
|
||||
|
||||
- `400 / BAD_JSON` — тело запроса не JSON.
|
||||
- `400 / BAD_FIELDS` — не заполнены sessionId или переданы одинаковые sessionId.
|
||||
- `404 / INITIATOR_NOT_FOUND` — сессия инициатора не найдена или неактивна.
|
||||
- `404 / RESPONDER_NOT_FOUND` — сессия получателя не найдена или неактивна.
|
||||
|
||||
## 3. `GET /debug/ws/logs`
|
||||
|
||||
Возвращает tail debug-логов из `DebugRunLogBuffer`.
|
||||
|
||||
### Query-параметры
|
||||
|
||||
- `limit` — количество записей.
|
||||
- `runId` — фильтр по runId.
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"count": 1,
|
||||
"limit": 100,
|
||||
"runIdFilter": "ui-run-1",
|
||||
"logs": [
|
||||
{
|
||||
"ts": 1774700000123,
|
||||
"level": "info",
|
||||
"runId": "ui-run-1",
|
||||
"source": "debug-connect",
|
||||
"sessionId": "SESSION_ID",
|
||||
"login": "alice",
|
||||
"message": "opened channels tab",
|
||||
"details": "{}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. `POST /debug/ws/ui-reload-all`
|
||||
|
||||
Рассылает активным UI-сессиям debug-событие на reload.
|
||||
|
||||
### Запрос
|
||||
|
||||
```json
|
||||
{
|
||||
"reason": "manual_debug_api",
|
||||
"reloadAfterMs": 700
|
||||
}
|
||||
```
|
||||
|
||||
Если `reason` не передан или пустой, сервер использует `manual_debug_api`. `reloadAfterMs` ограничивается диапазоном `100..15000`.
|
||||
|
||||
### Успешный ответ
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"accepted": true,
|
||||
"reason": "manual_debug_api",
|
||||
"reloadAfterMs": 700,
|
||||
"issuedAtMs": 1774700000123,
|
||||
"totalConnections": 2,
|
||||
"sentCount": 2,
|
||||
"skippedCount": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Ошибки
|
||||
|
||||
- `400 / BAD_JSON` — тело запроса не JSON.
|
||||
@ -1,25 +0,0 @@
|
||||
# Форматы блокчейнов и блоков (индекс раздела)
|
||||
|
||||
Этот раздел разбит на несколько файлов, чтобы формат добавляемых блоков было проще читать и сопровождать.
|
||||
|
||||
## Структура раздела
|
||||
|
||||
- `01_Common_Block_Format.md` — общий бинарный формат блока (Frame v0), правила подписи и проверки.
|
||||
- `02_Blockchain_Kinds_and_Lines.md` — виды блокчейнов и логические линии внутри цепочки.
|
||||
- `10_TECH_Blocks.md` — системные блоки (`type=0`).
|
||||
- `11_TEXT_Blocks.md` — текстовые блоки (`type=1`).
|
||||
- `12_REACTION_Blocks.md` — реакции (`type=2`).
|
||||
- `13_CONNECTION_Blocks.md` — связи/подписки (`type=3`).
|
||||
- `14_USER_PARAM_Blocks.md` — пользовательские параметры (`type=4`).
|
||||
|
||||
## Быстрая карта типов
|
||||
|
||||
- `type=0` — TECH: HEADER, CREATE_CHANNEL.
|
||||
- `type=1` — TEXT: POST/EDIT_POST/REPLY/EDIT_REPLY/REPOST.
|
||||
- `type=2` — REACTION: LIKE/UNLIKE.
|
||||
- `type=3` — CONNECTION: FRIEND/CONTACT/FOLLOW/SPOUSE/PARENT/CHILD/SIBLING и обратные операции.
|
||||
- `type=4` — USER_PARAM: key/value-параметры пользователя.
|
||||
|
||||
## Примечание
|
||||
|
||||
Если нужно добавить новый тип или подтип блока, сначала обновляйте профильный файл этого раздела, затем API-документацию в `Dev_Docs/API`.
|
||||
@ -1,40 +0,0 @@
|
||||
# Типы каналов и CreateChannel
|
||||
|
||||
## 1. Формат `CreateChannelBody` (`msg_type=0`, `subType=1`, `version=1`)
|
||||
Payload включает:
|
||||
|
||||
1. line-поля канала (`lineCode`, `prevLineNumber`, `prevLineHash32`, `thisLineNumber`);
|
||||
2. `channelName`;
|
||||
3. `channelDescription`;
|
||||
4. `channelType` (`uint16`, 2 байта);
|
||||
5. `channelTypeVersion` (`uint16`, 2 байта).
|
||||
|
||||
## 2. Типы каналов
|
||||
- `0` — `stories` (root-канал пользователя).
|
||||
- `1` — публичный канал.
|
||||
- `100` — персональный канал.
|
||||
- `200` — групповой/чатовый канал.
|
||||
|
||||
Версия типа (`channelTypeVersion`) сейчас используется со значением `1`.
|
||||
|
||||
Важно для MVP:
|
||||
- `100` и `200` в формате поддерживаются, но в текущем UI не используются.
|
||||
- В MVP рабочий UI-флоу — каналы `0` и `1`.
|
||||
|
||||
## 3. Имя root-канала
|
||||
- Root-канал (`line_code = 0`) в API/чтении отображается как `stories`.
|
||||
- Публикации в `stories` разрешены владельцу собственного блокчейна.
|
||||
|
||||
## 4. Уникальность имени канала
|
||||
Проверка уникальности выполняется на сервере по ключу:
|
||||
|
||||
`owner_bch_name + channel_type_code + slug(channel_name)`
|
||||
|
||||
Это означает:
|
||||
- одно и то же имя допустимо у одного владельца для разных типов (`1`, `100`, `200`);
|
||||
- в рамках одного типа и одного владельца имя уникально.
|
||||
|
||||
## 5. Персональные каналы (`type=100`)
|
||||
- Сервер не проверяет бизнес-валидность собеседника по имени канала.
|
||||
- Проверка существования login для персонального канала выполняется на UI при создании.
|
||||
- При чтении сервер пытается собрать парный поток `A->B` + `B->A` (если обратный канал существует).
|
||||
@ -1,49 +0,0 @@
|
||||
# Общий формат добавляемого блока (Frame v0)
|
||||
|
||||
Этот файл описывает **единый бинарный формат** блока, который клиент отправляет через `AddBlock` в поле `blockBytesB64`.
|
||||
|
||||
## 1. Полная структура блока
|
||||
|
||||
Блок состоит из двух частей:
|
||||
|
||||
1. **PREIMAGE** (подписывается)
|
||||
2. **TAIL** (маркер подписи + подпись)
|
||||
|
||||
### PREIMAGE
|
||||
|
||||
- `frameCode (uint16)`
|
||||
- `prevHash32 (32 bytes)`
|
||||
- `blockSize (int32)` — размер PREIMAGE
|
||||
- `blockNumber (int32)`
|
||||
- `timestamp (int64)`
|
||||
- `type (uint16)`
|
||||
- `subType (uint16)`
|
||||
- `version (uint16)`
|
||||
- `bodyBytes (N)`
|
||||
|
||||
### TAIL
|
||||
|
||||
- `sigMarker (uint16)`
|
||||
- `signature64 (64 bytes, Ed25519)`
|
||||
|
||||
## 2. Что проверяет сервер при AddBlock
|
||||
|
||||
- `frameCode` должен быть `0x0000`.
|
||||
- `sigMarker` должен быть `0x0100`.
|
||||
- `blockNumber` должен идти строго по порядку (`last + 1`).
|
||||
- `prevHash32` должен совпасть с вершиной цепочки на сервере.
|
||||
- `body` должен пройти `check()` для конкретного типа.
|
||||
- подпись должна валидироваться публичным ключом блокчейна.
|
||||
|
||||
## 3. Ограничения
|
||||
|
||||
- максимальный полный размер блока: до 4 MiB;
|
||||
- timestamp не должен сильно уходить в будущее;
|
||||
- `bodyBytes` парсится по `type/subType/version` из заголовка блока.
|
||||
|
||||
## 4. Почему это важно
|
||||
|
||||
Одинаковый общий формат позволяет:
|
||||
- передавать разные виды записей через один RPC `AddBlock`;
|
||||
- валидировать блоки единообразно;
|
||||
- расширять типы `body`, не ломая каркас блока.
|
||||
@ -1,47 +0,0 @@
|
||||
# Виды блокчейнов и логических линий
|
||||
|
||||
## 1. Именованный блокчейн
|
||||
|
||||
Базовый идентификатор цепочки пользователя:
|
||||
|
||||
- `blockchainName = <login>-<NNN>`
|
||||
- пример: `alice-001`
|
||||
|
||||
Обычно это одна основная цепочка пользователя.
|
||||
|
||||
## 2. Логические линии внутри одной цепочки
|
||||
|
||||
Физически цепочка одна, но внутри есть независимые логические последовательности (линии), которые ведутся через поля:
|
||||
|
||||
- `lineCode`
|
||||
- `prevLineNumber`
|
||||
- `prevLineHash32`
|
||||
- `thisLineNumber`
|
||||
|
||||
Линии используются для:
|
||||
- TECH-событий;
|
||||
- каналов с текстовыми постами;
|
||||
- связей и подписок;
|
||||
- пользовательских параметров.
|
||||
|
||||
## 3. Правила line-полей (фактическая серверная валидация)
|
||||
|
||||
Line-поля: `lineCode`, `prevLineNumber`, `prevLineHash32`, `thisLineNumber`.
|
||||
|
||||
- Line-поля разрешены только для `msg_type`: `0`, `1`, `3`, `4`.
|
||||
- Если передано хотя бы одно line-поле, должны быть переданы все 4.
|
||||
- `prevLineNumber/prevLineHash32` должны указывать на существующий блок этой же цепочки.
|
||||
- Для первого шага после root (`prevLineNumber == lineCode`):
|
||||
- `TEXT (msg_type=1)`: `thisLineNumber = 0`;
|
||||
- `TECH/CONNECTION/USER_PARAM (0/3/4)`: `thisLineNumber = 1`.
|
||||
- Для обычного шага:
|
||||
- `TEXT`: `thisLineNumber` допускает `same` или `+1` от предыдущего блока линии;
|
||||
- `TECH/CONNECTION/USER_PARAM`: строго `+1`.
|
||||
|
||||
## 4. Root-идея для каналов и подписок
|
||||
|
||||
Для ссылок вида follow/friend/contact принято ссылаться на корневые блоки:
|
||||
- `HEADER` для базовой сущности пользователя/канала `0`;
|
||||
- `CREATE_CHANNEL` для пользовательских каналов.
|
||||
|
||||
Так ссылки остаются стабильными, даже когда в канале появляются новые сообщения.
|
||||
@ -1,33 +0,0 @@
|
||||
# Командные сообщения каналов
|
||||
|
||||
## 1. Общий префикс
|
||||
Командные сообщения распознаются по префиксу:
|
||||
|
||||
`/.`
|
||||
|
||||
Пример:
|
||||
- `/.desc Новый комментарий канала`
|
||||
|
||||
## 2. Поддерживаемые команды
|
||||
|
||||
### Для всех типов каналов (`0`, `1`, `100`, `200`)
|
||||
- `/.desc <text>` — смена описания канала.
|
||||
|
||||
Примечание:
|
||||
- Описание канала в чтении определяется последней командой `/.desc` в линии канала.
|
||||
- Если `/.desc` не было, используется описание из `CreateChannel`.
|
||||
|
||||
### Дополнительно для `type=200`
|
||||
- `/.add <login> <channelName>`
|
||||
- `/.remove <login> <channelName>`
|
||||
|
||||
Формат аргументов фиксирован: через пробел.
|
||||
|
||||
## 3. Текущая модель применения
|
||||
- Команды передаются как обычные `TEXT_POST` сообщения.
|
||||
- Сервер уже применяет `/.desc` при вычислении актуального описания канала.
|
||||
- Команды `/.add` и `/.remove` зарезервированы под расширенную модель участников `type=200` на уровне UI/агрегации.
|
||||
|
||||
## 4. Статус для MVP
|
||||
- В текущем UI каналы `type=100` и `type=200` не используются.
|
||||
- Соответственно, `/.add` и `/.remove` считаются запланированными и пока не участвуют в рабочем UI-сценарии.
|
||||
@ -1,18 +0,0 @@
|
||||
# TECH блоки (`type=0`, `version=1`)
|
||||
|
||||
TECH-тип покрывает системные записи цепочки.
|
||||
|
||||
## Подтипы
|
||||
|
||||
1. `subType=0` — `HEADER_COMPAT`
|
||||
- стартовый блок цепочки;
|
||||
- payload: tag `SHiNE` + login владельца.
|
||||
|
||||
2. `subType=1` — `TECH_CREATE_CHANNEL`
|
||||
- создание нового канала;
|
||||
- хранит line-поля + `channelName` + `channelDescription` + `channelType` + `channelTypeVersion`.
|
||||
|
||||
## Назначение
|
||||
|
||||
- инициализация блокчейна;
|
||||
- управление набором каналов пользователя.
|
||||
@ -1,38 +0,0 @@
|
||||
# TEXT блоки (`type=1`, `version=1`)
|
||||
|
||||
TEXT-тип хранит сообщения и редактирования.
|
||||
|
||||
## Подтипы
|
||||
|
||||
1. `subType=10` — `TEXT_POST`
|
||||
- пост в линии канала;
|
||||
- содержит line-поля + текст.
|
||||
|
||||
2. `subType=11` — `TEXT_EDIT_POST`
|
||||
- редактирование поста;
|
||||
- line-поля + target на оригинальный POST + новый текст.
|
||||
|
||||
3. `subType=20` — `TEXT_REPLY`
|
||||
- ответ на сообщение;
|
||||
- target (`toBlockchainName`, `toBlockGlobalNumber`, `toBlockHash32`) + текст.
|
||||
|
||||
4. `subType=21` — `TEXT_EDIT_REPLY`
|
||||
- редактирование ответа;
|
||||
- target на исходный REPLY + новый текст.
|
||||
- допускается пустой `text` для логического удаления сообщения (без физического удаления блока).
|
||||
|
||||
5. `subType=30` — `TEXT_REPOST`
|
||||
- репост сообщения в линию канала;
|
||||
- содержит line-поля + target на оригинальное сообщение + текст комментария;
|
||||
- на текущем этапе продуктовой логики репост не редактируется (версии не накапливаются);
|
||||
- временно отключён для записи через `AddBlock` до будущей реализации репостов.
|
||||
|
||||
## Правило для edit
|
||||
|
||||
`EDIT_POST` и `EDIT_REPLY` должны ссылаться на **оригинальный** блок, а не на предыдущий edit.
|
||||
|
||||
## Пустой text в edit
|
||||
|
||||
- Для `TEXT_EDIT_POST` и `TEXT_EDIT_REPLY` допустим `textLen=0`.
|
||||
- Такой edit трактуется как логическое удаление содержимого сообщения.
|
||||
- Для удаления используется именно edit-блок; отдельного `DELETE`-подтипа нет.
|
||||
@ -1,14 +0,0 @@
|
||||
# REACTION блоки (`type=2`, `version=1`)
|
||||
|
||||
## Подтипы
|
||||
|
||||
1. `subType=1` — `REACTION_LIKE`
|
||||
- лайк на целевой блок;
|
||||
- хранит target: `toBlockchainName`, `toBlockGlobalNumber`, `toBlockHash32`.
|
||||
2. `subType=2` — `REACTION_UNLIKE`
|
||||
- снятие лайка с целевого блока;
|
||||
- хранит target: `toBlockchainName`, `toBlockGlobalNumber`, `toBlockHash32`.
|
||||
|
||||
## Назначение
|
||||
|
||||
- реакция на текстовые сообщения (и потенциально другие target-блоки, если это разрешено бизнес-логикой).
|
||||
@ -1,29 +0,0 @@
|
||||
# CONNECTION блоки (`type=3`, `version=1`)
|
||||
|
||||
CONNECTION-тип описывает социальные связи и подписки.
|
||||
|
||||
## Подтипы
|
||||
|
||||
• `10/11` — `close_friend / unclose_friend` (близкий друг)
|
||||
• `20/21` — `contact / uncontact` (контакт)
|
||||
• `30/31` — `follow / unfollow` (подписан)
|
||||
• `40/41` — `spouse / unspouse` (супруг/супруга)
|
||||
• `50/51` — `parent / unparent` (родитель)
|
||||
• `52/53` — `child / unchild` (ребёнок)
|
||||
• `54/55` — `sibling / unsibling` (брат/сестра)
|
||||
• `60/61` — `known_person / unknown_person` (знаю этого человека)
|
||||
• `70/71` — `shine_confirmed / shine_unconfirmed` (точно уверен, что сияющий)
|
||||
• `74/75` — `shine_seen / shine_unseen` (мало знаком, но видел сияющим)
|
||||
|
||||
## Общий формат payload
|
||||
|
||||
- line-поля (`lineCode`, `prevLineNumber`, `prevLineHash32`, `thisLineNumber`)
|
||||
- target (`toBlockchainName`, `toBlockGlobalNumber`, `toBlockHash32`)
|
||||
|
||||
## Правила target
|
||||
|
||||
- FRIEND/CONTACT обычно указывают на `HEADER` цели (`block 0`).
|
||||
- FOLLOW указывает на root канала:
|
||||
- `HEADER` для канала `0`;
|
||||
- `CREATE_CHANNEL` для пользовательского канала.
|
||||
- Для остальных типов связи (`SPOUSE/PARENT/CHILD/SIBLING`) используется тот же target-формат.
|
||||
@ -1,14 +0,0 @@
|
||||
# USER_PARAM блоки (`type=4`, `version=1`)
|
||||
|
||||
## Подтипы
|
||||
|
||||
1. `subType=1` — `USER_PARAM_TEXT_TEXT`
|
||||
- хранит line-поля + `paramKey` + `paramValue`.
|
||||
|
||||
## Назначение
|
||||
|
||||
- сохранение пользовательского состояния (настройки клиента, синк-метки, курсоры чтения и т.д.).
|
||||
|
||||
## Практика
|
||||
|
||||
Для сложных структур удобно хранить JSON-строку в `paramValue` с версией схемы.
|
||||
@ -1,48 +0,0 @@
|
||||
# История изменений документации блокчейна
|
||||
|
||||
## 2026-05-24 11:40:00 +0300
|
||||
- Базовый коммит-ориентир: `abdce05`.
|
||||
- `TEXT_REPOST (subType=30)` оставлен как зарезервированный формат, но новые блоки репоста временно отключены на уровне `AddBlock`.
|
||||
- В `11_TEXT_Blocks.md` зафиксировано, что запись `TEXT_REPOST` временно не используется до будущей реализации.
|
||||
- В `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md` добавлен код отказа `repost_disabled`.
|
||||
|
||||
## 2026-05-21 19:05:00 +0300
|
||||
- Базовый коммит-ориентир: `5344c42`.
|
||||
- Добавлен новый TEXT-подтип `TEXT_REPOST (subType=30)`:
|
||||
- обновлён перечень типов в `11_TEXT_Blocks.md`;
|
||||
- обновлена быстрая карта типов в `00_Blockchain_Formats_and_Block_Types.md`.
|
||||
- Уточнено API-описание поддержанных подтипов в `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`.
|
||||
- В документе `Dev_Docs/API/08_MCP_Чтение_и_дозапись_персонального_публичного_чата.md` зафиксировано, что чтение канала учитывает `TEXT_POST` и `TEXT_REPOST`.
|
||||
|
||||
## 2026-05-20 11:34:17 +0300
|
||||
- Базовый коммит-ориентир: `a53444b`.
|
||||
- В `13_CONNECTION_Blocks.md` добавлены новые CONNECTION подтипы:
|
||||
- `60/61` — `known_person / unknown_person` (знаю этого человека);
|
||||
- `70/71` — `shine_confirmed / shine_unconfirmed` (точно уверен, что сияющий);
|
||||
- `74/75` — `shine_seen / shine_unseen` (мало знаком, но видел сияющим).
|
||||
- Обновлён список CONNECTION-подтипов в `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`.
|
||||
|
||||
## 2026-05-19 20:30:21 +0300
|
||||
- Базовый коммит-ориентир: `7986184`.
|
||||
- Уточнён документ `11_TEXT_Blocks.md`: для `TEXT_EDIT_POST` и `TEXT_EDIT_REPLY` зафиксировано, что `textLen=0` допустим и трактуется как логическое удаление сообщения.
|
||||
- Явно закреплено, что отдельного `DELETE`-подтипа нет, удаление выполняется edit-блоком.
|
||||
|
||||
## 2026-05-19 00:22:46 +0300
|
||||
- Базовый коммит-ориентир: `c27da63a3e65`.
|
||||
- Актуализирован `README.md` как точка входа для MVP-документации по протоколу.
|
||||
- В документации явно зафиксировано, что `channelType=100` и `channelType=200` присутствуют в формате, но пока не используются в UI.
|
||||
- Актуализирован перечень REACTION-подтипов: добавлен `REACTION_UNLIKE (subType=2)`.
|
||||
- Актуализирован перечень CONNECTION-подтипов: добавлены `SPOUSE/PARENT/CHILD/SIBLING` и обратные операции.
|
||||
- В документ `02_Blockchain_Kinds_and_Lines.md` добавлены фактические серверные правила валидации line-полей.
|
||||
- Обновлён корневой `AGENTS.md`: формат блокчейна менять только после явного подтверждения пользователя и с предварительным предупреждением.
|
||||
|
||||
## 2026-05-13 00:02:32 +0300
|
||||
- Базовый коммит-ориентир: `f63f40f1eb2f`.
|
||||
- Добавлен текущий формат `CreateChannelBody` с полями `channelType (2 байта)` и `channelTypeVersion (2 байта)`.
|
||||
- Зафиксированы типы каналов: `0=stories`, `1=public`, `100=personal`, `200=group`.
|
||||
- Серверная уникальность имени канала изменена на `owner + type + name(slug)`.
|
||||
- Root-канал `0` переименован в `stories` на уровне API-чтения.
|
||||
- Для персонального канала (`type=100`) включена сборка парного потока при чтении (`A->B` + `B->A`, если существует).
|
||||
- Добавлена поддержка командного префикса `/.` и команды `/.desc` для актуализации описания канала при чтении.
|
||||
- Зафиксированы команды `/.add` и `/.remove` для каналов `type=200` (зарезервировано под расширение участниками).
|
||||
- В `AGENTS.md` добавлено обязательное правило актуализации документации в `Dev_Docs/Blockchain/`.
|
||||
@ -1,33 +0,0 @@
|
||||
# Документация блокчейна SHiNE (MVP)
|
||||
|
||||
Этот каталог описывает только текущий рабочий формат протокола для MVP.
|
||||
|
||||
## Основные документы
|
||||
1. [01_Common_Block_Format.md](./01_Common_Block_Format.md)
|
||||
Единый бинарный формат блока (Frame v0), подпись, базовые проверки.
|
||||
2. [02_Blockchain_Kinds_and_Lines.md](./02_Blockchain_Kinds_and_Lines.md)
|
||||
Виды цепочек и правила line-полей.
|
||||
3. [10_TECH_Blocks.md](./10_TECH_Blocks.md)
|
||||
Системные блоки (`msg_type=0`).
|
||||
4. [11_TEXT_Blocks.md](./11_TEXT_Blocks.md)
|
||||
Текстовые блоки (`msg_type=1`).
|
||||
5. [12_REACTION_Blocks.md](./12_REACTION_Blocks.md)
|
||||
Реакции (`msg_type=2`).
|
||||
6. [13_CONNECTION_Blocks.md](./13_CONNECTION_Blocks.md)
|
||||
Социальные связи (`msg_type=3`).
|
||||
7. [14_USER_PARAM_Blocks.md](./14_USER_PARAM_Blocks.md)
|
||||
Параметры пользователя (`msg_type=4`).
|
||||
8. [01_Channel_Types_and_CreateChannel.md](./01_Channel_Types_and_CreateChannel.md)
|
||||
Типы каналов и формат `CreateChannelBody`.
|
||||
9. [02_Channel_Commands.md](./02_Channel_Commands.md)
|
||||
Команды в текстовых сообщениях каналов.
|
||||
10. [CHANGELOG.md](./CHANGELOG.md)
|
||||
Журнал изменений документации.
|
||||
|
||||
## Важные ограничения MVP
|
||||
- Каналы `type=100` и `type=200` присутствуют в формате, но сейчас не используются в UI.
|
||||
- Поддерживаемый рабочий сценарий UI на текущем этапе: `stories (type=0)` и `public (type=1)`.
|
||||
|
||||
## Обязательное сопровождение
|
||||
- При любом изменении формата/правил блокчейна в коде документы этого каталога обновляются в том же наборе изменений.
|
||||
- Каждое обновление документов фиксируется в `CHANGELOG.md` с датой/временем и хэшем коммита-основания.
|
||||
@ -1,100 +0,0 @@
|
||||
# Синхронизация блоков и DM между серверами SHiNE
|
||||
|
||||
Документ описывает архитектуру и протокол синхронизации данных между партнёрскими серверами SHiNE.
|
||||
|
||||
## 1. Зачем нужна синхронизация
|
||||
|
||||
Пользователи SHiNE могут быть «приписаны» к разным серверам.
|
||||
Когда пользователь A (на сервере X) пишет пользователю B (на сервере Y):
|
||||
|
||||
1. Сервер X принимает сообщение;
|
||||
2. Сервер X должен переслать DM-блок серверу Y;
|
||||
3. Сервер Y сохраняет блок и доставляет в активные сессии пользователя B.
|
||||
|
||||
Аналогично, блоки пользовательского блокчейна (записи `AddBlock`) должны синхронизироваться,
|
||||
чтобы любой партнёрский сервер мог отдать полную историю пользователя.
|
||||
|
||||
## 2. Список серверов синхронизации (`sync_servers`)
|
||||
|
||||
Каждый сервер регистрирует в своей Solana PDA список `sync_servers` —
|
||||
логины SHiNE-аккаунтов партнёрских серверов, с которыми он синхронизируется.
|
||||
|
||||
- Список хранится в блоке `ServerProfileBlock` внутри `user_pda` сервера.
|
||||
- Адрес каждого партнёрского сервера читается из его PDA на Solana.
|
||||
- Синхронизация двусторонняя: оба сервера должны иметь друг друга в `sync_servers`.
|
||||
|
||||
## 3. Что синхронизируется
|
||||
|
||||
### 3.1 Личные сообщения (DM)
|
||||
|
||||
- Все DM-блоки форматов типов `1/2` (текст) и `3/4` (read-receipt).
|
||||
- Сервер-отправитель: при получении пары блоков от клиента перенаправляет их серверу получателя.
|
||||
- Сервер-получатель: сохраняет блоки в `signed_messages_v2`, доставляет в активные сессии.
|
||||
- Дедупликация по уникальному `message_key = from|to|timeMs|nonce|type`.
|
||||
|
||||
### 3.2 Блоки пользовательского блокчейна
|
||||
|
||||
- Все блоки `AddBlock` пользователей, зарегистрированных на сервере или синхронизирующихся через него.
|
||||
- Синхронизируются в обе стороны между всеми партнёрами из `sync_servers`.
|
||||
- Порядок блоков сохраняется (по глобальному номеру блока и хэшу).
|
||||
- Дедупликация по глобальному номеру блока и хэшу.
|
||||
|
||||
## 4. Протокол синхронизации (целевой, не реализован)
|
||||
|
||||
### 4.1 Межсерверное соединение
|
||||
|
||||
- Серверы устанавливают постоянное WebSocket-соединение друг с другом.
|
||||
- Адрес партнёра определяется по `server_address` из его Solana PDA.
|
||||
- Аутентификация: подпись Ed25519 корневым ключом сервера (`root_key` из PDA).
|
||||
- При разрыве — переподключение с экспоненциальным backoff.
|
||||
|
||||
### 4.2 Доставка новых данных (push)
|
||||
|
||||
- При получении нового блока или DM сервер немедленно пушит его всем подключённым партнёрам.
|
||||
- Партнёр подтверждает приём (ACK). Без ACK — повтор с backoff.
|
||||
|
||||
### 4.3 Начальная синхронизация (backfill)
|
||||
|
||||
- При первом подключении к партнёру серверы обмениваются «курсорами» состояния:
|
||||
последний глобальный номер блока, последний известный DM-ключ.
|
||||
- Сервер с более полной историей досылает недостающее партнёру.
|
||||
|
||||
### 4.4 Разрешение конфликтов
|
||||
|
||||
- Блоки пользовательского блокчейна: порядок определяется глобальным номером блока.
|
||||
Конфликтующие ветки (fork) разрешаются по правилам `AddBlock` (см. `Dev_Docs/Blockchain/README.md`).
|
||||
- DM: конфликтов нет, `message_key` уникален.
|
||||
|
||||
## 5. Маршрутизация DM между серверами
|
||||
|
||||
При отправке DM от пользователя A к пользователю B:
|
||||
|
||||
1. Клиент A отправляет пару блоков на свой сервер X.
|
||||
2. Сервер X определяет, на каком сервере зарегистрирован пользователь B.
|
||||
- Сначала проверяет локально (если B зарегистрирован на X).
|
||||
- Иначе читает PDA пользователя B из Solana и смотрит `access_servers`.
|
||||
- Выбирает первый доступный сервер из `access_servers` и перенаправляет туда DM.
|
||||
3. Сервер Y (из `access_servers` B) сохраняет и доставляет блоки.
|
||||
|
||||
Кэш адресов серверов: обновляется раз в сессию (при ошибке соединения).
|
||||
|
||||
## 6. Безопасность
|
||||
|
||||
- Все блоки подписаны ключами пользователя на клиенте — сервер не может подделать содержимое.
|
||||
- Серверы не расшифровывают DM-контент (шифрование — задача следующего этапа).
|
||||
- При синхронизации каждый блок проходит валидацию подписи на принимающем сервере.
|
||||
|
||||
## 7. Статус реализации
|
||||
|
||||
| Компонент | Статус |
|
||||
|-----------|--------|
|
||||
| Регистрация серверной PDA в Solana | ✅ Реализовано |
|
||||
| Чтение `sync_servers` из PDA | Нужна реализация |
|
||||
| Межсерверный WebSocket-канал | Нужна реализация |
|
||||
| Push новых DM партнёрам | Нужна реализация |
|
||||
| Push блоков блокчейна партнёрам | Нужна реализация |
|
||||
| Backfill при первом подключении | Нужна реализация |
|
||||
| Маршрутизация DM через access_servers | Нужна реализация (заглушка) |
|
||||
|
||||
Текущая версия сервера работает без межсерверной синхронизации.
|
||||
Синхронизация — задача следующего этапа разработки.
|
||||
@ -1,48 +0,0 @@
|
||||
# Будущие фичи
|
||||
|
||||
Эта папка хранит задачи, которые сознательно отложены и сейчас не должны попадать в активную разработку или ручную проверку без отдельной команды пользователя.
|
||||
|
||||
## Горизонты планирования
|
||||
|
||||
- `near/` - ближайшие планы: задачи, к которым можно вернуться сегодня или завтра.
|
||||
- `medium/` - среднесрочные планы: задачи на ближайшие недели или 1-2 месяца.
|
||||
- `far/` - дальнее будущее: идеи без понятного срока возврата.
|
||||
|
||||
Если пользователь спрашивает, какие есть планы, агент должен смотреть эти три папки и кратко перечислять задачи по горизонтам.
|
||||
|
||||
## Как использовать
|
||||
|
||||
1. Каждая будущая фича описывается отдельным markdown-файлом в одном из горизонтов.
|
||||
2. В файле нужно фиксировать:
|
||||
- зачем нужна фича;
|
||||
- к какому сроку или горизонту она относится;
|
||||
- что нужно сделать;
|
||||
- какие вопросы нужно уточнить перед реализацией;
|
||||
- что уже было сделано в коде, если фича частично реализована;
|
||||
- что временно отключено или закомментировано, если применимо;
|
||||
- какие документы нужно обновить при возврате к задаче;
|
||||
- с какого места продолжать разработку.
|
||||
3. Агент не должен начинать реализацию файлов из этой папки без явной просьбы пользователя.
|
||||
|
||||
## Текущие планы
|
||||
|
||||
### Ближайшие
|
||||
|
||||
- `near/2026-05-25_1106_telegram_agent_players.md` - разрешённые пользователи Telegram для агента, отдельные папки игроков, персональные истории и публикация краткого вопроса/ответа в общий канал.
|
||||
- `near/2026-05-25_1106_wallet_topup_solana_arweave.md` - пополнение Solana и Arweave через внешний сервис покупки с подсказкой и копированием адреса.
|
||||
|
||||
### Среднесрочные
|
||||
|
||||
- `medium/2026-05-24_1140_репосты_в_каналах_и_тредах.md` - репосты в каналах и тредах.
|
||||
- `medium/2026-05-25_1106_shine_balance_wallet.md` - кошелёк и пополнение баланса сияния через блокчейн.
|
||||
- `medium/2026-05-26_0029_esp32s3_file_storage.md` - ESP32S3 как личное файловое хранилище SHiNE для файлов переписок и вложений.
|
||||
- `medium/2026-06-03_подключение_других_устройств_через_qr.md` - довести подключение других устройств через QR: сейчас заготовка есть, но сценарий работает нестабильно и его нужно будет отдельно доделать.
|
||||
- `medium/2026-06-02_сессионные_саб_серверы_в_pda.md` - несколько саб-серверов пользователя как типизированные сессии в PDA с версией записи.
|
||||
|
||||
### DAO-запуск
|
||||
|
||||
- `dao_запуск/2026-06-05_esp32_hardware_wallet_device_session.md` - ESP32 как аппаратный кошелёк: постоянная device-сессия на сервере, подтверждение операций на экране, делегированные сессии для браузера/телефона.
|
||||
|
||||
### Дальнее будущее
|
||||
|
||||
- Сейчас задач нет.
|
||||
@ -1,64 +0,0 @@
|
||||
# ESP32 как аппаратный кошелёк (device-сессия)
|
||||
|
||||
## Суть фичи
|
||||
|
||||
ESP32 становится аппаратным HSM (hardware security module): хранит ключи, постоянно подключён к SHiNE-серверу как device-сессия, подтверждает операции нажатием на экране. Другие устройства (браузер, телефон) взаимодействуют с ESP32 через сервер — без прямого соединения.
|
||||
|
||||
## Два ключевых сценария
|
||||
|
||||
### Сценарий 1 — Создание делегированной сессии
|
||||
1. Браузер/телефон → сервер: «хочу делегированную сессию от имени пользователя X»
|
||||
2. Сервер → ESP32 (device-сессия): «запрос на одобрение»
|
||||
3. Пользователь нажимает «Да» на сенсорном экране ESP32
|
||||
4. ESP32 → сервер: одобрено → сервер создаёт делегированную сессию для браузера
|
||||
|
||||
### Сценарий 2 — Подпись транзакции / блока
|
||||
1. Браузер (через делегированную сессию) → сервер → ESP32: «подпиши вот это»
|
||||
2. ESP32 показывает запрос на экране, пользователь подтверждает
|
||||
3. ESP32 подписывает нужным ключом → ответ через сервер → браузер
|
||||
|
||||
## Что нужно сделать
|
||||
|
||||
### ESP32 (основная работа)
|
||||
- [ ] Инициализация WiFi (SSID/пароль в NVS)
|
||||
- [ ] WebSocket-клиент (`WebSocketsClient`) — постоянное соединение с сервером
|
||||
- [ ] Авторизация на сервере: `AuthChallenge` → `CreateAuthSession` через `deviceKey` (уже есть в NVS), сохранить `sessionId` в NVS
|
||||
- [ ] Обработчик входящих WebSocket-событий: JSON-парсинг, диспетчер по типу
|
||||
- [ ] Новые UI-экраны: «Разрешить сессию?» и «Подписать?» с кнопками Да/Нет
|
||||
- [ ] Расширенное хранилище ключей в NVS (произвольные именованные ключи сверх базовых трёх)
|
||||
- [ ] Переподключение при разрыве (reconnect loop)
|
||||
|
||||
### Сервер (минимальные изменения)
|
||||
- [ ] Добавить поле `sessionType` (`USER` / `DEVICE`) в таблицу `active_sessions`
|
||||
- [ ] Новая операция `DeviceApprovalRequest` — браузер запрашивает одобрение у device-сессии
|
||||
- [ ] Новая операция `DeviceApprovalResponse` — ESP32 отвечает (одобрено/отклонено)
|
||||
- [ ] Новые операции `SignRequest` / `SignResponse` — запрос подписи и ответ
|
||||
- [ ] Роутинг: при получении запроса найти device-сессию через `ActiveConnectionsRegistry.getByLogin(login)` + фильтр по `sessionType=DEVICE`, переслать туда
|
||||
|
||||
### Клиент (отдельный этап)
|
||||
- [ ] Браузерное расширение или UI: создание делегированной сессии, отправка `SignRequest`
|
||||
|
||||
## Что уже готово (переиспользуем)
|
||||
|
||||
- **Роутинг сообщений** — `SendDirectMessage` с `TARGET_ONE_SESSION` и `CallSignalToSession` уже умеют точечно доставлять в конкретный `sessionId`. Механизм готов, нужно добавить только новые op-коды поверх него.
|
||||
- **Ed25519 на ESP32** — библиотека `<Ed25519.h>` уже используется в скетче. Подписи работают.
|
||||
- **NVS** — уже хранит логин, мастер-секрет, 3 пары ключей. Расширяется легко.
|
||||
- **`ActiveConnectionsRegistry`** — поиск по `login` и `sessionId` уже есть на сервере.
|
||||
- **Аутентификация** — схема `AuthChallenge` → `CreateAuthSession` через Ed25519 уже полностью реализована.
|
||||
|
||||
## Оценка сложности
|
||||
|
||||
| Компонент | Сложность |
|
||||
|---|---|
|
||||
| ESP32: WiFi + WebSocket-клиент + авторизация | Средняя |
|
||||
| ESP32: обработчик входящих + UI подтверждений | Средняя |
|
||||
| Сервер: флаг sessionType + 4 новых op-а + роутинг | Низкая–средняя |
|
||||
| Браузерное расширение | Высокая (отдельный этап) |
|
||||
|
||||
**Итого фазы ESP32 + сервер: ~1–1.5 недели.**
|
||||
|
||||
## С чего начинать
|
||||
|
||||
1. Серверная часть проще и быстрее — начать с добавления `sessionType` и `DeviceApprovalRequest/Response`.
|
||||
2. Затем ESP32: WiFi → WebSocket → авторизация → обработчик входящих → UI.
|
||||
3. Браузерное расширение — отдельная итерация после того как ESP32 + сервер работают.
|
||||
@ -1,5 +0,0 @@
|
||||
# Дальнее будущее
|
||||
|
||||
Сейчас в этом горизонте нет активных идей.
|
||||
|
||||
Сюда переносить задачи, у которых нет понятного срока возврата и которые не нужно учитывать в ближайшем или среднесрочном планировании.
|
||||
@ -1,99 +0,0 @@
|
||||
# Репосты в каналах и тредах
|
||||
|
||||
- Статус:
|
||||
`future`
|
||||
|
||||
- Горизонт:
|
||||
`medium`
|
||||
|
||||
- Ориентир:
|
||||
1-2 месяца
|
||||
|
||||
- Решение от 2026-05-24:
|
||||
Репосты временно убраны из активной разработки. Фича уже была частично реализована, но не доведена до финальной проверки. Чтобы она не мешала запуску проекта, пользовательский вход в репосты отключён в UI, а сервер больше не принимает новые `TEXT_REPOST` через `AddBlock`.
|
||||
|
||||
## Что должна делать фича
|
||||
|
||||
Репост должен позволять взять сообщение из канала или треда и опубликовать его в один из своих каналов с комментарием.
|
||||
|
||||
Ожидаемый пользовательский сценарий после возврата к задаче:
|
||||
|
||||
1. Пользователь открывает сообщение в канале или треде.
|
||||
2. Нажимает `Репост`.
|
||||
3. Выбирает один из своих каналов.
|
||||
4. Добавляет комментарий.
|
||||
5. Отправляет репост.
|
||||
6. В целевом канале появляется новый пост-репост.
|
||||
7. У репоста есть переход к исходному сообщению через действие `Оригинал`.
|
||||
|
||||
## Что уже есть в коде
|
||||
|
||||
- В блокчейн-формате зарезервирован и описан подтип `TEXT_REPOST (30)`.
|
||||
- Парсер блокчейна умеет распознавать тело репоста как `TextLineBody`.
|
||||
- В UI есть функция сборки тела репоста:
|
||||
`shine-UI/js/services/auth-service.js`, `makeTextRepostBodyBytes`.
|
||||
- В UI есть клиентская операция:
|
||||
`shine-UI/js/services/auth-service.js`, `addBlockRepost`.
|
||||
- В экране канала был обработчик репоста:
|
||||
`shine-UI/js/pages/channel-view.js`, `onRepost`.
|
||||
- В экране треда был обработчик репоста:
|
||||
`shine-UI/js/pages/channel-thread-view.js`, `onRepost`.
|
||||
- Серверная выдача каналов и тредов частично учитывает `TEXT_REPOST` и target-поля:
|
||||
`targetBlockchainName`, `targetBlockNumber`, `targetBlockHash`.
|
||||
- В списке подписок `TEXT_REPOST` считается публикацией канала.
|
||||
|
||||
## Что сейчас отключено
|
||||
|
||||
- В `shine-UI/js/pages/channel-view.js` кнопка `Репост` больше не создаётся и не добавляется в список действий сообщения.
|
||||
- В `shine-UI/js/pages/channel-thread-view.js` кнопка `Репост` больше не создаётся и не добавляется в список действий ответа/сообщения треда.
|
||||
- В `shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_AddBlock_Handler.java` добавлена явная временная блокировка:
|
||||
если новый блок имеет `type=1` и `subType=TEXT_REPOST (30)`, `AddBlock` возвращает ошибку `repost_disabled`.
|
||||
|
||||
## Что осталось активным намеренно
|
||||
|
||||
- Константа `TEXT_REPOST (30)` остаётся в коде и документации как зарезервированный формат.
|
||||
- Парсер блокчейна продолжает знать формат `TEXT_REPOST`, чтобы не потерять уже написанную основу и не ломать потенциальное чтение старых тестовых данных.
|
||||
- Код формирования репоста в `auth-service.js` не удалён: его можно будет использовать как основу при возвращении к задаче.
|
||||
- Код отображения target-полей и перехода к оригиналу не удалён: он нужен для будущей проверки и возможной совместимости с уже созданными тестовыми блоками.
|
||||
|
||||
## Почему это не лежит в Pending_Features
|
||||
|
||||
`Dev_Docs/Pending_Features/` предназначена для фич, которые уже реализованы и ждут ручной проверки.
|
||||
|
||||
Репосты сейчас не подходят под этот статус: они не должны проверяться как готовая фича, потому что пользовательский сценарий временно закрыт, а серверная запись новых репостов заблокирована. Поэтому старый pending-файл удалён, а задача перенесена сюда как будущая.
|
||||
|
||||
## Что сделать при возврате к реализации
|
||||
|
||||
1. Решить, остаётся ли формат `TEXT_REPOST (30)` финальным.
|
||||
2. Если формат меняется, заранее предупредить пользователя и получить отдельное подтверждение на изменение блокчейн-формата.
|
||||
3. Вернуть UI-кнопки репоста в:
|
||||
- `shine-UI/js/pages/channel-view.js`;
|
||||
- `shine-UI/js/pages/channel-thread-view.js`.
|
||||
4. Снять временную блокировку `repost_disabled` в:
|
||||
`shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_AddBlock_Handler.java`.
|
||||
5. Проверить `auth-service.js`:
|
||||
- `makeTextRepostBodyBytes`;
|
||||
- `addBlockRepost`;
|
||||
- актуализацию вершины блокчейна перед `AddBlock`;
|
||||
- корректность target-полей исходного сообщения.
|
||||
6. Проверить серверное чтение:
|
||||
- `GetChannelMessages`;
|
||||
- `GetMessageThread`;
|
||||
- отображение `targetBlockchainName`, `targetBlockNumber`, `targetBlockHash`.
|
||||
7. Добавить или обновить тесты на успешный репост и отказ некорректных target-полей.
|
||||
8. Обновить документацию:
|
||||
- `Dev_Docs/Blockchain/11_TEXT_Blocks.md`;
|
||||
- `Dev_Docs/Blockchain/CHANGELOG.md`;
|
||||
- `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`;
|
||||
- документы API чтения каналов/тредов, если изменятся поля ответа.
|
||||
9. После реализации перенести задачу из `Dev_Docs/Future_Features/` в `Dev_Docs/Pending_Features/` как фичу, требующую ручной проверки.
|
||||
|
||||
## Минимальный чек-лист ручной проверки в будущем
|
||||
|
||||
1. Репост из сообщения канала в свой канал.
|
||||
2. Репост из ответа в треде в свой канал.
|
||||
3. Ошибка при попытке репоста в чужой канал.
|
||||
4. Переход `Оригинал` из репоста к исходному сообщению.
|
||||
5. Корректное отображение комментария к репосту.
|
||||
6. Корректная работа после перезагрузки страницы.
|
||||
7. Отсутствие поломки обычных постов, ответов, лайков и отправки ссылки.
|
||||
@ -1,62 +0,0 @@
|
||||
# Кошелёк и пополнение баланса сияния
|
||||
|
||||
- Горизонт:
|
||||
`medium`
|
||||
- Ориентир:
|
||||
среднесрочно
|
||||
- Статус:
|
||||
`proposal`
|
||||
|
||||
## Кратко
|
||||
|
||||
Нужно добавить кошелёк для внутреннего баланса сияния и пополнение этого баланса через блокчейн-логику проекта. Задача связана с регистрацией пользователя и будущим учётом баланса.
|
||||
|
||||
## Предполагаемый сценарий
|
||||
|
||||
1. Пользователь регистрируется и получает/подключает нужные кошельки.
|
||||
2. В интерфейсе появляется баланс сияния.
|
||||
3. Пользователь открывает пополнение баланса сияния.
|
||||
4. Система создаёт или принимает блокчейн-операцию пополнения.
|
||||
5. После подтверждения баланса UI обновляет значение.
|
||||
|
||||
## Что нужно продумать
|
||||
|
||||
1. Что именно является единицей баланса сияния.
|
||||
2. Где хранится состояние баланса: в существующем блокчейне SHiNE, Solana-модуле или комбинированно.
|
||||
3. Какая операция отвечает за пополнение.
|
||||
4. Нужно ли делать отдельную регистрацию кошелька сияния или использовать существующую регистрацию пользователя.
|
||||
5. Как баланс восстанавливается после перезагрузки клиента.
|
||||
6. Какие права нужны для пополнения и списания.
|
||||
7. Нужна ли история операций баланса.
|
||||
|
||||
## Вопросы перед реализацией
|
||||
|
||||
1. Пополнение баланса сияния должно идти через основной блокчейн SHiNE или через Solana-программу.
|
||||
2. Нужна ли конвертация из SOL/AR в сияние.
|
||||
3. Кто может выпускать или начислять сияние.
|
||||
4. Нужно ли поддерживать перевод сияния между пользователями.
|
||||
5. Нужны ли лимиты, комиссии или статусы подтверждения.
|
||||
6. Какой экран должен показывать баланс: регистрация, профиль, кошелёк или отдельная страница.
|
||||
7. Нужно ли отображать неподтверждённый баланс отдельно от подтверждённого.
|
||||
|
||||
## Важное ограничение
|
||||
|
||||
Если для баланса сияния потребуется новый формат блокчейн-блока или изменение существующего формата, перед реализацией нужно отдельно предупредить пользователя и получить явное подтверждение на изменение формата блокчейна.
|
||||
|
||||
Если потребуется новый серверный API или изменение существующих `op`, перед реализацией нужно отдельно предупредить пользователя и получить явное подтверждение на изменение API.
|
||||
|
||||
## Документы, которые обновить при реализации
|
||||
|
||||
- `Dev_Docs/Blockchain/`, если появятся или изменятся блоки баланса.
|
||||
- `Dev_Docs/Blockchain/CHANGELOG.md`, если меняется блокчейн-формат.
|
||||
- `Dev_Docs/API/`, если меняется серверный API.
|
||||
- `Dev_Docs/Pending_Features/` - добавить файл ручной проверки после реализации.
|
||||
- Документацию Solana-регистрации, если баланс будет связан с Solana-модулем.
|
||||
|
||||
## Минимальная проверка в будущем
|
||||
|
||||
1. Новый пользователь видит корректный начальный баланс.
|
||||
2. Пополнение создаёт правильную операцию.
|
||||
3. Баланс обновляется после подтверждения.
|
||||
4. После перезагрузки UI баланс остаётся корректным.
|
||||
5. Ошибочные или повторные операции не начисляют баланс дважды.
|
||||
@ -1,44 +0,0 @@
|
||||
# ESP32S3 как личное файловое хранилище SHiNE
|
||||
|
||||
## Горизонт
|
||||
|
||||
Среднесрочный: ближайшие недели или 1-2 месяца.
|
||||
|
||||
## Зачем нужна фича
|
||||
|
||||
Нужно проработать маленький физический сервер на ESP32S3 как персональное или доверенное файловое хранилище SHiNE.
|
||||
|
||||
Идея: при обмене сообщениями пользователи смогут использовать такой сервер для хранения своих файлов, вложений, файлов общих переписок и связанных данных.
|
||||
|
||||
## Что нужно сделать
|
||||
|
||||
- Описать роль ESP32S3-сервера в общей архитектуре ключей и сессий.
|
||||
- Определить, какие ключи может хранить такое устройство.
|
||||
- Решить, хранит ли устройство только файлы или также подписывает пользовательские операции.
|
||||
- Описать протокол загрузки, скачивания и удаления файлов.
|
||||
- Определить правила шифрования файлов до отправки на устройство.
|
||||
- Продумать индексацию файлов для личных и общих переписок.
|
||||
- Решить, как устройство авторизуется на основном сервере SHiNE.
|
||||
|
||||
## Вопросы перед реализацией
|
||||
|
||||
- ESP32S3 должен работать как полностью локальное устройство или как публично доступный мини-сервер?
|
||||
- Нужен ли внешний relay, если устройство находится за NAT?
|
||||
- Какие ограничения по размеру файла считаем допустимыми?
|
||||
- Хранит ли устройство метаданные переписок или только зашифрованные blob-файлы?
|
||||
- Как восстанавливать доступ, если устройство потеряно или заменено?
|
||||
|
||||
## Что уже сделано
|
||||
|
||||
Код не реализован. Идея зафиксирована как будущая задача после описания модели ключей.
|
||||
|
||||
## Документы, которые нужно обновить при возврате
|
||||
|
||||
- `Dev_Docs/Keys/README.md`
|
||||
- `Dev_Docs/Personal_Messages/README.md`
|
||||
- `Dev_Docs/API/`
|
||||
- `Dev_Docs/Blockchain/`, если появятся новые блоки или команды для файлов.
|
||||
|
||||
## С какого места продолжать
|
||||
|
||||
Начать с короткого протокольного документа: роли устройства, авторизация, шифрование файлов, минимальные API-операции и сценарии восстановления.
|
||||
@ -1,105 +0,0 @@
|
||||
# Сессионные саб-серверы в PDA пользователя
|
||||
|
||||
- Статус:
|
||||
`future`
|
||||
|
||||
- Горизонт:
|
||||
`medium`
|
||||
|
||||
- Ориентир:
|
||||
после завершения первого этапа по пользовательским сессиям
|
||||
|
||||
- Основание:
|
||||
Идея зафиксирована после обсуждения архитектуры пользовательских сессий и внутренних саб-серверов. Сейчас задача сознательно отложена: сначала нужно аккуратно ввести базовую модель сессий, а затем возвращаться к расширенной серверной роли.
|
||||
|
||||
## Зачем нужна фича
|
||||
|
||||
У одного пользователя может быть несколько доверенных внутренних саб-серверов, и каждый из них должен жить как отдельная пользовательская сессия, а не как отдельная особая сущность вне общей модели.
|
||||
|
||||
Это нужно, чтобы:
|
||||
|
||||
- хранить несколько саб-серверов у одного пользователя одновременно;
|
||||
- различать обычные клиентские сессии и серверные сессии по явному типу;
|
||||
- дать расширяемый формат записи с версией;
|
||||
- использовать единый подход для DM, звонков и внутренних команд между сессиями.
|
||||
|
||||
## Целевая идея
|
||||
|
||||
В пользовательском PDA должен появиться список записей сессий, где каждая запись содержит как минимум:
|
||||
|
||||
- `sessionType` (`u8`);
|
||||
- `sessionVersion` (`u8`);
|
||||
- `sessionName`;
|
||||
- `sessionPubKey`.
|
||||
|
||||
Предварительные значения:
|
||||
|
||||
- тип `1` - обычная пользовательская сессия;
|
||||
- тип `100` - саб-сервер пользователя;
|
||||
- версия `1` - первая рабочая версия формата записи сессии.
|
||||
|
||||
На текущем этапе под это уже зарезервирован отдельный блок `SessionsBlock` с `block_type = 55`, а `TrustedStateBlock` остаётся на `50`.
|
||||
|
||||
Важно: саб-серверов у одного пользователя может быть несколько.
|
||||
|
||||
## Архитектурный принцип
|
||||
|
||||
Внутренний протокол взаимодействия должен оставаться транспортным.
|
||||
|
||||
То есть SHiNE-сервер не должен разбирать прикладной смысл внутренней нагрузки саб-сервера, а должен:
|
||||
|
||||
- доставлять сообщения между сессиями;
|
||||
- доставлять сигналы звонков между сессиями;
|
||||
- хранить и маршрутизировать адресацию;
|
||||
- не принимать на себя бизнес-логику содержимого внутренних команд.
|
||||
|
||||
## Что уже подтверждается текущим кодом
|
||||
|
||||
- Личные сообщения уже доставляются по всем сессиям целевого пользователя с отдельным учётом доставки на каждую сессию.
|
||||
- Подтверждение доставки DM уже идёт отдельно по каждой сессии.
|
||||
- Вызов звонка уже рассылается по нескольким активным сессиям пользователя.
|
||||
- Сигналы звонка уже адресуются конкретной сессии, а stop-сигналы дублируются на остальные сессии того же пользователя.
|
||||
|
||||
Иными словами, текущая серверная логика ближе к модели "сервер доставляет между сессиями", чем к модели "сервер понимает внутренний протокол саб-сервера".
|
||||
|
||||
## Что нужно сделать при возврате к задаче
|
||||
|
||||
1. Согласовать финальный бинарный формат записи сессии в PDA пользователя.
|
||||
2. Проверить, не меняет ли это уже опубликованный формат пользовательской PDA-записи.
|
||||
3. Если формат PDA меняется, заранее предупредить пользователя и получить отдельное подтверждение.
|
||||
4. Решить, где именно хранится массив сессий:
|
||||
- в основной записи пользователя;
|
||||
- в отдельной PDA-структуре расширения;
|
||||
- или в смешанной схеме с базовой записью и внешними индексами.
|
||||
5. Зафиксировать ограничения:
|
||||
- максимальное число сессий;
|
||||
- максимальную длину `sessionName`;
|
||||
- правила удаления и обновления записи;
|
||||
- правила ротации `sessionPubKey`.
|
||||
6. Продумать, как UI и сервер будут отличать тип `1` и тип `100`.
|
||||
7. Определить, какие внутренние сообщения саб-сервера останутся полностью прозрачными для SHiNE-сервера, а какие потребуют только технической маршрутизации.
|
||||
8. Добавить API/операции чтения и обновления списка сессий, если для этого не хватит существующих механизмов.
|
||||
9. После реализации обязательно обновить документацию.
|
||||
|
||||
## Что нужно обновить при реализации
|
||||
|
||||
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md`
|
||||
- `Dev_Docs/Solana_Architecture/README.md`
|
||||
- `Dev_Docs/Инициализация_Solana_регистрации/README.md`
|
||||
- `Dev_Docs/Keys/README.md`
|
||||
- `Dev_Docs/Personal_Messages/README.md`, если изменится адресация DM по типам сессий
|
||||
- `Dev_Docs/API/`, если появятся новые серверные операции или изменятся ответы
|
||||
|
||||
## Что пока не делать
|
||||
|
||||
- Не включать это автоматически в основной deploy сервера.
|
||||
- Не менять сейчас Solana PDA-формат без отдельного подтверждения.
|
||||
- Не добавлять временные поля в публичный API "на всякий случай".
|
||||
|
||||
## С какого места продолжать
|
||||
|
||||
Продолжать после завершения первой части:
|
||||
|
||||
1. описать минимальный формат записи пользовательской сессии;
|
||||
2. отдельно решить, живут ли саб-серверы в том же списке, что и обычные сессии;
|
||||
3. затем уже проектировать операции регистрации, обновления и отключения таких сессий.
|
||||
@ -1,45 +0,0 @@
|
||||
# Подключение других устройств через QR
|
||||
|
||||
- Горизонт:
|
||||
`medium`
|
||||
- Ориентир:
|
||||
позже, не сейчас
|
||||
- Статус:
|
||||
`future`
|
||||
|
||||
## Зачем нужна фича
|
||||
|
||||
Нужно нормально довести подключение другого устройства через QR-код. Сейчас есть полуготовая заготовка, но сценарий работает нестабильно и требует отдельной доработки.
|
||||
|
||||
## Что уже есть
|
||||
|
||||
- В UI уже есть экраны:
|
||||
- `shine-UI/js/pages/connect-device-view.js`
|
||||
- `shine-UI/js/pages/device-qr-view.js`
|
||||
- Есть сервис переноса ключей через QR:
|
||||
- `shine-UI/js/services/qr-key-transfer-service.js`
|
||||
- Логика частично собрана, но её нельзя считать завершённой или надёжной.
|
||||
|
||||
## Что нужно будет сделать потом
|
||||
|
||||
1. Проверить и довести формат QR-передачи.
|
||||
2. Проверить сканирование и ручной ввод QR-текста.
|
||||
3. Проверить перенос `device`, `blockchain`, `root` ключей только по реальному наличию на исходном устройстве.
|
||||
4. Проверить, что после переноса очищается старая история нужного логина и не ломается вход.
|
||||
5. Отдельно проверить сценарий без `BarcodeDetector`.
|
||||
6. Довести экран подтверждения на втором устройстве.
|
||||
|
||||
## Что сейчас важно
|
||||
|
||||
- Не считать эту часть готовой.
|
||||
- Не возвращать её в активную разработку без отдельной команды пользователя.
|
||||
- Если вернёмся к задаче, сначала нужно понять, что именно уже работает, а что нет, и потом починить целиком.
|
||||
|
||||
## Что обновить при возврате
|
||||
|
||||
- `Dev_Docs/Pending_Features/README.md`
|
||||
- `shine-UI/js/pages/connect-device-view.js`
|
||||
- `shine-UI/js/pages/device-qr-view.js`
|
||||
- `shine-UI/js/services/qr-key-transfer-service.js`
|
||||
- документацию по ключам, если формат переноса меняется
|
||||
|
||||
@ -1,94 +0,0 @@
|
||||
# Telegram-агент для разрешённых игроков
|
||||
|
||||
- Горизонт:
|
||||
`near`
|
||||
- Ориентир:
|
||||
сегодня/завтра
|
||||
- Статус:
|
||||
`proposal`
|
||||
|
||||
## Кратко
|
||||
|
||||
Нужно расширить `SHiNE-agent-bot-coder`, чтобы агент мог принимать личные сообщения от заранее разрешённых пользователей, вести по каждому отдельную рабочую папку и историю, помогать им с обсуждениями/документами без изменения кода, а краткий результат публиковать в общий канал.
|
||||
|
||||
## Пользовательский сценарий
|
||||
|
||||
1. Разрешённый пользователь пишет агенту в личные сообщения текстом или голосом.
|
||||
2. Голосовое сообщение распознаётся так же, как сейчас распознаются voice/audio-задачи.
|
||||
3. Сервис определяет пользователя по разрешённому списку логинов.
|
||||
4. Для пользователя используется отдельная папка в `Players/`.
|
||||
5. Codex запускается с системным контекстом: от имени какого человека он работает, где лежит его папка, какие у него локальные инструкции.
|
||||
6. Агент может читать код и документацию проекта, но писать должен только в папку этого пользователя, если нет отдельного согласования на изменение общего проекта.
|
||||
7. После ответа пользователю агент отправляет в общий канал короткую сводку двумя сообщениями или двумя блоками: вопрос пользователя и полученный ответ.
|
||||
8. Команда `/new` или `New` сбрасывает только сессию этого пользователя.
|
||||
|
||||
## Предлагаемая структура
|
||||
|
||||
- `Players/`
|
||||
- `Ivan/`
|
||||
- `AGENTS.md`
|
||||
- `history/`
|
||||
- `files/`
|
||||
- `Sergey/`
|
||||
- `AGENTS.md`
|
||||
- `history/`
|
||||
- `files/`
|
||||
- `Milana/`
|
||||
- `AGENTS.md`
|
||||
- `history/`
|
||||
- `files/`
|
||||
|
||||
Имена папок можно уточнить после получения точных Telegram-логинов.
|
||||
|
||||
## Что нужно сделать
|
||||
|
||||
1. Добавить конфигурацию разрешённых Telegram-пользователей.
|
||||
2. Описать соответствие `telegram username -> имя игрока -> папка`.
|
||||
3. Создавать или использовать отдельную историю диалога для каждого игрока.
|
||||
4. Поддержать личные сообщения от разрешённых пользователей.
|
||||
5. Запретить постановку задач от неизвестных пользователей.
|
||||
6. Для групп/каналов оставить текущую логику: команды Айдара имеют приоритет.
|
||||
7. При запуске Codex для игрока добавлять отдельный системный контекст:
|
||||
- имя пользователя;
|
||||
- путь к его папке;
|
||||
- правило записи только в эту папку;
|
||||
- путь к персональному `AGENTS.md`.
|
||||
8. После ответа игроку отправлять краткую сводку в общий канал.
|
||||
9. Поддержать `/new`/`New` как сброс только персональной сессии игрока.
|
||||
10. Добавить защиту от случайного изменения общего кода в режиме игрока.
|
||||
|
||||
## Вопросы перед реализацией
|
||||
|
||||
1. Точные Telegram-логины Ивана, Сергея и Миланы.
|
||||
2. Какой общий канал использовать для сводок: текущий `@shine_writing` или отдельный чат.
|
||||
3. Нужно ли отправлять в общий канал полный текст вопроса/ответа или краткую выжимку.
|
||||
4. Нужно ли пересылать вложения игроков в общий канал или только текстовые сводки.
|
||||
5. Разрешить ли игрокам читать все документы проекта, включая технические заметки деплоя.
|
||||
6. Что делать, если пользователь просит изменить код: отказать, создать предложение в своей папке или просить подтверждение Айдара.
|
||||
7. Нужны ли русские имена папок (`Иван`, `Сергей`, `Милана`) или ASCII-имена (`Ivan`, `Sergey`, `Milana`).
|
||||
8. Нужно ли хранить истории игроков в общей папке сервиса или внутри `Players/<name>/history/`.
|
||||
|
||||
## Риски и ограничения
|
||||
|
||||
- Нужно аккуратно разделить режим Айдара и режим игрока, чтобы игроки не могли случайно запустить изменение общего кода.
|
||||
- Нужно не смешать истории разных пользователей.
|
||||
- Нужно ограничить публикацию в общий канал, чтобы не утекали личные или слишком длинные ответы.
|
||||
- Нужна проверка Telegram-идентификации: username может меняться, поэтому желательно хранить и `user_id`.
|
||||
|
||||
## Документы, которые обновить при реализации
|
||||
|
||||
- `SHiNE-agent-bot-coder/AGENTS.md`
|
||||
- `SHiNE-agent-bot-coder/AGENT.md`
|
||||
- `SHiNE-agent-bot-coder/README.md`
|
||||
- `Dev_Docs/deploy/agent-bot-coder-local-systemd.md`, если появятся новые переменные окружения или настройки сервиса.
|
||||
|
||||
## Минимальная проверка
|
||||
|
||||
1. Айдар по-прежнему может ставить задачи из `@shine_writing`.
|
||||
2. Неизвестный пользователь не ставит задачу в очередь.
|
||||
3. Разрешённый игрок пишет личное текстовое сообщение и получает ответ.
|
||||
4. Разрешённый игрок отправляет voice, оно распознаётся и обрабатывается.
|
||||
5. История одного игрока не попадает в историю другого.
|
||||
6. `/new` сбрасывает только историю текущего игрока.
|
||||
7. Сводка вопрос/ответ появляется в общем канале.
|
||||
8. В режиме игрока агент не пишет за пределы `Players/<name>/` без отдельного подтверждения.
|
||||
@ -1,71 +0,0 @@
|
||||
# Пополнение Solana и Arweave через внешний сервис покупки
|
||||
|
||||
- Горизонт:
|
||||
`near`
|
||||
- Ориентир:
|
||||
сегодня/завтра
|
||||
- Статус:
|
||||
`proposal`
|
||||
|
||||
## Кратко
|
||||
|
||||
Нужно добавить удобное пополнение кошельков на экране регистрации/кошелька: для Solana и Arweave дать отдельные действия `Пополнить`, которые ведут на международный сервис покупки криптовалюты с карты и помогают пользователю скопировать адрес кошелька.
|
||||
|
||||
## Пользовательский сценарий
|
||||
|
||||
1. Пользователь видит адрес кошелька Solana или Arweave.
|
||||
2. Нажимает `Пополнить`.
|
||||
3. Открывается промежуточное окно с инструкцией:
|
||||
- сейчас пользователь перейдёт на страницу покупки/пополнения;
|
||||
- нужно указать или проверить адрес кошелька;
|
||||
- после оплаты нужно закрыть внешнюю страницу и вернуться назад;
|
||||
- Solana обычно приходит быстро, ориентир 10-15 секунд после подтверждения сети;
|
||||
- Arweave может идти дольше, точное время нужно уточнить по выбранному сервису.
|
||||
4. В окне есть кнопки:
|
||||
- `Скопировать адрес и перейти`;
|
||||
- `Перейти без копирования`.
|
||||
5. Для Solana и Arweave используются разные окна/инструкции и, возможно, разные внешние ссылки.
|
||||
|
||||
## Что нужно сделать
|
||||
|
||||
1. Найти текущий экран, где показываются кошельки при регистрации и пополнении.
|
||||
2. Найти текущую ссылку покупки Arweave, если она уже есть в UI.
|
||||
3. Выбрать международный сервис покупки Solana с карты, не российский.
|
||||
4. Проверить, поддерживает ли сервис deep link с предзаполненным адресом кошелька.
|
||||
5. Если deep link невозможен, реализовать промежуточное окно с копированием адреса.
|
||||
6. Добавить отдельные действия для Solana и Arweave.
|
||||
7. Сделать текст инструкции коротким и понятным.
|
||||
8. Проверить, что адрес копируется в буфер обмена в браузере.
|
||||
9. Проверить мобильный сценарий и desktop-сценарий.
|
||||
|
||||
## Вопросы перед реализацией
|
||||
|
||||
1. Какой сервис покупки Solana использовать: тот же провайдер, что для Arweave, или другой международный on-ramp.
|
||||
2. Нужно ли разрешать покупку только SOL или также USDC/SPL-токены на Solana.
|
||||
3. Где именно показывать кнопку `Пополнить`: только регистрация, настройки кошелька или оба места.
|
||||
4. Нужно ли показывать предупреждение о комиссиях и стороннем сервисе.
|
||||
5. Нужно ли открывать внешнюю страницу в новой вкладке или в текущем окне.
|
||||
6. Нужно ли логировать факт нажатия `Пополнить` на сервере.
|
||||
7. Какой точный текст использовать для времени прихода Arweave.
|
||||
|
||||
## Риски и ограничения
|
||||
|
||||
- On-ramp-сервисы меняют ссылки и параметры, поэтому deep link нужно проверять перед реализацией.
|
||||
- Clipboard API может требовать HTTPS и пользовательский жест.
|
||||
- Нельзя обещать точное время поступления средств: лучше писать ориентир и зависимость от сети/провайдера.
|
||||
- Внешний сервис может быть недоступен в отдельных странах или для отдельных карт.
|
||||
|
||||
## Документы, которые обновить при реализации
|
||||
|
||||
- Документацию UI/кошельков, если такая есть.
|
||||
- `Dev_Docs/Pending_Features/` - добавить файл ручной проверки после реализации.
|
||||
- `Dev_Docs/API/`, только если появится новый серверный API или логирование.
|
||||
|
||||
## Минимальная проверка
|
||||
|
||||
1. На Solana-кошельке открывается правильное окно пополнения.
|
||||
2. Кнопка `Скопировать адрес и перейти` копирует Solana-адрес и открывает внешний сервис.
|
||||
3. Кнопка `Перейти без копирования` открывает внешний сервис без копирования.
|
||||
4. Аналогичный сценарий работает для Arweave.
|
||||
5. На мобильном экране текст и кнопки не перекрываются.
|
||||
6. Возврат назад в приложение не ломает состояние регистрации/кошелька.
|
||||
@ -1,175 +0,0 @@
|
||||
# Ключи SHiNE
|
||||
|
||||
Этот документ описывает роли ключей в SHiNE и их связь с Solana, персональным блокчейном, личными сообщениями, сессиями и будущими аппаратными устройствами.
|
||||
|
||||
Документ является архитектурной справкой. Он не меняет текущие форматы API, DM-блоков или блокчейна сам по себе.
|
||||
|
||||
## Коротко
|
||||
|
||||
В SHiNE у пользователя есть несколько уровней ключей:
|
||||
|
||||
- `root key` - главный корневой ключ пользователя, он же основной Solana-ключ.
|
||||
- `blockchain key` - ключ записи в персональный SHiNE-блокчейн пользователя.
|
||||
- `device key` - общий ключ пользовательских устройств для повседневной работы, звонков, DM и мелких платежей.
|
||||
- `session key` - ключ конкретной сессии или конкретного устройства для авторизации на сервере.
|
||||
|
||||
Главная идея: самые важные ключи можно держать на доверенном серверном или аппаратном устройстве, а обычные клиентские устройства получают только ключи, нужные для текущей работы.
|
||||
|
||||
## `root key`
|
||||
|
||||
`root key` - главный ключ пользователя.
|
||||
|
||||
Назначение:
|
||||
|
||||
- регистрация пользователя в Solana;
|
||||
- создание и обновление пользовательской PDA-записи;
|
||||
- вызов критически важных Solana-функций;
|
||||
- изменение главных настроек пользователя;
|
||||
- управление остальными ключами;
|
||||
- подтверждение операций, которые должны иметь максимальный уровень доверия.
|
||||
|
||||
В текущей модели `root key` совпадает по смыслу с главным Solana-ключом пользователя.
|
||||
|
||||
На `root key` могут храниться значимые средства, если пользователь сознательно выбирает такую модель. Для мелких текущих расходов предпочтительнее использовать `device key`.
|
||||
|
||||
## `blockchain key`
|
||||
|
||||
`blockchain key` - ключ записи в персональный SHiNE-блокчейн пользователя.
|
||||
|
||||
Назначение:
|
||||
|
||||
- подпись записей в персональном блокчейне пользователя;
|
||||
- подтверждение действий, которые должны попасть в SHiNE-блокчейн;
|
||||
- разделение полномочий между главным Solana-ключом и ключом ежедневной записи.
|
||||
|
||||
У пользователя может быть несколько персональных блокчейнов или веток. При смене `blockchain key` фактически создаётся новая ветка записи:
|
||||
|
||||
- `username-001` - первая ветка;
|
||||
- `username-002` - вторая ветка;
|
||||
- `username-003` - третья ветка.
|
||||
|
||||
Рабочая логика по умолчанию должна использовать последнюю актуальную ветку. Старые ветки остаются читаемыми и показывают историю смены ключей.
|
||||
|
||||
## `device key`
|
||||
|
||||
`device key` - общий ключ, который знают доверенные устройства пользователя.
|
||||
|
||||
Назначение:
|
||||
|
||||
- повседневные входящие и исходящие личные сообщения;
|
||||
- звонки и связанные с ними сообщения;
|
||||
- self-messages, то есть внутренние сообщения пользователя самому себе;
|
||||
- мелкие Solana-расходы на текущие операции;
|
||||
- derivation Arweave-кошелька;
|
||||
- оплата или подготовка добавления данных в Arweave-кошелек по отдельному протоколу.
|
||||
|
||||
Arweave-кошелёк должен выводиться из `device key` по протоколу:
|
||||
|
||||
- `Dev_Docs/Протоколы/SHINE_ARWEAVE_DERIVATION_V1.md`
|
||||
|
||||
Если пользователь теряет только `device key`, в худшем случае ломается повседневная переписка и доступ конкретных устройств к ежедневным операциям. `root key` и `blockchain key` при правильной архитектуре остаются отдельно защищёнными.
|
||||
|
||||
## `session key`
|
||||
|
||||
`session key` - уникальный ключ конкретной сессии или устройства.
|
||||
|
||||
Возможные форматы:
|
||||
|
||||
- `Ed25519` - предпочтительный современный вариант;
|
||||
- `RSA` - legacy-вариант, полезный для устройств, где системное защищённое хранилище хорошо поддерживает RSA-ключи и не позволяет извлекать приватный ключ.
|
||||
|
||||
Назначение:
|
||||
|
||||
- авторизация сессии на сервере;
|
||||
- привязка устройства к пользователю;
|
||||
- подтверждение запросов от конкретной сессии;
|
||||
- доступ к зашифрованному `device key` после успешной авторизации.
|
||||
|
||||
Одна и та же сессия может быть пригодна для подключения к нескольким серверам пользователя, если архитектура конкретного пользователя это допускает.
|
||||
|
||||
У сессии должны быть:
|
||||
|
||||
- имя сессии;
|
||||
- тип сессии;
|
||||
- публичная часть ключа;
|
||||
- ссылка на пользователя;
|
||||
- информация о сервере или серверах, которым эта сессия доверена.
|
||||
|
||||
Имя сессии может создаваться автоматически из названия устройства и короткого случайного идентификатора, например `Android-a1b2c3`, `Ubuntu-f47a90`. Пользователь может переименовать сессию.
|
||||
|
||||
## Типы сессий
|
||||
|
||||
Базовые типы:
|
||||
|
||||
- обычная пользовательская сессия;
|
||||
- серверная сессия;
|
||||
- аппаратная или доверенная сессия с доступом к расширенным ключам.
|
||||
|
||||
Обычное устройство обычно имеет:
|
||||
|
||||
- собственный `session key`;
|
||||
- зашифрованный `device key`, который открывается после авторизации;
|
||||
- доступ к DM, звонкам и обычным пользовательским операциям.
|
||||
|
||||
Доверенное серверное или аппаратное устройство может иметь:
|
||||
|
||||
- `root key`;
|
||||
- `blockchain key`;
|
||||
- `device key`;
|
||||
- собственный `session key`.
|
||||
|
||||
Такая сессия может подписывать операции повышенной важности по запросам пользователя.
|
||||
|
||||
## Внутренние self-messages
|
||||
|
||||
Self-message - это сообщение пользователя самому себе.
|
||||
|
||||
Такие сообщения нужны, чтобы обычное устройство могло попросить доверенное устройство выполнить действие:
|
||||
|
||||
- подписать запись `blockchain key` и передать её в SHiNE-блокчейн;
|
||||
- подписать изменение настройки через `root key`;
|
||||
- обновить ключи;
|
||||
- сохранить внутреннюю команду или настройку;
|
||||
- отправить сообщение другому пользователю с сохранением копии себе;
|
||||
- сохранить сообщение только себе.
|
||||
|
||||
Важно: self-message не является публичной командой сервера. Это пользовательская внутренняя команда, которую сервер или доверенное устройство обрабатывает в рамках прав конкретного пользователя.
|
||||
|
||||
## Шифрование входящих сообщений
|
||||
|
||||
Входящее сообщение может быть зашифровано:
|
||||
|
||||
- `device key`;
|
||||
- `session key`;
|
||||
- отдельным ключом конкретного чата;
|
||||
- другим ключом, который уже известен клиенту.
|
||||
|
||||
В сообщении не должно быть лишнего раскрытия того, каким именно ключом оно зашифровано. Клиент пробует расшифровать сообщение доступными ключами по порядку. Если расшифровка не удалась, сообщение остаётся непонятным для этого устройства.
|
||||
|
||||
## Копии сообщений
|
||||
|
||||
Для отправки сообщений нужны несколько режимов:
|
||||
|
||||
- сообщение другому пользователю с исходящей копией себе;
|
||||
- сообщение другому пользователю без локальной исходящей копии;
|
||||
- сообщение только себе.
|
||||
|
||||
Это должно позволить строить обычные DM, внутренние команды, личные заметки и зашифрованные пользовательские чаты поверх одной общей модели сообщений.
|
||||
|
||||
## Связанные документы
|
||||
|
||||
- `Dev_Docs/Personal_Messages/README.md` - текущая документация личных сообщений.
|
||||
- `Dev_Docs/Blockchain/README.md` - точка входа по форматам SHiNE-блокчейна.
|
||||
- `Dev_Docs/Solana_Architecture/README.md` - архитектура Solana-программ, PDA-счетов, DAO и движения средств.
|
||||
- `Dev_Docs/Инициализация_Solana_регистрации/README.md` - деплой и первичная инициализация Solana-регистрации.
|
||||
- `Dev_Docs/Протоколы/SHINE_ARWEAVE_DERIVATION_V1.md` - derivation Arweave-кошелька из `device key`.
|
||||
|
||||
## Что нужно уточнить перед реализацией
|
||||
|
||||
- точный формат записи списка ключей в Solana PDA;
|
||||
- как именно обозначать активную ветку персонального блокчейна;
|
||||
- какие операции требуют `root key`, а какие достаточно подписывать `blockchain key`;
|
||||
- формат self-message-команд;
|
||||
- порядок перебора ключей при расшифровке входящих сообщений;
|
||||
- правила ротации `device key` и восстановления доступа после потери устройства;
|
||||
- какие типы серверных и аппаратных сессий нужны в первой реализации.
|
||||
@ -1,43 +0,0 @@
|
||||
# Кошелёк: лимит/закрепление блокчейна Сияния
|
||||
|
||||
- статус: `pending`
|
||||
|
||||
## Кратко что сделано
|
||||
|
||||
- На экране `Кошелёк -> Блокчейн Сияния` добавлены 2 слоя данных:
|
||||
- фактическое состояние цепочки на сервере (`кол-во блоков`, `размер`, `крайний блок`, `hash`, `размер крайнего блока`);
|
||||
- закреплённое состояние в Solana PDA (`лимит`, `использовано`, `остаток`, `крайний блок`, `hash`).
|
||||
- Добавлены действия:
|
||||
- `Закрепить в Solana` — обновляет PDA до текущего состояния серверной цепочки;
|
||||
- `Увеличить лимит` — увеличивает `paid_limit_bytes` в PDA с учётом цены из economy PDA.
|
||||
- Если `rootKey`/`blockchainKey` не сохранены локально, экран запрашивает пароль, восстанавливает ключи через стандартную derivation-логику и предлагает сохранить их в зашифрованный контейнер.
|
||||
|
||||
## Что проверять вручную
|
||||
|
||||
1. Открыть `Кошелёк -> Блокчейн Сияния` под авторизованным пользователем.
|
||||
2. Проверить, что в блоке "Фактическое состояние на сервере" отображаются:
|
||||
- число блоков;
|
||||
- размер цепочки;
|
||||
- номер/хэш крайнего блока;
|
||||
- размер крайнего блока.
|
||||
3. Проверить, что в блоке "Закреплено в Solana" отображаются:
|
||||
- лимит;
|
||||
- израсходовано;
|
||||
- остаток;
|
||||
- номер/хэш крайнего закреплённого блока.
|
||||
4. Нажать `Закрепить в Solana` и убедиться, что:
|
||||
- приходит успешная транзакция;
|
||||
- после обновления Solana-показатели подтягиваются до серверных (или максимально близко по актуальному состоянию).
|
||||
5. Нажать `Увеличить лимит`, ввести значение кратное шагу, подтвердить списание и проверить:
|
||||
- лимит увеличился;
|
||||
- отображение цены/списания соответствует economy PDA.
|
||||
6. Повторить пункты 4-5 в сценарии, когда `rootKey`/`blockchainKey` не сохранены, и проверить:
|
||||
- появляется запрос пароля;
|
||||
- после ввода пароля операции выполняются;
|
||||
- предложение сохранить ключи показывается.
|
||||
|
||||
## Ожидаемый результат
|
||||
|
||||
- Экран корректно разделяет "фактическое состояние на сервере" и "закреплённое в Solana".
|
||||
- Обе операции (`Закрепить в Solana`, `Увеличить лимит`) выполняются без ошибок при валидных данных.
|
||||
- Восстановление ключей через пароль работает, а без нужных ключей операция не выполняется молча.
|
||||
@ -1,29 +0,0 @@
|
||||
# Озвучивание ответов агента
|
||||
|
||||
## Что сделано
|
||||
|
||||
В локальный Telegram-бот-сервис агента-кодера добавлены персональные настройки озвучивания финальных ответов:
|
||||
|
||||
- `/voice_on` включает озвучивание для текущего Telegram-пользователя;
|
||||
- `/voice_off` выключает озвучивание для текущего Telegram-пользователя;
|
||||
- `/voice_status` показывает текущее состояние;
|
||||
- если озвучивание включено, после текстового финального ответа сервис генерирует voice-файл через OpenAI TTS и отправляет его в Telegram;
|
||||
- длинные ответы делятся на несколько фрагментов озвучки.
|
||||
|
||||
## Что проверять
|
||||
|
||||
1. Перезапустить `shine-agent-bot-coder`.
|
||||
2. Отправить `/voice_status` и убедиться, что по умолчанию озвучивание выключено.
|
||||
3. Отправить `/voice_on`.
|
||||
4. Дать простую задачу агенту и проверить, что пришёл полный текстовый ответ и voice-файл с тем же ответом.
|
||||
5. Отправить `/voice_off`.
|
||||
6. Дать ещё одну простую задачу и проверить, что приходит только текст.
|
||||
7. При возможности проверить второго whitelist-пользователя: его настройка должна быть независимой.
|
||||
|
||||
## Ожидаемый результат
|
||||
|
||||
Настройка хранится персонально по username и сохраняется после перезапуска сервиса. При включённой настройке Telegram получает текстовый ответ и дополнительное voice-сообщение с озвучкой. При выключенной настройке поведение остаётся прежним.
|
||||
|
||||
## Статус
|
||||
|
||||
pending
|
||||
@ -1,19 +0,0 @@
|
||||
# Голосовая адаптация ответов Telegram-бота
|
||||
|
||||
## Краткое описание
|
||||
Добавлены персональные настройки голосовых ответов и адаптации текста перед озвучкой. Если голосовые ответы включены, сервис перед TTS может отдельно прогонять финальный текст через OpenAI-модель и отправлять более короткую голосовую версию в исходный чат, личный чат пользователя и общий чат `@shine_writing`, если эти чаты доступны и отличаются.
|
||||
|
||||
## Что проверить
|
||||
- Команды `/voice_on`, `/voice_off`, `/voice_status` для конкретного пользователя.
|
||||
- Команды `/voice_rewrite_on`, `/voice_rewrite_off`, `/voice_rewrite_status` для конкретного пользователя.
|
||||
- Команда `/status` показывает очередь, голосовые ответы и адаптацию текста перед озвучкой.
|
||||
- При включённых голосовых ответах после задачи приходит текстовый ответ и voice-ответ.
|
||||
- При включённой адаптации voice-ответ короче и без длинных технических строк.
|
||||
- При задаче из личного чата voice дополнительно появляется в общем чате `@shine_writing`.
|
||||
- При задаче из общего чата voice дополнительно появляется в личном чате пользователя, если сервис уже знает его личный chat_id.
|
||||
|
||||
## Ожидаемый результат
|
||||
Текстовый ответ остаётся полным. Голосовая версия приходит отдельно, звучит короче и естественнее, а персональные настройки одного пользователя не меняют поведение других пользователей.
|
||||
|
||||
## Статус
|
||||
pending
|
||||
@ -1,24 +0,0 @@
|
||||
# Эксперимент Understand Anything
|
||||
|
||||
## Краткое описание
|
||||
|
||||
Добавлена изолированная лаборатория для проверки `Lum1104/Understand-Anything` без подключения к сборке, деплою и рабочему коду SHiNE.
|
||||
|
||||
## Что проверять
|
||||
|
||||
- Установить Node.js 22+ и pnpm 10+.
|
||||
- Запустить `./tools/understand-anything-lab/install_codex_skills.sh`.
|
||||
- Перезапустить Codex-сессию.
|
||||
- Выполнить `/understand --language ru` в корне проекта.
|
||||
- После генерации выполнить `/understand-dashboard` и проверить, что граф открывается и помогает ориентироваться по серверным, UI, Solana и агентским папкам.
|
||||
|
||||
## Ожидаемый результат
|
||||
|
||||
- В проекте появляется локальная папка `.understand-anything/` с графом знаний.
|
||||
- Dashboard открывается и показывает интерактивный граф проекта.
|
||||
- Основные процессы сборки и деплоя SHiNE не меняются.
|
||||
|
||||
## Статус
|
||||
|
||||
`pending`
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
# Центр задач Telegram-агента
|
||||
|
||||
## Краткое описание
|
||||
|
||||
Добавлена первая версия центра задач и предложений внутри `SHiNE-agent-bot-coder`.
|
||||
|
||||
Бот хранит задачи и предложения в JSON-файле данных сервиса, умеет показывать список через `/tasks`, создавать задачи для игроков по фразе Айдара, принимать предложения игроков по префиксу `предложение:`, менять статусы и добавлять короткие напоминания после ответов.
|
||||
|
||||
## Что проверять
|
||||
|
||||
- Айдар пишет `/tasks` и видит текущий список задач и предложений без уже закрытого предложения от Димы.
|
||||
- Айдар пишет `поставь задачу Милане: проверить описание SHiNE` и задача появляется в списке Миланы.
|
||||
- Милана пишет `/tasks` и видит назначенную задачу.
|
||||
- Игрок пишет `предложение: ...`, после чего предложение появляется у Айдара.
|
||||
- Айдар меняет статус фразами вида `одобрить TC-XXXX`, `доработать TC-XXXX`, `закрыть TC-XXXX`, где `TC-XXXX` - ID существующей задачи или предложения.
|
||||
- После обычного ответа бота Айдару или игроку появляется короткое напоминание, если у пользователя есть активные задачи.
|
||||
|
||||
## Ожидаемый результат
|
||||
|
||||
Задачи и предложения сохраняются между перезапусками сервиса, статусы меняются корректно, напоминания не мешают основному ответу Codex.
|
||||
|
||||
## Статус
|
||||
|
||||
pending
|
||||
@ -1,31 +0,0 @@
|
||||
# Рестарты и voice-настройки Telegram-агента
|
||||
|
||||
## Краткое описание
|
||||
|
||||
Добавлена первая версия безопасного рестарта Telegram-агента:
|
||||
|
||||
- `/restart` и `/restart_service` ставят отложенный рестарт после текущей задачи и до взятия следующей;
|
||||
- `/restart_hard`, `/restart_now`, `/restart_force` выполняют жёсткий рестарт сразу;
|
||||
- команды рестарта доступны только Айдару;
|
||||
- voice-ответы включены по умолчанию для новых пользователей;
|
||||
- адаптация текста перед озвучкой стала ближе к исходному ответу и не должна менять смысл;
|
||||
- скрыты отдельные команды статуса voice-функций из справки, состояние показывается через `/status`.
|
||||
|
||||
## Что проверить
|
||||
|
||||
1. Отправить `/restart` во время активной задачи игрока или Айдара.
|
||||
2. Убедиться, что активная задача завершается, после чего сервис перезапускается до следующей задачи.
|
||||
3. Отправить `/restart_hard` и убедиться, что сервис перезапускается сразу.
|
||||
4. Проверить, что игрок не может выполнить команды рестарта.
|
||||
5. Проверить `/status`: он показывает очередь и состояния голосовых функций.
|
||||
6. Проверить нового пользователя: voice-ответы должны быть включены по умолчанию.
|
||||
7. Проверить текстовый запрос пользователя с включённым voice: после текстового ответа должен прийти voice-файл.
|
||||
8. Проверить, что адаптированная озвучка не превращается в другой ответ, а только убирает длинные технические строки.
|
||||
|
||||
## Ожидаемый результат
|
||||
|
||||
Сервис можно обновлять без потери текущей задачи через отложенный рестарт. Жёсткий рестарт остаётся аварийной командой Айдара. Voice-ответы работают для текстовых и голосовых запросов, а голосовая версия остаётся близкой к текстовой.
|
||||
|
||||
## Статус
|
||||
|
||||
pending
|
||||
@ -1,26 +0,0 @@
|
||||
# Кнопки вкладки «Каналы»
|
||||
|
||||
## Что сделано
|
||||
|
||||
Доработана верхняя панель вкладки «Каналы»:
|
||||
- при открытии нижней кнопкой «Каналы» показывается режим «Все каналы»;
|
||||
- в режиме «Все каналы» справа доступны кнопка «Мои каналы» и иконка поиска канала;
|
||||
- в режиме «Мои каналы» доступен переход обратно во «Все каналы», а справа показывается плюсик создания канала.
|
||||
|
||||
## Что проверить
|
||||
|
||||
1. Открыть вкладку «Каналы» через нижнюю навигацию.
|
||||
2. Убедиться, что открыт режим «Все каналы», а плюсик создания канала не отображается.
|
||||
3. Нажать иконку поиска в режиме «Все каналы».
|
||||
4. Убедиться, что открывается текущий сценарий поиска каналов.
|
||||
5. Нажать «Мои каналы».
|
||||
6. Убедиться, что справа появился плюсик создания канала.
|
||||
7. Нажать «Все каналы» или стрелку назад и проверить возврат к режиму «Все каналы».
|
||||
|
||||
## Ожидаемый результат
|
||||
|
||||
Кнопки верхней панели соответствуют активному режиму: поиск в «Все каналы», создание только в «Мои каналы».
|
||||
|
||||
## Статус
|
||||
|
||||
pending
|
||||
@ -1,13 +0,0 @@
|
||||
# Длинные voice/audio в Telegram-боте агента
|
||||
|
||||
- краткое описание фичи:
|
||||
Бот теперь умеет обрабатывать длинные voice/audio аккуратнее: учитывает лимит Telegram Bot API на скачивание слишком больших файлов, поддерживает альтернативный `TELEGRAM_API_BASE_URL` для локального `telegram-bot-api`, локально пережимает длинное аудио через `ffmpeg`, режет на куски и отправляет их в OpenAI transcription последовательно.
|
||||
- что именно проверять:
|
||||
1. Короткий `voice` по-прежнему распознаётся без заметной задержки.
|
||||
2. Длинный `audio/voice`, который помещается в скачивание Telegram, успешно пережимается, режется на части и даёт цельную расшифровку.
|
||||
3. Очень большой файл через обычный `https://api.telegram.org` даёт понятное сообщение про лимит Telegram.
|
||||
4. После переключения на локальный `telegram-bot-api` такой же большой файл начинает скачиваться и распознаваться.
|
||||
- ожидаемый результат:
|
||||
Бот не падает на длинных аудио, даёт либо расшифровку, либо понятное объяснение, какой именно лимит мешает и что нужно включить.
|
||||
- статус:
|
||||
pending
|
||||
@ -1,14 +0,0 @@
|
||||
# Диагностика больших voice/audio в Telegram-боте
|
||||
|
||||
- краткое описание фичи:
|
||||
- Бот при большом voice/audio больше не отказывается заранее по метаданным Telegram. Теперь он сначала сообщает, что пробует скачать файл, затем отдельно сообщает об успешном скачивании и только после этого переходит к подготовке аудио и распознаванию через OpenAI.
|
||||
- что именно проверять:
|
||||
- Отправить в бота большой `voice` или `audio`, который раньше попадал под ранний отказ.
|
||||
- Проверить, что сначала приходит сообщение о попытке скачать большой файл.
|
||||
- Проверить два сценария:
|
||||
- скачивание удалось: бот пишет об успешной загрузке и продолжает распознавание;
|
||||
- скачивание не удалось: бот пишет именно о неудачном скачивании из Telegram, без ложной привязки к ошибке OpenAI.
|
||||
- ожидаемый результат:
|
||||
- Пользователь видит понятную поэтапную диагностику: попытка скачивания, результат скачивания и только потом следующий этап обработки.
|
||||
- статус:
|
||||
- pending
|
||||
@ -1,24 +0,0 @@
|
||||
# Перенос server UI в shine-UI
|
||||
|
||||
- краткое описание фичи:
|
||||
Веб-панель управления серверной Solana PDA перенесена в `shine-UI/` как отдельные страницы.
|
||||
Новая точка входа: `shine-UI/server-ui.html`.
|
||||
Общая логика работы с PDA вынесена в единый модуль `shine-UI/js/services/shine-user-pda-service.js`.
|
||||
|
||||
- что именно проверять:
|
||||
1. Открытие `shine-UI/server-ui.html` и переходы на страницы создания и обновления PDA.
|
||||
2. Генерацию ключей из логина и пароля на странице создания.
|
||||
3. Ручной ввод base58-ключей и регистрацию серверного PDA.
|
||||
4. Загрузку существующей серверной PDA на странице обновления.
|
||||
5. Обновление `server_address` и `sync_servers` только по `root + device` без blockchain-ключа.
|
||||
6. Корректное чтение нового формата `ServerProfileBlock` через общий PDA-модуль.
|
||||
7. То, что актуальной точкой входа остаётся `shine-UI/server-ui.html`.
|
||||
|
||||
- ожидаемый результат:
|
||||
1. Новые страницы открываются без JS-ошибок.
|
||||
2. Создание серверной PDA проходит через общий модуль и пишет актуальный формат.
|
||||
3. Обновление серверной PDA переиспользует существующую подпись LastBlockState и не требует blockchain-ключ.
|
||||
4. Клиентский UI не ломается после перевода общего PDA-слоя на новый формат.
|
||||
|
||||
- статус:
|
||||
pending
|
||||
@ -1,20 +0,0 @@
|
||||
# Кнопка настройки сервера и DEVNET topup
|
||||
|
||||
- краткое описание фичи:
|
||||
На экране `entry-settings-view` добавлена кнопка `Настроить свой сервер`, открывающая `server-ui.html` в новой вкладке.
|
||||
На страницах серверного UI добавлена кнопка открытия `devnet-topup-view` в новой вкладке с автоматической передачей `wallet` из device-адреса.
|
||||
|
||||
- что именно проверять:
|
||||
1. На странице настроек входа есть кнопка `Настроить свой сервер`.
|
||||
2. Кнопка открывает `shine-UI/server-ui.html` в новой вкладке.
|
||||
3. На страницах `create-server-pda.html` и `update-server-pda.html` есть кнопка `Открыть пополнение DEVNET`.
|
||||
4. Если device public key заполнен, новая вкладка открывает `devnet-topup-view?wallet=...` с правильным адресом.
|
||||
5. Если device-адрес не введён, серверный UI показывает понятную ошибку и не открывает пустую ссылку.
|
||||
|
||||
- ожидаемый результат:
|
||||
1. Переход в серверный UI с клиентской страницы настроек работает.
|
||||
2. Пополнение devnet из серверного UI открывается сразу на нужный адрес.
|
||||
3. Основной клиентский UI и серверные страницы не получают JS-ошибок при загрузке.
|
||||
|
||||
- статус:
|
||||
pending
|
||||
@ -1,15 +0,0 @@
|
||||
# Фикс DEVNET topup и автоподстановки пароля
|
||||
|
||||
- статус: pending
|
||||
- кратко: исправлена ширина экрана `devnet-topup-view` после успешного пополнения и отключена нежелательная автоподстановка пароля в server UI и на экранах входа/регистрации.
|
||||
|
||||
## Что проверять
|
||||
- Открыть страницу пополнения DEVNET, выполнить пополнение и убедиться, что после появления `Signature` экран не расширяется по ширине.
|
||||
- Проверить, что кнопки на странице пополнения остаются аккуратными и не разъезжаются.
|
||||
- Открыть `server-ui/update-server-pda.html`, загрузить PDA и убедиться, что поле пароля остаётся пустым.
|
||||
- Проверить обычные экраны входа и регистрации: поле пароля не должно самопроизвольно заполняться длинной строкой.
|
||||
|
||||
## Ожидаемый результат
|
||||
- Длинная transaction signature переносится по строкам внутри прежней ширины экрана.
|
||||
- Кнопки сохраняют компактный mobile-first layout.
|
||||
- Поля пароля пустые, пока пользователь сам ничего не вводил.
|
||||
@ -1,17 +0,0 @@
|
||||
# Диагностика ключей server PDA и баланс device
|
||||
|
||||
- статус: pending
|
||||
- кратко: на странице обновления server PDA добавлена сверка ожидаемых ключей с уже загруженной PDA, предупреждение о неверном пароле, кнопка показа баланса device-аккаунта и уточнение, что create/update оплачиваются с deviceKey.
|
||||
|
||||
## Что проверять
|
||||
- На `update-server-pda.html` загрузить существующую PDA и убедиться, что видны ожидаемые `root/blockchain/device` public key.
|
||||
- Ввести правильный пароль и нажать `Сгенерировать`: должно появиться сообщение, что ключи совпадают.
|
||||
- Ввести неверный пароль и нажать `Сгенерировать`: должно появиться сообщение, что ключи не совпали и пароль, вероятно, неверный.
|
||||
- На `create-server-pda.html` и `update-server-pda.html` нажать `Показать / обновить баланс device` и убедиться, что баланс читается по текущему `devPub`.
|
||||
- Повторить `update_user_pda` после увеличения `heap frame` и проверить, ушла ли ошибка `memory allocation failed`.
|
||||
|
||||
## Ожидаемый результат
|
||||
- Пользователь видит, какие именно public key должны получиться для загруженной PDA.
|
||||
- Ошибка неправильного пароля выявляется до отправки транзакции.
|
||||
- Баланс device-кошелька читается прямо со страницы.
|
||||
- Если проблема `OOM` была только в размере heap frame/compute budget клиента, `update_user_pda` начинает проходить.
|
||||
@ -1,15 +0,0 @@
|
||||
# Lazy-import Solana PDA: актуальный формат
|
||||
|
||||
- Краткое описание:
|
||||
Серверный Java lazy-import пользователя из `shine_users` обновлён под актуальный формат `user_pda`. Убран RPC-фильтр по размеру PDA, добавлен разбор нового `ServerProfileBlock` (`block_type = 30`) без сохранения server-only полей в `solana_users`.
|
||||
- Что проверять:
|
||||
1. Взять логин пользователя, который существует в Solana PDA, но отсутствует в локальной таблице `solana_users`.
|
||||
2. Выполнить вход этим логином через сервер.
|
||||
3. Убедиться, что lazy-import подтянул пользователя из Solana.
|
||||
4. Убедиться, что запись в `solana_users` создана с полями `login`, `blockchain_name`, `solana_key`, `blockchain_key`, `device_key`.
|
||||
5. Убедиться, что отсутствие/наличие server-полей в PDA не ломает импорт.
|
||||
- Ожидаемый результат:
|
||||
1. Пользователь успешно находится и импортируется из Solana PDA независимо от фактического размера PDA.
|
||||
2. Новый `ServerProfileBlock` не ломает парсер.
|
||||
3. В БД не появляются лишние server-only поля.
|
||||
- Статус: `pending`
|
||||
@ -1,33 +0,0 @@
|
||||
# Pure Rust `shine_users` и `shine_login_guard`
|
||||
|
||||
Статус: `pending`
|
||||
|
||||
## Что сделано
|
||||
|
||||
- `shine_login_guard` переписан без Anchor на чистый Rust/Solana SDK.
|
||||
- `shine_users` переписан без Anchor на чистый Rust/Solana SDK.
|
||||
- Для `shine_users` введён новый instruction ABI без Anchor discriminator'ов.
|
||||
- Для `shine_users` используются новые seed'ы:
|
||||
- `user_login=` для `user_pda`
|
||||
- `shine_users_economy_config` для economy PDA
|
||||
- Формат блоков PDA синхронизирован:
|
||||
- `SessionsBlock = 50`
|
||||
- `TrustedStateBlock = 70`
|
||||
- UI JS-модуль и Java lazy-import обновлены под новые seeds/ABI/коды блоков.
|
||||
|
||||
## Что проверить руками
|
||||
|
||||
1. В обычном UI выполнить регистрацию нового пользователя в Solana.
|
||||
2. Проверить, что после регистрации читается новая `user_pda`.
|
||||
3. В server UI выполнить создание server PDA.
|
||||
4. В server UI выполнить update server PDA.
|
||||
5. Проверить, что после update растёт `record_number`.
|
||||
6. Проверить, что lazy-import на сервере читает новый формат PDA без ошибок.
|
||||
7. Проверить, что старые Anchor discriminator'ы больше нигде не требуются.
|
||||
|
||||
## Ожидаемый результат
|
||||
|
||||
- Регистрация и update работают на новых чисто-rust программах.
|
||||
- UI не использует старый Anchor ABI.
|
||||
- Серверный Java parser читает новый формат PDA.
|
||||
- Ошибок `out of memory` и anchor-specific падений больше нет.
|
||||
@ -1,25 +0,0 @@
|
||||
# ESP32 Argon2/UI совместимость и экран результата
|
||||
|
||||
- краткое описание фичи:
|
||||
выравнивание derivation на `ESP32` с текущим `UI` по нормализации логина, совместимости `master secret`/`root.key`/`bch.key`/`dev.key`, а также правки экрана результата и progress bar.
|
||||
|
||||
- что именно проверять:
|
||||
1. На `UI` и `ESP32` ввести один и тот же логин в разном регистре, например `Anya24`, и один и тот же непустой пароль.
|
||||
2. Убедиться, что после нормализации логина на `ESP32` и `UI` получаются одинаковые:
|
||||
`master secret`, `root`, `blockchain`, `device` в `Base58`.
|
||||
3. Проверить режим пустого пароля:
|
||||
`UI` и `ESP32` должны выдать одинаковые ключи в legacy-режиме.
|
||||
4. Проверить, что пустой логин на `ESP32` не запускает расчёт и показывает сообщение об ошибке.
|
||||
5. Проверить progress bar:
|
||||
при непустом пароле полоса должна быть видна и двигаться.
|
||||
6. Проверить экран результата:
|
||||
сначала `Login`, затем `Password`, затем `Master secret` и ключи;
|
||||
свайп вверх/вниз должен прокручивать длинный результат без артефактов.
|
||||
|
||||
- ожидаемый результат:
|
||||
`ESP32` и `UI` считают одинаковый `master secret` и одинаковые ключи для одинаковых входных данных;
|
||||
progress bar виден;
|
||||
экран результата читаемый и корректно прокручивается.
|
||||
|
||||
- статус:
|
||||
pending
|
||||
@ -1,17 +0,0 @@
|
||||
## Краткое описание
|
||||
В `SHiNE-agent-bot-coder` для личных чатов добавлен режим одного редактируемого статусного сообщения. Бот принимает запрос, обновляет это сообщение по этапам обработки и в конце превращает его в финальный текстовый ответ. При длинном ответе допускается ещё одно дополнительное текстовое сообщение с продолжением. Голосовой ответ остаётся отдельным сообщением.
|
||||
|
||||
## Что проверять
|
||||
1. Отправить в личный чат короткий текстовый запрос и убедиться, что бот не шлёт цепочку промежуточных сообщений, а редактирует одно сообщение до финального ответа.
|
||||
2. Отправить в личный чат `voice` или `audio` и убедиться, что в том же сообщении последовательно видны этапы распознавания и выполнения.
|
||||
3. Проверить длинный ответ, который не помещается в один Telegram message: должно получиться не больше двух текстовых сообщений.
|
||||
4. Проверить, что `voice`-ответ приходит отдельным новым сообщением после текстового.
|
||||
5. Проверить, что в `@shine_writing` по-прежнему логируются только итоговые `вопрос -> ответ`, без промежуточных статусов.
|
||||
|
||||
## Ожидаемый результат
|
||||
- В личке основная переписка стала чище: промежуточные этапы живут в одном редактируемом сообщении.
|
||||
- При длинном ответе бот не разбрасывает ответ на много сообщений.
|
||||
- Канал `@shine_writing` работает по старой схеме без лишнего шума.
|
||||
|
||||
## Статус
|
||||
`pending`
|
||||
@ -1,24 +0,0 @@
|
||||
## Краткое описание
|
||||
В локальный Telegram-бот `SHiNE-agent-bot-coder` добавлена команда `/settings`, которая сразу показывает текущие персональные настройки пользователя и список доступных команд для их изменения. В `/help` оставлена только ссылка на `/settings` без перечисления самих команд настроек. Также добавлен переключатель режима ответа в личке: один редактируемый статус или отдельные сообщения по этапам.
|
||||
|
||||
## Что проверять
|
||||
1. Отправить `/help` и убедиться, что в справке есть `/settings`, но нет списка команд `/voice_*` и `/single_message_*`.
|
||||
2. Отправить `/settings` и проверить, что бот показывает текущие значения:
|
||||
- озвучивание финальных ответов;
|
||||
- адаптацию текста перед озвучкой;
|
||||
- режим одного редактируемого сообщения в личке.
|
||||
3. По очереди переключить:
|
||||
- `/voice_on` и `/voice_off`;
|
||||
- `/voice_rewrite_on` и `/voice_rewrite_off`;
|
||||
- `/single_message_on` и `/single_message_off`.
|
||||
4. После каждого переключения снова вызвать `/settings` и убедиться, что статус изменился и сохранился.
|
||||
5. При `/single_message_on` отправить обычный запрос в личку и проверить, что бот ведёт его через одно редактируемое сообщение.
|
||||
6. При `/single_message_off` отправить обычный запрос в личку и проверить, что бот снова шлёт отдельные сообщения по этапам и отдельный финальный ответ.
|
||||
|
||||
## Ожидаемый результат
|
||||
- `/settings` стал основной точкой входа для пользовательских настроек.
|
||||
- `/help` стал короче и не дублирует список команд настроек.
|
||||
- Режим ответа в личке реально переключается персонально для пользователя и сохраняется после перезапуска сервиса.
|
||||
|
||||
## Статус
|
||||
`pending`
|
||||
@ -1,210 +0,0 @@
|
||||
# Shine Payments: e2e после переписи без Anchor и добавления Q3
|
||||
|
||||
## Краткое описание
|
||||
|
||||
Нужно вручную и через вспомогательные CLI-проверки подтвердить, что программа `shine_payments` после:
|
||||
|
||||
- переписи на чистый `solana_program`;
|
||||
- отказа от `programs/common`;
|
||||
- добавления очереди `Q3`;
|
||||
- обновления HTML UI;
|
||||
|
||||
корректно работает на devnet с новым `program id`.
|
||||
|
||||
Отличие от финального боевого сценария:
|
||||
|
||||
- вместо DAO-механики используется обычный кошелёк `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P`, которому даны права DAO на изменение коэффициента и выдачу лимитов менеджеру.
|
||||
|
||||
## Что именно проверять
|
||||
|
||||
### 1. Подготовка окружения
|
||||
|
||||
Проверить и зафиксировать:
|
||||
|
||||
- новый keypair программы `shine_payments`;
|
||||
- новый `program id`;
|
||||
- обновление `program id` в HTML UI и связанных настройках;
|
||||
- наличие deploy authority, которой можно закрыть старый buffer/programdata, если это технически доступно;
|
||||
- адреса тестовых кошельков:
|
||||
- DAO/базовый кошелёк;
|
||||
- менеджер;
|
||||
- покупатель 1;
|
||||
- покупатель 2;
|
||||
- получатели выплат.
|
||||
|
||||
### 2. Очистка/смена старой программы
|
||||
|
||||
Проверить один из сценариев:
|
||||
|
||||
- если возможно, закрыть старый `program buffer/programdata` текущими ключами;
|
||||
- если закрытие невозможно или нецелесообразно, зафиксировать это и продолжить с новым `program id`.
|
||||
|
||||
Отдельно проверить, что старые PDA предыдущей версии не используются новой программой.
|
||||
|
||||
### 3. Деплой и init новой программы
|
||||
|
||||
Проверить:
|
||||
|
||||
- `cargo build-sbf` проходит;
|
||||
- новая программа деплоится на devnet;
|
||||
- `init` выполняется один раз на пустых PDA;
|
||||
- после `init` читаются:
|
||||
- `config`;
|
||||
- `coef_limit`;
|
||||
- `queues`;
|
||||
- `inflow_vault`.
|
||||
|
||||
Сразу после `init` запросить состояние очередей и зафиксировать, что:
|
||||
|
||||
- `Q1`, `Q2`, `Q3` пустые;
|
||||
- `tickets_total = 0`;
|
||||
- `tickets_paid = 0`;
|
||||
- все суммы равны `0`.
|
||||
|
||||
### 4. Проверка покупки билета
|
||||
|
||||
На минимальных суммах проверить:
|
||||
|
||||
1. покупку через `buy_ticket_usd`;
|
||||
2. покупку через `buy_ticket_sol`;
|
||||
3. при необходимости ещё один вызов базового `buy_ticket`.
|
||||
|
||||
После каждой покупки:
|
||||
|
||||
- запросить состояние `Q1`;
|
||||
- убедиться, что создался следующий ticket;
|
||||
- проверить рост:
|
||||
- `q1_tickets_total`;
|
||||
- `q1_sum_total_usd_cents`;
|
||||
- убедиться, что деньги покупки ушли в `dao_wallet`, а не в `inflow_vault`.
|
||||
|
||||
### 5. Проверка DAO-управления
|
||||
|
||||
Проверить:
|
||||
|
||||
1. изменение коэффициента через `update_coef_limit`;
|
||||
2. повторный запрос `coef_limit` и подтверждение нового значения;
|
||||
3. выдачу менеджеру прав через `grant_manager_limits`:
|
||||
- отдельно под `Q1`;
|
||||
- отдельно под `Q2`;
|
||||
- отдельно под `Q3`.
|
||||
|
||||
После выдачи лимитов:
|
||||
|
||||
- считать `manager_allowance_pda`;
|
||||
- убедиться, что лимиты записаны отдельно по трём очередям.
|
||||
|
||||
### 6. Проверка manager_add_ticket
|
||||
|
||||
На минимальных суммах создать менеджерские тикеты:
|
||||
|
||||
1. один ticket в `Q1`;
|
||||
2. один ticket в `Q2`;
|
||||
3. один ticket в `Q3`.
|
||||
|
||||
После каждого добавления:
|
||||
|
||||
- запросить состояние очередей;
|
||||
- проверить рост счётчиков и сумм именно у нужной очереди;
|
||||
- проверить уменьшение соответствующего manager allowance.
|
||||
|
||||
### 7. Проверка приоритета очередей
|
||||
|
||||
Подтвердить очередность `step_payout`:
|
||||
|
||||
1. сначала выплачивается `Q1`;
|
||||
2. затем `Q2`;
|
||||
3. затем `Q3`.
|
||||
|
||||
Для этого:
|
||||
|
||||
- между шагами регулярно читать `queues`;
|
||||
- фиксировать, какой именно ticket был следующим к выплате;
|
||||
- убедиться, что при наличии pending в `Q1` программа не уходит в `Q2` или `Q3`.
|
||||
|
||||
### 8. Проверка частичных выплат
|
||||
|
||||
Перед выплатами пополнять `inflow_vault` только минимально достаточными суммами.
|
||||
|
||||
Нужно проверить:
|
||||
|
||||
1. частичную серию выплат, когда часть тикетов ещё остаётся pending;
|
||||
2. дополнительную покупку билета в промежутке между выплатами;
|
||||
3. повторную проверку приоритета после появления нового билета в `Q1`.
|
||||
|
||||
После каждого `step_payout`:
|
||||
|
||||
- запрашивать состояние очередей;
|
||||
- проверять:
|
||||
- рост `tickets_paid`;
|
||||
- рост `sum_paid_usd_cents`;
|
||||
- `is_paid = true` у погашенного ticket;
|
||||
- правильный DAO multiplier:
|
||||
- `Q1 -> 1x`;
|
||||
- `Q2 -> 2x`;
|
||||
- `Q3 -> 3x`.
|
||||
|
||||
### 9. Проверка финального добора
|
||||
|
||||
После частичных выплат:
|
||||
|
||||
- купить ещё один билет;
|
||||
- допополнить `inflow_vault`;
|
||||
- выполнить оставшиеся `step_payout` до полного погашения всех трёх очередей.
|
||||
|
||||
В конце:
|
||||
|
||||
- все pending ticket должны отсутствовать;
|
||||
- все суммы paid должны совпасть с total по каждой очереди;
|
||||
- если вызвать `step_payout` на пустых очередях, доступный остаток `inflow_vault` должен уйти в `dao_wallet`.
|
||||
|
||||
### 10. Финальный возврат лампортов
|
||||
|
||||
После завершения теста вернуть все доступные остатки, которые можно вернуть текущими полномочиями, на базовый кошелёк:
|
||||
|
||||
- `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P`
|
||||
|
||||
Отдельно зафиксировать:
|
||||
|
||||
- что именно удалось вернуть;
|
||||
- что именно нельзя вернуть без специальной инструкции закрытия или без deploy authority.
|
||||
|
||||
## Ожидаемый результат
|
||||
|
||||
- `buy_ticket_usd` и `buy_ticket_sol` создают ticket без ошибок чтения state;
|
||||
- `Q3` работает наравне с `Q2`, но с третьим приоритетом;
|
||||
- DAO может менять коэффициент и выдавать лимиты;
|
||||
- менеджер может создавать билеты во все три очереди;
|
||||
- `step_payout` соблюдает порядок `Q1 -> Q2 -> Q3`;
|
||||
- DAO-множитель на выплатах равен `1x/2x/3x` для `Q1/Q2/Q3`;
|
||||
- HTML UI и on-chain программа используют один и тот же актуальный `program id`;
|
||||
- остатки средств после теста по максимуму возвращены на базовый DAO-кошелёк.
|
||||
|
||||
## Статус
|
||||
|
||||
- `done`
|
||||
|
||||
## Итог выполнения
|
||||
|
||||
- новый `shine_payments` задеплоен в devnet с `program id`:
|
||||
- `c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW`
|
||||
- старый `shine_payments`:
|
||||
- `m48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR`
|
||||
- закрыт, лампорты возвращены на базовый DAO-кошелёк
|
||||
- HTML UI переведён на новый `program id`
|
||||
- подтверждены:
|
||||
- `init`
|
||||
- `buy_ticket_usd`
|
||||
- `buy_ticket_sol`
|
||||
- `grant_manager_limits`
|
||||
- `manager_add_ticket` для `Q1/Q2/Q3`
|
||||
- `change_ticket_recipient`
|
||||
- `update_coef_limit`
|
||||
- `step_payout` по порядку `Q1 -> Q2 -> Q3`
|
||||
- повторный возврат приоритета в `Q1` после новой покупки
|
||||
- итоговые агрегаты очередей:
|
||||
- `Q1 total=4, paid=4, sum_total=780, sum_paid=780`
|
||||
- `Q2 total=1, paid=1, sum_total=60, sum_paid=60`
|
||||
- `Q3 total=1, paid=1, sum_total=70, sum_paid=70`
|
||||
- временные тестовые кошельки собраны обратно в базовый DAO-кошелёк
|
||||
- в `inflow_vault` остался только rent-минимум PDA
|
||||
@ -1,30 +0,0 @@
|
||||
# Клиентская Solana-регистрация после ухода от Anchor
|
||||
|
||||
## Краткое описание
|
||||
|
||||
Исправлен рассинхрон обычного клиентского UI с no-Anchor ABI программ:
|
||||
|
||||
- `shine_login_guard`
|
||||
- `shine_users`
|
||||
|
||||
Исправлены клиентские вызовы:
|
||||
|
||||
1. Solana-предпроверка логина в обычном UI.
|
||||
2. `init_users_economy_config` в обычном UI.
|
||||
|
||||
## Что проверять
|
||||
|
||||
1. На странице регистрации проверка свободного логина не выдаёт `InvalidInstructionData`.
|
||||
2. Для свободного обычного логина отображается корректный статус без fallback-предупреждения про недоступную Solana-предпроверку.
|
||||
3. Регистрация пользователя через обычный UI проходит до конца.
|
||||
4. Страница `Solana: init регистрации` в обычном UI отправляет корректную транзакцию и не падает из-за старого Anchor discriminator.
|
||||
|
||||
## Ожидаемый результат
|
||||
|
||||
1. `shine_login_guard` принимает клиентский precheck.
|
||||
2. `init_users_economy_config` из обычного UI совместим с текущей программой `shine_users`.
|
||||
3. Обычный клиентский UI ведёт себя так же, как серверный UI, там где используется общий no-Anchor путь.
|
||||
|
||||
## Статус
|
||||
|
||||
- `pending`
|
||||
@ -1,20 +0,0 @@
|
||||
# Недопроверенные фичи
|
||||
|
||||
Эта папка хранит список доработок, которые уже реализованы, но ещё не подтверждены ручной проверкой.
|
||||
|
||||
## Как использовать
|
||||
|
||||
1. При каждом коммите с новыми пользовательскими фичами (если нужна ручная проверка) добавить новый файл:
|
||||
- формат: `YYYY-MM-DD_HHMM_<short-feature-name>.md`
|
||||
- название `<short-feature-name>` и текст файла по возможности писать на русском языке
|
||||
2. В файле указать:
|
||||
- что сделано;
|
||||
- как проверять;
|
||||
- ожидаемый результат;
|
||||
- текущий статус (`pending` / `in_progress` / `done`).
|
||||
3. После подтверждения работоспособности — удалить файл фичи из этой папки.
|
||||
|
||||
## Важно
|
||||
|
||||
- `README.md` не удаляется.
|
||||
- Количество недопроверенных фич = число файлов `*.md` в этой папке, кроме `README.md`.
|
||||
@ -1,269 +0,0 @@
|
||||
# Личные сообщения (DM): как это устроено
|
||||
|
||||
## Коротко (для быстрого понимания)
|
||||
|
||||
Личные сообщения в SHiNE сейчас работают как пара **подписанных клиентом блоков** в формате `SHiNE_dm2`:
|
||||
|
||||
- тип `1` — входящее сообщение для собеседника;
|
||||
- тип `2` — исходящая копия того же сообщения для автора.
|
||||
|
||||
Оба блока отправляются вместе одной операцией (`SendMessagePair` / `ReceiveOutcomingMessage`) и либо сохраняются оба, либо не сохраняются вовсе.
|
||||
Дальше сервер доставляет их по активным сессиям целевого логина событием `SignedMessageArrived`, а клиент подтверждает доставку на конкретную сессию через `AckSessionDelivery`.
|
||||
|
||||
Подтверждение прочтения также идёт парой блоков:
|
||||
|
||||
- тип `3` — «прочитано» для исходящего сообщения автора;
|
||||
- тип `4` — зеркальная копия для второй стороны.
|
||||
|
||||
UI чата строится на этих типах: текстовые сообщения (1/2), read-receipt (3/4), непрочитанные, галочки и история.
|
||||
|
||||
---
|
||||
|
||||
## Подробно
|
||||
|
||||
## 1) Общая схема потока
|
||||
|
||||
1. Клиент формирует текст сообщения и строит **2 подписанных блока** (`type=1` и `type=2`) с одинаковыми `fromLogin/toLogin/timeMs/nonce`.
|
||||
2. Клиент отправляет оба блока в одном RPC: `SendMessagePair` (алиас: `ReceiveOutcomingMessage`).
|
||||
3. Сервер:
|
||||
- парсит оба блока;
|
||||
- валидирует пару;
|
||||
- проверяет существование `from/to` пользователей и подписи;
|
||||
- атомарно сохраняет пару в `signed_messages_v2`.
|
||||
4. Сервер доставляет блоки в активные сессии целевого логина событием `SignedMessageArrived`.
|
||||
5. Клиент, получив событие, кладёт сообщение в локальный чат и отправляет `AckSessionDelivery(messageKey)`.
|
||||
6. При открытии чата клиент отправляет read-receipt (пара `type=3/4`) для непрочитанных входящих.
|
||||
|
||||
## 2) Формат signed DM-блока (`SHiNE_dm2`)
|
||||
|
||||
Префикс: `SHiNE_dm2` (ASCII).
|
||||
|
||||
Далее поля (big-endian):
|
||||
|
||||
1. `toLoginLen` (`u8`) + `toLogin` (ASCII, 1..60);
|
||||
2. `fromLoginLen` (`u8`) + `fromLogin` (ASCII, 1..60);
|
||||
3. `timeMs` (`u64`);
|
||||
4. `nonce` (`u32`);
|
||||
5. `messageType` (`u16`);
|
||||
6. `payloadLen` (`u16`);
|
||||
7. `payloadBytes` (`1..4096`);
|
||||
8. `signature` (`64 bytes`, Ed25519).
|
||||
|
||||
Ограничения:
|
||||
|
||||
- полный пакет: до `8192` байт;
|
||||
- `messageType` сейчас допустим только `1..4`.
|
||||
|
||||
## 3) Типы DM-сообщений
|
||||
|
||||
- `1` (`TYPE_INCOMING_TEXT`) — входящий текст для получателя.
|
||||
- `2` (`TYPE_OUTGOING_COPY`) — исходящая копия в истории автора.
|
||||
- `3` (`TYPE_READ_INCOMING`) — read-receipt (входящий тип для пары квитанции).
|
||||
- `4` (`TYPE_READ_OUTGOING_COPY`) — зеркальная копия read-receipt.
|
||||
|
||||
Правило пары:
|
||||
|
||||
- первый блок должен быть нечётным (`1` или `3`);
|
||||
- второй должен быть ровно `+1` (`2` или `4`);
|
||||
- ключевые поля пары совпадают: `toLogin/fromLogin/timeMs/nonce`.
|
||||
|
||||
## 4) Ключи сообщений
|
||||
|
||||
- `baseKey = from|to|timeMs|nonce`
|
||||
- `messageKey = baseKey|messageType`
|
||||
|
||||
Эти ключи используются:
|
||||
|
||||
- для дедупликации;
|
||||
- для связи read-receipt с исходным сообщением;
|
||||
- для ACK доставки по сессии.
|
||||
|
||||
## 5) RPC и события
|
||||
|
||||
## `SendMessagePair` (алиас `ReceiveOutcomingMessage`)
|
||||
|
||||
Запрос:
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SendMessagePair",
|
||||
"requestId": "req-1",
|
||||
"payload": {
|
||||
"incomingBlobB64": "<base64 signed block type 1 or 3>",
|
||||
"outgoingBlobB64": "<base64 signed block type 2 or 4>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Успешный ответ:
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SendMessagePair",
|
||||
"requestId": "req-1",
|
||||
"status": 200,
|
||||
"ok": true,
|
||||
"payload": {
|
||||
"baseKey": "from|to|time|nonce",
|
||||
"incomingKey": "from|to|time|nonce|1",
|
||||
"outgoingKey": "from|to|time|nonce|2",
|
||||
"deliveredWsSessions": 2,
|
||||
"deliveredWebPushSessions": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `SignedMessageArrived` (server event)
|
||||
|
||||
Событие в сессию получателя содержит:
|
||||
|
||||
- `messageKey`, `baseKey`;
|
||||
- `fromLogin`, `toLogin`, `targetLogin`;
|
||||
- `messageType`, `timeMs`, `nonce`;
|
||||
- `blobB64`;
|
||||
- `backlog` (признак догрузки из очереди).
|
||||
|
||||
## `AckSessionDelivery`
|
||||
|
||||
Запрос:
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "AckSessionDelivery",
|
||||
"requestId": "ack-1",
|
||||
"payload": {
|
||||
"messageKey": "from|to|time|nonce|1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ответ: `status=200`, echo `messageKey`.
|
||||
|
||||
## 6) Хранение на сервере (SQLite)
|
||||
|
||||
Основные таблицы:
|
||||
|
||||
1. `signed_messages_v2` — сами DM-блоки типов `1/2/3/4`:
|
||||
- `message_key` (PK),
|
||||
- `base_key`,
|
||||
- `target_login`,
|
||||
- `from_login`, `to_login`,
|
||||
- `time_ms`, `nonce`, `message_type`,
|
||||
- `raw_block`,
|
||||
- `source_api`, `origin_session_id`,
|
||||
- `receipt_ref_base_key`, `receipt_ref_type`.
|
||||
2. `signed_message_session_delivery` — доставка по сессиям:
|
||||
- составной PK `(message_key, session_id)`,
|
||||
- `delivered` (0/1),
|
||||
- `delivered_at_ms`, `created_at_ms`.
|
||||
|
||||
Примечание: историческая таблица `signed_direct_messages_history` в БД присутствует как legacy-слой, но текущий рабочий поток DM v2 опирается на `signed_messages_v2` + `signed_message_session_delivery`.
|
||||
|
||||
## 7) Доставка и backlog
|
||||
|
||||
- При сохранении пары сервер пытается сразу доставить в онлайн-сессии.
|
||||
- Для офлайн/недоступных сессий остаётся pending-запись доставки в таблице `signed_message_session_delivery`.
|
||||
- При подключении сессии сервер автоматически вызывает `dispatchPendingForSession`:
|
||||
- для новой сессии регистрирует все существующие сообщения адресата как «недоставленные»;
|
||||
- отправляет **все** pending через WebSocket событием `SignedMessageArrived(backlog=true)`;
|
||||
- лимита на количество сообщений нет — передаётся вся история без ограничений.
|
||||
- Клиент дедублирует входящие через `knownMessageKeys`: если `messageKey` уже есть локально — игнорирует.
|
||||
- После получения клиент отправляет `AckSessionDelivery`, чтобы отметить `delivered=1` в таблице доставки.
|
||||
|
||||
## 8) Read-receipt логика
|
||||
|
||||
Когда клиент открывает чат:
|
||||
|
||||
1. ищет входящие `messageType=1` без `readReceiptSent`;
|
||||
2. для каждого отправляет read-receipt как пару `type=3/4`;
|
||||
3. после успешной отправки помечает `readReceiptSent`.
|
||||
|
||||
Сервер для read-receipt хранит ссылку на исходное сообщение:
|
||||
|
||||
- `receipt_ref_base_key`;
|
||||
- `receipt_ref_type`.
|
||||
|
||||
Есть уникальность, чтобы не плодить дубликаты receipt на один и тот же `baseKey` для одного `target_login`.
|
||||
|
||||
## 9) Логика UI-клиента
|
||||
|
||||
### Хранилище сообщений
|
||||
|
||||
- In-memory: `state.chats[chatId]` — массив сообщений по каждому диалогу.
|
||||
- Персистентно: IndexedDB база `shine-ui-messages-v1`, object store `messages`, ключ `messageKey`.
|
||||
- `chatId` для `type=1` — `fromLogin`, для `type=2` — `toLogin`.
|
||||
|
||||
### Жизненный цикл при старте/подключении
|
||||
|
||||
1. `hydrateMessagesFromStore()` — читает все сообщения из IndexedDB в `state.chats` (до WebSocket-соединения).
|
||||
2. После установки WebSocket-сессии сервер присылает backlog (`SignedMessageArrived(backlog=true)`) для всех недоставленных сообщений.
|
||||
3. Клиент дедублирует через `knownMessageKeys` — уже имеющиеся в IndexedDB игнорируются.
|
||||
4. Новые сообщения в реальном времени приходят теми же WebSocket-событиями.
|
||||
|
||||
### Очистка при выходе и смене пользователя
|
||||
|
||||
- При любом логауте (`terminateCurrentSession`) IndexedDB с сообщениями **удаляется полностью**.
|
||||
- При входе нового пользователя через QR — IndexedDB удаляется явно до вызова `terminateCurrentSession`.
|
||||
- При входе нового пользователя через логин/пароль — IndexedDB удаляется в `registration-keys-view.js` прямо перед `authorizeSession()`.
|
||||
- Это гарантирует: при любом способе входа старые сообщения предыдущего пользователя не попадут к следующему.
|
||||
|
||||
### UI-поведение
|
||||
|
||||
- непрочитанные считаются по `from='in' && unread=true`;
|
||||
- доставка/прочтение исходящих:
|
||||
- `firstTick` — сообщение принято сервером,
|
||||
- `secondTick` — пришло подтверждение прочтения;
|
||||
- при открытии диалога UI автопрокручивает ленту в самый низ;
|
||||
- после отправки нового сообщения UI сразу прокручивает ленту вниз.
|
||||
|
||||
## 10) Синхронизация личных сообщений между серверами
|
||||
|
||||
Когда пользователи зарегистрированы на разных серверах SHiNE, серверы должны синхронизировать DM между собой.
|
||||
|
||||
### Общий принцип
|
||||
|
||||
- Сервер A получает DM-блок, адресованный пользователю на сервере B.
|
||||
- Сервер A пересылает этот блок серверу B (межсерверный relay).
|
||||
- Сервер B сохраняет блок и доставляет его в активные сессии получателя.
|
||||
- Серверы, между которыми идёт синхронизация, задаются списком `sync_servers` в PDA пользователя-сервера.
|
||||
|
||||
### Что синхронизируется
|
||||
|
||||
- Все DM-блоки типов `1/2` (текстовые сообщения) и `3/4` (read-receipt).
|
||||
- Синхронизация двусторонняя: оба сервера должны уметь принимать и пересылать блоки.
|
||||
|
||||
### Идемпотентность
|
||||
|
||||
- Блоки имеют уникальный `message_key` (`from|to|timeMs|nonce|type`).
|
||||
- Повторная доставка одного и того же блока безопасна — дедупликация происходит по `message_key`.
|
||||
|
||||
### Статус реализации
|
||||
|
||||
Межсерверная синхронизация DM **пока не реализована**. Текущая версия работает только в рамках одного сервера. Это задача для следующего этапа.
|
||||
|
||||
---
|
||||
|
||||
## 11) Инварианты (обязательно соблюдать при доработках)
|
||||
|
||||
1. Пара блоков (1/2 или 3/4) должна оставаться атомарной.
|
||||
2. `messageKey`/`baseKey` формат должен быть совместим с текущей логикой дедупликации и receipt.
|
||||
3. Доставка должна оставаться **по сессиям** с явным `AckSessionDelivery`.
|
||||
4. Read-receipt не должен отправляться многократно на один и тот же `baseKey`.
|
||||
5. Любые изменения DM-логики в коде должны сразу отражаться в этом документе.
|
||||
|
||||
## 12) Ключевые файлы реализации
|
||||
|
||||
- UI:
|
||||
- `shine-UI/js/services/auth-service.js`
|
||||
- `shine-UI/js/app.js`
|
||||
- `shine-UI/js/state.js`
|
||||
- `shine-UI/js/pages/chat-view.js`
|
||||
- Сервер:
|
||||
- `shine-server-net-protocol/.../messages/SignedMessageBlock.java`
|
||||
- `shine-server-net-protocol/.../messages/SignedMessagesCore.java`
|
||||
- `shine-server-net-protocol/.../messages/Net_SendMessagePair_Handler.java`
|
||||
- `shine-server-net-protocol/.../messages/SignedMessagesRealtime.java`
|
||||
- `shine-server-net-protocol/.../messages/Net_AckSessionDelivery_Handler.java`
|
||||
- БД:
|
||||
- `shine-server-db/src/main/java/shine/db/DatabaseInitializer.java`
|
||||
- `shine-server-db/src/main/java/shine/db/dao/SignedMessagesV2DAO.java`
|
||||
@ -1,42 +0,0 @@
|
||||
# TODO: доработка персональных сообщений для агентов
|
||||
|
||||
Статус: отложено.
|
||||
|
||||
## Что хотели сделать
|
||||
|
||||
Добавить упрощённую маршрутизацию персональных сообщений через служебную инструкцию в начале текстового payload (внутри подписанного DM-блока), чтобы:
|
||||
|
||||
- отличать сообщения человеку от сообщений агенту;
|
||||
- отличать сообщения от человека и от агента;
|
||||
- скрывать в обычном UI сообщения, адресованные агенту (`target=agent`);
|
||||
- поддержать сценарий «сообщения самому себе между своими клиентами/устройствами», где один клиент/агент пишет другому в рамках одного логина.
|
||||
|
||||
## Базовая идея формата (черновик)
|
||||
|
||||
Пример префикса:
|
||||
|
||||
```text
|
||||
@shine:pm:v1 {"target":"agent","agentId":"assistant","author":"human"}
|
||||
Текст сообщения...
|
||||
```
|
||||
|
||||
Пример ответа агента:
|
||||
|
||||
```text
|
||||
@shine:pm:v1 {"target":"user","author":"agent","agentId":"assistant","agentLabel":"My Bot"}
|
||||
Ответ агента...
|
||||
```
|
||||
|
||||
## Почему отложено
|
||||
|
||||
- нужно отдельно согласовать финальный формат инструкции;
|
||||
- нужно определить строгие правила UI-фильтрации и fallback;
|
||||
- нужно определить, нужен ли позднее отдельный серверный роутинг для agent-сессий.
|
||||
|
||||
## Что сделать при возвращении к задаче
|
||||
|
||||
1. Зафиксировать окончательный формат префикса и JSON-полей.
|
||||
2. Описать правила парсинга/валидации (включая битые/неполные префиксы).
|
||||
3. Добавить UI-логику показа/скрытия agent-сообщений.
|
||||
4. Добавить маркировку «ответ агента» в диалоге.
|
||||
5. Продумать режим self-chat (между своими клиентами/агентом) в рамках одного логина.
|
||||
@ -1,424 +0,0 @@
|
||||
# Solana user_pda: итоговый целевой формат пользовательской записи
|
||||
|
||||
Документ описывает целевой формат пользовательской PDA-записи `user_pda` для Solana-программы `shine_users`.
|
||||
|
||||
Это не формат основного блокчейна SHiNE и не документация по `AddBlock`. Основной блокчейн SHiNE описан отдельно в `Dev_Docs/Blockchain/`.
|
||||
|
||||
Статус документа: итоговый согласованный формат, к которому приведены `create_user_pda`, `update_user_pda` и тестовый сериализатор Solana-модуля.
|
||||
|
||||
## 1. Назначение user_pda
|
||||
|
||||
`user_pda` хранит публичное состояние пользователя в Solana:
|
||||
|
||||
- логин пользователя;
|
||||
- неизменяемые параметры создания записи;
|
||||
- корневой публичный ключ пользователя;
|
||||
- ключ устройства;
|
||||
- данные одного или нескольких пользовательских блокчейнов SHiNE;
|
||||
- серверные данные пользователя, если пользователь выступает сервером;
|
||||
- серверы доступа пользователя;
|
||||
- счетчики/лимиты;
|
||||
- подпись записи.
|
||||
|
||||
На первом этапе поддерживается один пользовательский блокчейн SHiNE, но формат блока блокчейна сразу допускает повторение таких блоков в будущем.
|
||||
|
||||
## 2. Адрес PDA
|
||||
|
||||
Адрес пользовательской PDA вычисляется по логину:
|
||||
|
||||
- seed prefix: `user_login=`;
|
||||
- второй seed: нормализованный логин в нижнем регистре;
|
||||
- program id: программа `shine_users`.
|
||||
|
||||
Один логин соответствует одной `user_pda`.
|
||||
|
||||
## 2.1. Кто оплачивает create/update PDA
|
||||
|
||||
- Инструкции `create_user_pda` и `update_user_pda` оплачиваются с `device_key`.
|
||||
- `root_key` используется для подписи unsigned части записи через Ed25519 instruction и не является fee payer.
|
||||
- Для server PDA это правило то же самое: пополнять SOL нужно на адрес `device_key`.
|
||||
|
||||
## 3. Общие правила кодирования
|
||||
|
||||
- Числа кодируются в Little Endian.
|
||||
- `u8`, `u16`, `u32`, `u64` имеют обычный фиксированный размер.
|
||||
- Публичный ключ Solana/Ed25519: 32 байта.
|
||||
- Ed25519-подпись: 64 байта.
|
||||
- SHA-256/Solana hash: 32 байта.
|
||||
- Строка переменной длины: `len: u8` + `bytes[len]` в UTF-8.
|
||||
- Arweave `tx_id`: строка переменной длины. Ожидаемая практическая длина base64url tx id - 43 байта, но формат хранит длину явно.
|
||||
- Все типизированные блоки после фиксированного заголовка начинаются с `block_type: u8` и `block_version: u8`.
|
||||
- Отдельный `block_len` у типизированных блоков не хранится: блоки парсятся по известным полям, счетчикам и строкам с `len: u8`.
|
||||
|
||||
## 4. Верхний формат записи
|
||||
|
||||
Первые 9 полей фиксированы и идут строго в указанном порядке. Это общий заголовок записи.
|
||||
|
||||
| N | Поле | Тип | Размер | Правило |
|
||||
|---|------|-----|--------|---------|
|
||||
| 1 | `magic` | bytes | 5 | Всегда `SHiNE`. |
|
||||
| 2 | `format_major` | `u8` | 1 | Для первого формата: `1`. |
|
||||
| 3 | `format_minor` | `u8` | 1 | Для первой версии нового формата: `0`. |
|
||||
| 4 | `record_len` | `u16` | 2 | Длина полезной записи от `magic` до `signature` включительно, без padding. |
|
||||
| 5 | `created_at_ms` | `u64` | 8 | Время создания записи, Unix time в миллисекундах. Не меняется. |
|
||||
| 6 | `updated_at_ms` | `u64` | 8 | Время последнего обновления записи. |
|
||||
| 7 | `record_number` | `u32` | 4 | Номер версии записи пользователя. При создании `0`, при обновлении +1. |
|
||||
| 8 | `prev_record_hash` | bytes | 32 | Хэш unsigned-части предыдущей записи. При создании 32 нулевых байта. |
|
||||
| 9 | `login` | string | `1 + len` | Логин пользователя. Не меняется. |
|
||||
|
||||
После первых 9 полей идет набор типизированных блоков:
|
||||
|
||||
```text
|
||||
UserPdaRecordV1
|
||||
- fixed_header: поля 1..9
|
||||
- blocks_count: u8
|
||||
- blocks: TypedBlock[blocks_count]
|
||||
- signature: [u8; 64]
|
||||
- padding: bytes до размера PDA, если нужен
|
||||
```
|
||||
|
||||
`blocks_count` входит в unsigned-часть записи и подписывается.
|
||||
|
||||
## 5. Типы блоков
|
||||
|
||||
Зарезервированные значения `block_type`:
|
||||
|
||||
| block_type | Блок | Назначение |
|
||||
|------------|------|------------|
|
||||
| `1` | `RootKeyBlock` | Корневой ключ пользователя. |
|
||||
| `2` | `DeviceKeyBlock` | Ключ устройства пользователя. |
|
||||
| `3` | `BlockchainRegistryBlock` | Один или несколько блокчейнов пользователя. |
|
||||
| `30` | `ServerProfileBlock` | Серверные данные пользователя. |
|
||||
| `40` | `AccessServersBlock` | Серверы доступа/relay. |
|
||||
| `50` | `SessionsBlock` | Опубликованные пользовательские сессии и саб-серверы. |
|
||||
| `70` | `TrustedStateBlock` | Счетчик trusted-связей. |
|
||||
| `255` | `ReservedBlock` | Зарезервировано, пока не используется. |
|
||||
|
||||
Правила:
|
||||
|
||||
- неизвестный `block_type` в `format_major = 1` считается ошибкой;
|
||||
- обязательные блоки: `RootKeyBlock`, `DeviceKeyBlock`, `BlockchainRegistryBlock`;
|
||||
- необязательные блоки: `ServerProfileBlock`, `AccessServersBlock`, `SessionsBlock`, `TrustedStateBlock`;
|
||||
- каждый обязательный блок должен встречаться ровно один раз;
|
||||
- порядок блоков в записи фиксируется для простоты проверки:
|
||||
`RootKey`, `DeviceKey`, `BlockchainRegistry`, `ServerProfile`, `AccessServers`, `Sessions`, `TrustedState`.
|
||||
|
||||
## 6. RootKeyBlock
|
||||
|
||||
Смена `root_key` пока не проектируется и не реализуется. Блок фиксирует только стадию `0`.
|
||||
|
||||
```text
|
||||
RootKeyBlock
|
||||
- block_type: u8 = 1
|
||||
- block_version: u8 = 0
|
||||
- root_key: [u8; 32]
|
||||
```
|
||||
|
||||
Правила:
|
||||
|
||||
- при создании задается корневой публичный ключ пользователя;
|
||||
- при обновлении `root_key` должен совпадать с предыдущей записью;
|
||||
- ротация root-key будет отдельным форматом/сценарием в будущем.
|
||||
|
||||
## 7. DeviceKeyBlock
|
||||
|
||||
Смена `device_key` пока также не проектируется как отдельная ротация. В версии `0` хранится один ключ устройства.
|
||||
|
||||
```text
|
||||
DeviceKeyBlock
|
||||
- block_type: u8 = 2
|
||||
- block_version: u8 = 0
|
||||
- device_key: [u8; 32]
|
||||
```
|
||||
|
||||
Правила:
|
||||
|
||||
- при создании задается текущий публичный ключ устройства;
|
||||
- при обновлении ключ устройства может быть обновлен только если это отдельно разрешено бизнес-логикой инструкции;
|
||||
- история устройств и несколько устройств в этом формате не хранятся.
|
||||
|
||||
## 8. BlockchainRegistryBlock
|
||||
|
||||
Блок хранит данные пользовательских блокчейнов SHiNE. Сейчас используется один блокчейн, но структура сразу сделана как список.
|
||||
|
||||
```text
|
||||
BlockchainRegistryBlock
|
||||
- block_type: u8 = 3
|
||||
- block_version: u8 = 0
|
||||
- blockchain_count: u8
|
||||
- blockchain_records: BlockchainRecord[blockchain_count]
|
||||
```
|
||||
|
||||
Правила:
|
||||
|
||||
- на первом этапе `blockchain_count = 1`;
|
||||
- в будущем можно увеличить количество записей без изменения смысла `BlockchainRecord`;
|
||||
- каждый `BlockchainRecord` описывает один пользовательский SHiNE-блокчейн.
|
||||
|
||||
## 9. BlockchainRecord
|
||||
|
||||
```text
|
||||
BlockchainRecord
|
||||
- blockchain_type: u8
|
||||
- blockchain_name: string
|
||||
- blockchain_public_key: [u8; 32]
|
||||
- paid_limit_bytes: u64
|
||||
- used_bytes: u64
|
||||
- last_block_number: u32
|
||||
- last_block_hash: [u8; 32]
|
||||
- last_block_signature: [u8; 64]
|
||||
- arweave_present: u8
|
||||
- arweave_tx_id: string, только если arweave_present = 1
|
||||
```
|
||||
|
||||
`blockchain_type`:
|
||||
|
||||
| Значение | Смысл |
|
||||
|----------|-------|
|
||||
| `1` | Основной пользовательский SHiNE-блокчейн. |
|
||||
|
||||
Поля:
|
||||
|
||||
- `blockchain_name` - строковое имя пользовательского блокчейна, например `login-001`. На первом этапе для основного блокчейна пользователя используется имя вида `<login>-001`, потому что это первый блокчейн этого пользователя.
|
||||
- `blockchain_public_key` - публичный ключ блокчейна пользователя.
|
||||
- `paid_limit_bytes` - оплаченный лимит хранения/записей в байтах.
|
||||
- `used_bytes` - сколько байт уже занято в пользовательском SHiNE-блокчейне.
|
||||
- `last_block_number` - номер последнего известного блока пользовательского блокчейна.
|
||||
- `last_block_hash` - хэш последнего известного блока.
|
||||
- `last_block_signature` - подпись хэша специального сообщения о вершине блокчейна ключом `blockchain_public_key`.
|
||||
- `arweave_present` - `0`, если ссылки нет; `1`, если ссылка есть.
|
||||
- `arweave_tx_id` - Arweave transaction id, где лежит выгруженный пользовательский канал/состояние.
|
||||
|
||||
Arweave `tx_id` - обычное поле внутри записи конкретного блокчейна. Solana-программа не проверяет, что такой Arweave transaction действительно существует и содержит корректные данные; это ответственность клиента/сервера/пользователя.
|
||||
|
||||
## 10. Правила обновления BlockchainRecord
|
||||
|
||||
При обновлении записи:
|
||||
|
||||
- `blockchain_type` для существующей записи не меняется;
|
||||
- `blockchain_public_key` пока не ротируется автоматически; смена ключа требует отдельного согласованного сценария;
|
||||
- `paid_limit_bytes` может только увеличиваться или оставаться прежним;
|
||||
- при увеличении `paid_limit_bytes` пользователь платит комиссию в Solana по тарифам программы;
|
||||
- `used_bytes` может только увеличиваться или оставаться прежним;
|
||||
- `last_block_number` может только увеличиваться или оставаться прежним;
|
||||
- `used_bytes <= paid_limit_bytes`;
|
||||
- если `last_block_number` увеличился, то должны быть переданы новый `last_block_hash` и новая `last_block_signature`;
|
||||
- `last_block_signature` проверяется через Ed25519-инструкцию Solana: подпись должна соответствовать хэшу сообщения `LastBlockState` и `blockchain_public_key`;
|
||||
- в транзакции `create_user_pda` / `update_user_pda` две Ed25519-инструкции должны идти непосредственно перед вызовом `shine_users`: сначала подпись `root_key`, затем подпись `blockchain_public_key`;
|
||||
- `arweave_tx_id` можно добавить или заменить на новый, если пользователь выгрузил более актуальное состояние в Arweave;
|
||||
- уменьшать лимит, число блоков или занятый размер нельзя.
|
||||
|
||||
Сообщение `LastBlockState`, которое хэшируется и подписывается ключом `blockchain_public_key`:
|
||||
|
||||
```text
|
||||
LastBlockState
|
||||
- constant: bytes = "SHiNE_LAST_BLOCK"
|
||||
- login: string
|
||||
- blockchain_name: string
|
||||
- last_block_number: u32
|
||||
- last_block_hash: [u8; 32]
|
||||
- used_bytes: u64
|
||||
```
|
||||
|
||||
Алгоритм:
|
||||
|
||||
```text
|
||||
message = SHA-256(LastBlockState bytes)
|
||||
last_block_signature = Ed25519(blockchain_public_key, message)
|
||||
```
|
||||
|
||||
Причина проверки подписи `LastBlockState`: `root_key` управляет Solana-записью пользователя, а `blockchain_public_key` подтверждает состояние конкретного пользовательского блокчейна. Подписывается не голый хэш, а связка логина, имени блокчейна, номера последнего блока, хэша последнего блока и занятого размера.
|
||||
|
||||
## 11. ServerProfileBlock
|
||||
|
||||
Блок присутствует, если пользователь выступает сервером.
|
||||
|
||||
```text
|
||||
ServerProfileBlock
|
||||
- block_type: u8 = 30
|
||||
- block_version: u8 = 0
|
||||
- is_server: u8
|
||||
- address_format_type: u8, только если is_server = 1
|
||||
- address_format_version: u8, только если is_server = 1
|
||||
- server_address: string, только если is_server = 1
|
||||
- sync_servers_count: u8, только если is_server = 1
|
||||
- sync_servers: string[sync_servers_count], только если is_server = 1
|
||||
```
|
||||
|
||||
Правила:
|
||||
|
||||
- `is_server = 0` означает, что серверных данных нет;
|
||||
- `is_server = 1` означает, что пользователь публикует серверный профиль;
|
||||
- `address_format_type` — тип формата адреса сервера: `1` = URL-строка (например `https://shineup.me/ws`);
|
||||
- `address_format_version` — версия формата адреса, сейчас `0`;
|
||||
- `sync_servers_count` максимум `32`;
|
||||
- `server_address` - строковый адрес сервера в соответствии с `address_format_type`;
|
||||
- `sync_servers` - логины SHiNE-пользователей, зарегистрированных как серверы, с которыми этот сервер синхронизирует блокчейн и личные сообщения. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы.
|
||||
|
||||
## 12. AccessServersBlock
|
||||
|
||||
Блок хранит серверы доступа/relay для пользователя.
|
||||
|
||||
```text
|
||||
AccessServersBlock
|
||||
- block_type: u8 = 40
|
||||
- block_version: u8 = 0
|
||||
- access_servers_count: u8
|
||||
- access_servers: string[access_servers_count]
|
||||
```
|
||||
|
||||
Правила:
|
||||
|
||||
- блок может отсутствовать, если серверы доступа не заданы;
|
||||
- список может обновляться при изменении маршрутизации пользователя;
|
||||
- `access_servers` - логины пользователей системы, используемых как серверы доступа/relay. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы;
|
||||
- точная семантика выбора сервера доступа определяется клиентской/серверной логикой SHiNE.
|
||||
|
||||
## 13. SessionsBlock
|
||||
|
||||
Блок хранит опубликованные пользовательские сессии. На текущем этапе регистрация пользователя не добавляет туда записи автоматически, поэтому стандартный create/update продолжает работать с пустым списком.
|
||||
|
||||
```text
|
||||
SessionsBlock
|
||||
- block_type: u8 = 50
|
||||
- block_version: u8 = 0
|
||||
- sessions_mode: u8
|
||||
- sessions_count: u8
|
||||
- sessions: SessionRecord[sessions_count]
|
||||
```
|
||||
|
||||
`sessions_mode`:
|
||||
|
||||
| Значение | Смысл |
|
||||
|----------|-------|
|
||||
| `1` | Можно использовать и сессии, зарегистрированные в PDA, и сессии, созданные вне PDA. |
|
||||
| `10` | Зарезервировано на будущее: можно использовать только сессии, опубликованные в PDA. |
|
||||
|
||||
Сейчас рабочий режим по умолчанию: `sessions_mode = 1`. Серверная логика пока не реализует особое поведение для `10`; это задел под будущее расширение.
|
||||
|
||||
```text
|
||||
SessionRecord
|
||||
- session_type: u8
|
||||
- session_version: u8
|
||||
- session_name: string
|
||||
- session_pub_key: [u8; 32]
|
||||
```
|
||||
|
||||
`session_type`:
|
||||
|
||||
| Значение | Смысл |
|
||||
|----------|-------|
|
||||
| `1` | Обычная пользовательская сессия. |
|
||||
| `100` | Саб-сервер пользователя. |
|
||||
|
||||
Правила:
|
||||
|
||||
- максимум `64` записей на пользователя;
|
||||
- `session_name` не пустой, максимум `64` байта;
|
||||
- `session_name` может содержать только символы `[A-Za-z0-9_]`;
|
||||
- `session_version` сейчас должна быть равна `1`;
|
||||
- внутри одного блока должны быть уникальны и `session_name`, и `session_pub_key`;
|
||||
- на текущем этапе UI и регистрация не обязаны добавлять туда записи автоматически.
|
||||
|
||||
## 14. TrustedStateBlock
|
||||
|
||||
Пока trusted-логика не реализована полностью, поэтому блок хранит только счетчик.
|
||||
|
||||
```text
|
||||
TrustedStateBlock
|
||||
- block_type: u8 = 70
|
||||
- block_version: u8 = 0
|
||||
- trusted_count: u8 = 0
|
||||
```
|
||||
|
||||
Пока блок с доверенными лицами не реализуется, потому что полный формат trusted-логики еще не составлен. В будущем trusted-связи, очереди, таймеры и подтверждения должны быть вынесены в отдельный формат.
|
||||
|
||||
## 15. Подпись user_pda
|
||||
|
||||
Подписывается не вся PDA целиком, а unsigned-часть записи:
|
||||
|
||||
- от `magic` до последнего байта последнего типизированного блока включительно;
|
||||
- включая `record_len`, `blocks_count`, все заголовки блоков и тела блоков;
|
||||
- без поля `signature`;
|
||||
- без padding.
|
||||
|
||||
Алгоритм:
|
||||
|
||||
```text
|
||||
message = hash(unsigned_record_bytes)
|
||||
signature = Ed25519(root_key, message)
|
||||
```
|
||||
|
||||
Solana-программа проверяет подпись через встроенную Ed25519-инструкцию. Подписантом должен быть `root_key` из `RootKeyBlock`.
|
||||
Для `shine_users` эта инструкция должна стоять в транзакции сразу перед Ed25519-инструкцией `last_block_signature` и непосредственно перед самой `create/update`-инструкцией программы.
|
||||
|
||||
Смену формата подписи сейчас не трогаем.
|
||||
|
||||
## 16. Регистрация пользователя
|
||||
|
||||
При регистрации:
|
||||
|
||||
- PDA еще не должна существовать;
|
||||
- логин проходит проверку формата и login guard;
|
||||
- `record_number = 0`;
|
||||
- `prev_record_hash = 0x00...00`;
|
||||
- `created_at_ms = updated_at_ms`;
|
||||
- обязательные блоки присутствуют;
|
||||
- создается минимум один `BlockchainRecord`;
|
||||
- новый `SessionsBlock` может присутствовать, но при обычной регистрации сейчас записывается пустой список с `sessions_mode = 1`;
|
||||
- стартовый `paid_limit_bytes` равен стартовому бонусу плюс оплаченный дополнительный лимит;
|
||||
- `used_bytes <= paid_limit_bytes`;
|
||||
- пользователь платит регистрационную комиссию;
|
||||
- если покупается дополнительный лимит, пользователь платит комиссию за этот лимит;
|
||||
- вся unsigned-часть записи подписана `root_key`.
|
||||
|
||||
## 17. Обновление пользователя
|
||||
|
||||
При обновлении:
|
||||
|
||||
- PDA должна существовать;
|
||||
- `login`, `created_at_ms`, `root_key` не меняются;
|
||||
- `record_number = previous_record_number + 1`;
|
||||
- `prev_record_hash` равен хэшу unsigned-части предыдущей записи;
|
||||
- `updated_at_ms` обновляется;
|
||||
- unsigned-часть новой записи подписана `root_key`;
|
||||
- лимиты блокчейнов могут только увеличиваться;
|
||||
- занятый размер и номер последнего блока не могут уменьшаться;
|
||||
- при увеличении оплаченного лимита пользователь доплачивает комиссию;
|
||||
- Arweave `tx_id` может быть пустым или обновленным, но его содержимое Solana не валидирует.
|
||||
|
||||
## 18. Отличия от старого линейного формата
|
||||
|
||||
Старый формат после `login` хранил поля линейно:
|
||||
|
||||
- `root_key_status`;
|
||||
- `root_key`;
|
||||
- `blockchain_key_status`;
|
||||
- `blockchain_key`;
|
||||
- `device_key_status`;
|
||||
- `device_key`;
|
||||
- `chain_number`;
|
||||
- `balance`;
|
||||
- серверные поля;
|
||||
- access-серверы;
|
||||
- `trusted_count`;
|
||||
- `reserved`;
|
||||
- `signature`.
|
||||
|
||||
Новый целевой формат сохраняет первые 9 фиксированных полей как заголовок, но дальше переходит на типизированные блоки:
|
||||
|
||||
- ключи становятся отдельными блоками;
|
||||
- данные блокчейна становятся расширенным блоком со своим публичным ключом, лимитом, занятым размером, вершиной цепочки и Arweave `tx_id`;
|
||||
- серверные данные и access-серверы отделяются от данных блокчейна;
|
||||
- расширение формата делается добавлением новых версий блоков или новых `block_type`, а не вставкой полей в середину линейной записи.
|
||||
|
||||
## 18. Что пока не входит в формат
|
||||
|
||||
Пока не проектируем:
|
||||
|
||||
- ротацию `root_key`;
|
||||
- сложную ротацию `device_key`;
|
||||
- ротацию `blockchain_public_key`;
|
||||
- проверку содержимого Arweave transaction;
|
||||
- хранение полной истории пользовательского блокчейна внутри Solana;
|
||||
- подключение Solana-модуля к сборке/деплою основного сервера SHiNE.
|
||||
@ -1,166 +0,0 @@
|
||||
# Архитектура Solana-программ SHiNE
|
||||
|
||||
Документ описывает рабочую архитектуру Solana-части SHiNE: три Anchor-программы, DAO, ключи управления, PDA-счета и движение денег.
|
||||
|
||||
Это архитектурная справка. Она не меняет код, формат PDA-записи пользователя, серверный API или формат блокчейна SHiNE.
|
||||
|
||||
Статус: актуализировано по коду `shine-solana/shine/programs/*` на 2026-05-25.
|
||||
|
||||
Связанные документы:
|
||||
|
||||
- `Dev_Docs/Инициализация_Solana_регистрации/README.md` — single source of truth по деплою и первичной инициализации регистрации пользователей.
|
||||
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md` — точный формат `user_pda` для `shine_users`.
|
||||
- `shine-solana/shine/doc/FUNDS_FLOW.md` — короткая справка по денежным потокам внутри Solana-модуля.
|
||||
|
||||
## Кратко
|
||||
|
||||
В Solana-модуле сейчас три основные программы:
|
||||
|
||||
1. `shine_login_guard` — проверяет логин и возвращает класс логина: обычный, premium или trademark.
|
||||
2. `shine_users` — создает и обновляет пользовательскую PDA-запись, проверяет подписи и берет оплату за регистрацию/увеличение лимита.
|
||||
3. `shine_payments` — принимает входящий поток средств в `inflow_vault`, ведет очереди тикетов, позволяет DAO выдавать лимиты менеджерам и выполняет выплаты.
|
||||
|
||||
DAO в текущем виде не является отдельной Anchor-программой SHiNE внутри `programs/`. Это управляющая модель поверх кошельков, governance-скриптов и authority-адресов. Для проектирования ее удобно считать отдельным управляющим блоком: DAO голосует, назначает управляющие ключи, управляет казной и вызывает защищенные методы второй и третьей программ.
|
||||
|
||||
## Общая схема
|
||||
|
||||
Редактируемая Mermaid-схема находится в [schemes/architecture.mmd](schemes/architecture.mmd).
|
||||
|
||||
Картинки:
|
||||
|
||||
- [schemes/architecture.svg](schemes/architecture.svg)
|
||||
- [schemes/architecture.png](schemes/architecture.png)
|
||||
|
||||
## Программы и функции
|
||||
|
||||
| Блок | Папка/имя | Текущие функции из кода | Основной смысл |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | `shine_login_guard` | `classify_login` | Проверка логина перед регистрацией. |
|
||||
| 2 | `shine_users` | `init_users_economy_config`, `update_users_economy_config`, `create_user_pda`, `update_user_pda` | Регистрация пользователя, обновление записи, экономика лимита. |
|
||||
| 3 | `shine_payments` | `init`, `update_coef_limit`, `grant_manager_limits`, `buy_ticket`, `buy_ticket_usd`, `buy_ticket_sol`, `manager_add_ticket`, `step_payout`, `change_ticket_recipient` | Vault, билеты, очереди, выплаты, DAO-настройки, лимиты менеджеров. |
|
||||
| DAO | governance/authority | Вызовы через governance и управляющие ключи | Управление правами, казной, настройками и будущими обновлениями программ. |
|
||||
|
||||
## Актуальные program id
|
||||
|
||||
Актуальные адреса заданы одновременно в `Anchor.toml`, `declare_id!` программ и `programs/common/src/deploy_config.rs`:
|
||||
|
||||
| Программа | Program ID |
|
||||
| --- | --- |
|
||||
| `shine_login_guard` | `3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo` |
|
||||
| `shine_users` | `FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm` |
|
||||
| `shine_payments` | `c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW` |
|
||||
|
||||
Если эти адреса меняются, нужно синхронно обновить:
|
||||
|
||||
1. `shine-solana/shine/Anchor.toml`
|
||||
2. `declare_id!` в `programs/*/src/lib.rs`
|
||||
3. `programs/common/src/deploy_config.rs`
|
||||
4. UI/серверные константы, перечисленные в `Dev_Docs/Инициализация_Solana_регистрации/README.md`
|
||||
|
||||
## Ключи и authority
|
||||
|
||||
Для удобного понимания на старте можно считать, что есть четыре группы ключей:
|
||||
|
||||
1. `key_1` / authority программы `shine_login_guard`.
|
||||
- Сейчас программа только классифицирует логин.
|
||||
- На первом этапе ее можно оставить под отдельным ключом.
|
||||
- В будущем право обновления можно передать DAO.
|
||||
|
||||
2. `key_2` / authority программы `shine_users`.
|
||||
- Отвечает за деплой/upgrade второй программы.
|
||||
- Защищенное обновление economy-конфига в коде уже проверяет `DAO_AUTHORITY`.
|
||||
- В целевой модели upgrade-authority второй программы нужно передать DAO.
|
||||
|
||||
3. `key_3` / authority программы `shine_payments`.
|
||||
- Отвечает за деплой/upgrade третьей программы.
|
||||
- Защищенные методы `update_coef_limit` и `grant_manager_limits` проверяют `dao_wallet` из `ConfigState`.
|
||||
- В целевой модели upgrade-authority третьей программы нужно передать DAO.
|
||||
|
||||
4. DAO-ключи.
|
||||
- Это управляющие кошельки/токены/realm governance.
|
||||
- DAO может добавлять и отзывать управляющие ключи по голосованию.
|
||||
- DAO-казна получает деньги от покупки тикетов и DAO-часть выплат из `inflow_vault`.
|
||||
|
||||
Адреса program id сейчас берутся из `programs/common/src/deploy_config.rs`. Для production/devnet можно подбирать vanity-адреса с понятным началом вроде `SHi...`, но это отдельная операция генерации ключей и деплоя.
|
||||
|
||||
## Счета и PDA
|
||||
|
||||
Постоянные PDA и счета:
|
||||
|
||||
1. `shine_users`
|
||||
- `user_pda` — пользовательская запись по seed `login=<login>`, создается для каждого логина.
|
||||
- `users_economy_config_pda` — общие параметры экономики регистрации и лимита.
|
||||
|
||||
2. `shine_payments`
|
||||
- `config_pda` — хранит `dao_wallet` и адрес `inflow_vault`.
|
||||
- `coef_limit_pda` — хранит коэффициент выплат, лимит очереди и награду вызывающему `step_payout`.
|
||||
- `queues_pda` — агрегаты очередей выплат.
|
||||
- `inflow_vault_pda` — PDA-вольт, куда `shine_users` переводит оплату регистрации и увеличения лимита.
|
||||
- `ticket_pda` — отдельная PDA-запись тикета на каждую покупку/менеджерскую выдачу.
|
||||
- `manager_allowance_pda` — PDA лимитов конкретного менеджера.
|
||||
|
||||
3. DAO
|
||||
- `dao_wallet` / treasury — казна DAO.
|
||||
- governance-аккаунты DAO — realm, governance, proposal/vote records и связанные аккаунты SPL Governance, если используется эта модель.
|
||||
|
||||
## Правило разделения с основным сервером
|
||||
|
||||
Solana-модуль лежит в основном репозитории как отдельная папка `shine-solana/shine/`, но не подключается автоматически к сборке или деплою основного сервера SHiNE. Команды `deployServer` и `deployUI` не должны деплоить Anchor-программы. Solana build/deploy выполняется отдельно из папки `shine-solana/shine/` по локальным правилам модуля.
|
||||
|
||||
## Движение денег
|
||||
|
||||
Основные потоки:
|
||||
|
||||
1. Регистрация пользователя через `shine_users::create_user_pda`.
|
||||
- Платит `signer`.
|
||||
- Деньги идут в `shine_payments::inflow_vault_pda`.
|
||||
- Сумма состоит из регистрационной комиссии и оплаты дополнительного лимита.
|
||||
|
||||
2. Увеличение лимита через `shine_users::update_user_pda`.
|
||||
- Платит `signer`.
|
||||
- Деньги идут в тот же `inflow_vault_pda`.
|
||||
- Сумма равна оплате дополнительного лимита.
|
||||
|
||||
3. Покупка тикета через `shine_payments::buy_ticket*`.
|
||||
- Платит покупатель.
|
||||
- Деньги сразу идут в `dao_wallet`.
|
||||
- Одновременно создается тикет на выплату.
|
||||
|
||||
4. Выплата через `shine_payments::step_payout`.
|
||||
- Вызвать может любой подписант.
|
||||
- Деньги берутся из `inflow_vault_pda`.
|
||||
- Часть идет получателю тикета.
|
||||
- Часть идет в `dao_wallet`.
|
||||
- Небольшая награда идет вызвавшему шаг выплат.
|
||||
- Если очереди пустые, весь доступный остаток `inflow_vault_pda` переводится в DAO.
|
||||
|
||||
## Передача прав DAO
|
||||
|
||||
Минимальная целевая модель:
|
||||
|
||||
1. `shine_login_guard`
|
||||
- Пока оставить на отдельном ключе `key_1`.
|
||||
- Передачу DAO сделать позже, когда логика premium/trademark стабилизируется.
|
||||
|
||||
2. `shine_users`
|
||||
- Economy-настройки уже должны обновляться DAO-authority.
|
||||
- Upgrade-authority программы после проверки можно передать DAO.
|
||||
|
||||
3. `shine_payments`
|
||||
- DAO уже управляет настройками выплат и лимитами менеджеров через `dao_wallet`.
|
||||
- Upgrade-authority программы после проверки можно передать DAO.
|
||||
|
||||
4. DAO
|
||||
- Управляет казной.
|
||||
- Принимает решения голосованием.
|
||||
- Добавляет/отзывает управляющие ключи.
|
||||
- Вызывает защищенные методы второй и третьей программ.
|
||||
- В будущем может принять управление первой программой.
|
||||
|
||||
## Детальные файлы
|
||||
|
||||
- [details/shine_login_guard.md](details/shine_login_guard.md)
|
||||
- [details/shine_users.md](details/shine_users.md)
|
||||
- [details/shine_payments.md](details/shine_payments.md)
|
||||
- [details/shine_dao.md](details/shine_dao.md)
|
||||
- [details/accounts_and_money_flow.md](details/accounts_and_money_flow.md)
|
||||
@ -1,110 +0,0 @@
|
||||
# Счета, ключи и движение денег
|
||||
|
||||
## Кратко
|
||||
|
||||
В архитектуре есть три типа объектов:
|
||||
|
||||
1. Ключи программ и DAO.
|
||||
2. PDA-счета состояния.
|
||||
3. Денежные счета, через которые проходят SOL/lamports.
|
||||
|
||||
## Ключи
|
||||
|
||||
Минимальный набор для понимания:
|
||||
|
||||
1. `key_1` — deploy/upgrade authority `shine_login_guard`.
|
||||
2. `key_2` — deploy/upgrade authority `shine_users`.
|
||||
3. `key_3` — deploy/upgrade authority `shine_payments`.
|
||||
4. `DAO_AUTHORITY` — адрес, который имеет право менять защищенные настройки.
|
||||
5. `DAO_TREASURY_WALLET` / `dao_wallet` — казна DAO.
|
||||
6. `manager_wallet` — кошелек менеджера, которому DAO выдает лимиты на создание тикетов.
|
||||
7. `user root_key` — корневой ключ пользователя для подписи пользовательской записи.
|
||||
8. `user device_key` — ключ устройства пользователя.
|
||||
9. `server_key` — ключ сервера пользователя, если пользователь является сервером.
|
||||
|
||||
Текущие адреса из `programs/common/src/deploy_config.rs`:
|
||||
|
||||
| Роль | Адрес |
|
||||
| --- | --- |
|
||||
| `SHINE_LOGIN_GUARD_PROGRAM_ID` | `3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo` |
|
||||
| `SHINE_USERS_PROGRAM_ID` | `FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm` |
|
||||
| `SHINE_PAYMENTS_PROGRAM_ID` | `c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW` |
|
||||
| `DAO_AUTHORITY` | `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P` |
|
||||
| `DAO_TREASURY_WALLET` | `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P` |
|
||||
|
||||
## Постоянные PDA
|
||||
|
||||
`shine_users`:
|
||||
|
||||
- `user_pda` — создается для каждого логина, seed `login=` + normalized login.
|
||||
- `users_economy_config_pda` — один PDA с экономикой регистрации, seed `shine_users_economy_config`.
|
||||
|
||||
`shine_payments`:
|
||||
|
||||
- `config_pda` — один PDA конфига, seed `shine_payments_config`.
|
||||
- `coef_limit_pda` — один PDA коэффициента/лимита/награды, seed `shine_payments_coef_limit`.
|
||||
- `queues_pda` — один PDA агрегатов очередей, seed `shine_payments_queues`.
|
||||
- `inflow_vault_pda` — один PDA-вольт входящих средств, seed `shine_payments_inflow_vault`.
|
||||
- `ticket_pda` — много PDA, по одному на тикет, seed `shine_payments_q1_ticket` или `shine_payments_q2_ticket` + индекс.
|
||||
- `manager_allowance_pda` — много PDA, по одному на менеджера, seed `shine_p_manager_allow` + адрес менеджера.
|
||||
|
||||
## Денежные потоки
|
||||
|
||||
### Регистрация
|
||||
|
||||
```text
|
||||
user signer -> shine_users::create_user_pda -> shine_payments::inflow_vault_pda
|
||||
```
|
||||
|
||||
Состав платежа:
|
||||
|
||||
- регистрационная комиссия;
|
||||
- оплата `additional_limit`.
|
||||
|
||||
### Увеличение лимита
|
||||
|
||||
```text
|
||||
user signer -> shine_users::update_user_pda -> shine_payments::inflow_vault_pda
|
||||
```
|
||||
|
||||
Состав платежа:
|
||||
|
||||
- только оплата `additional_limit`.
|
||||
|
||||
### Покупка тикета
|
||||
|
||||
```text
|
||||
buyer signer -> shine_payments::buy_ticket* -> dao_wallet
|
||||
```
|
||||
|
||||
При этом создается `ticket_pda`, но деньги в `inflow_vault_pda` на этом шаге не идут.
|
||||
|
||||
### Выплата
|
||||
|
||||
```text
|
||||
shine_payments::inflow_vault_pda -> ticket_recipient_wallet
|
||||
shine_payments::inflow_vault_pda -> dao_wallet
|
||||
shine_payments::inflow_vault_pda -> step_payout caller
|
||||
```
|
||||
|
||||
Если очереди пустые:
|
||||
|
||||
```text
|
||||
shine_payments::inflow_vault_pda -> dao_wallet
|
||||
```
|
||||
|
||||
## Что нужно создать на старте
|
||||
|
||||
Минимально:
|
||||
|
||||
1. Три program id для `shine_login_guard`, `shine_users`, `shine_payments`.
|
||||
2. Три upgrade-authority ключа или один временный deploy-ключ с четким планом передачи прав.
|
||||
3. DAO authority/treasury.
|
||||
4. `users_economy_config_pda`.
|
||||
5. `shine_payments` PDA: `config_pda`, `coef_limit_pda`, `queues_pda`, `inflow_vault_pda`.
|
||||
|
||||
Динамически будут создаваться:
|
||||
|
||||
- `user_pda` на каждого пользователя;
|
||||
- `ticket_pda` на каждый тикет;
|
||||
- `manager_allowance_pda` на каждого менеджера.
|
||||
@ -1,74 +0,0 @@
|
||||
# SHiNE DAO
|
||||
|
||||
## Кратко
|
||||
|
||||
DAO — управляющий слой Solana-части SHiNE. В текущем коде это не отдельная Anchor-программа в `programs/`, а модель управления через DAO-кошелек, DAO-authority, governance-скрипты и будущую передачу upgrade-authority программ.
|
||||
|
||||
## Что DAO должно уметь
|
||||
|
||||
1. Управлять казной.
|
||||
- Принимать средства на `dao_wallet`.
|
||||
- Выплачивать средства со счета DAO по решениям голосования.
|
||||
|
||||
2. Управлять настройками `shine_users`.
|
||||
- Обновлять регистрационную комиссию.
|
||||
- Обновлять цену шага лимита.
|
||||
- Обновлять стартовый бонус лимита.
|
||||
|
||||
3. Управлять настройками `shine_payments`.
|
||||
- Обновлять коэффициент выплат.
|
||||
- Обновлять лимит очереди.
|
||||
- Обновлять награду за вызов `step_payout`.
|
||||
|
||||
4. Управлять менеджерами.
|
||||
- Выдавать менеджеру лимит на добавление тикетов.
|
||||
- Отдельно учитывать лимиты Q1 и Q2.
|
||||
|
||||
5. Управлять правами программ.
|
||||
- Принять upgrade-authority `shine_users`.
|
||||
- Принять upgrade-authority `shine_payments`.
|
||||
- Позже принять upgrade-authority `shine_login_guard`, если это потребуется.
|
||||
|
||||
6. Управлять ключами DAO.
|
||||
- Добавлять управляющие ключи.
|
||||
- Отзывать или сжигать управляющие ключи.
|
||||
- Делать это через голосование, а не вручную одним админом.
|
||||
|
||||
7. Фиксировать решения.
|
||||
- Делать заявления/решения через governance-механику.
|
||||
- Привязывать важные изменения к proposal/vote/execute.
|
||||
|
||||
## Текущие адреса управления
|
||||
|
||||
В общем deploy-конфиге сейчас есть два важных адреса:
|
||||
|
||||
- `DAO_AUTHORITY` — используется `shine_users` для проверки права менять economy-конфиг.
|
||||
- `DAO_TREASURY_WALLET` — используется `shine_payments` как `dao_wallet`.
|
||||
|
||||
Сейчас они могут совпадать. В целевой DAO-модели их лучше рассматривать как разные роли:
|
||||
|
||||
- authority/governance signer — кто имеет право исполнять управленческие инструкции;
|
||||
- treasury wallet — счет, куда приходят деньги DAO.
|
||||
|
||||
## Передача прав
|
||||
|
||||
Рекомендуемый порядок:
|
||||
|
||||
1. Сначала стабилизировать и проверить `shine_users` и `shine_payments`.
|
||||
2. Передать DAO право обновлять настройки, если оно еще не передано.
|
||||
3. Передать DAO upgrade-authority второй и третьей программ.
|
||||
4. Оставить `shine_login_guard` на отдельном ключе до стабилизации словарей и правил логинов.
|
||||
5. После стабилизации решить отдельным голосованием, передавать ли первую программу DAO.
|
||||
|
||||
## Важное разделение
|
||||
|
||||
Есть два разных типа прав:
|
||||
|
||||
1. Право вызвать защищенную функцию программы.
|
||||
- Например, `update_coef_limit` или `grant_manager_limits`.
|
||||
- Проверяется внутри программы по `dao_wallet` или `DAO_AUTHORITY`.
|
||||
|
||||
2. Право обновить саму программу.
|
||||
- Это upgrade-authority Solana ProgramData.
|
||||
- Оно передается отдельной Solana-командой/DAO-транзакцией и не равно обычному PDA-счету.
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
# `shine_login_guard`
|
||||
|
||||
## Кратко
|
||||
|
||||
`shine_login_guard` — первая программа Solana-модуля SHiNE. Она проверяет логин перед регистрацией пользователя и возвращает класс логина.
|
||||
|
||||
Папка программы: `shine-solana/shine/programs/shine_login_guard/`.
|
||||
|
||||
## Текущая функция
|
||||
|
||||
1. `classify_login(login: String)`
|
||||
- Нормализует логин.
|
||||
- Проверяет длину и допустимые символы.
|
||||
- Сравнивает части логина со словарями premium/trademark.
|
||||
- Возвращает результат через `set_return_data`.
|
||||
|
||||
Классы результата:
|
||||
|
||||
- `0` — обычный логин, регистрацию можно продолжать.
|
||||
- `1` — premium-логин.
|
||||
- `2` — trademark-логин, нужна отдельная проверка/разрешение.
|
||||
|
||||
## Правила нормализации и классификации
|
||||
|
||||
Текущая логика из `programs/shine_login_guard/src/lib.rs`:
|
||||
|
||||
- пустой логин или логин длиннее 20 символов получает класс `premium`;
|
||||
- `_` при нормализации удаляется;
|
||||
- допустимы только ASCII-буквы и цифры, остальные символы дают класс `premium`;
|
||||
- после удаления `_` результат приводится к нижнему регистру;
|
||||
- логины длиной 7 символов или меньше считаются `premium`;
|
||||
- логин разбивается максимум на 3 словарных фрагмента;
|
||||
- если среди найденных фрагментов есть trademark-слово, результат `trademark`;
|
||||
- если найдены только premium-слова, результат `premium`;
|
||||
- если разбиение по словарям не найдено, результат `free`.
|
||||
|
||||
Словари собираются на этапе build из файлов:
|
||||
|
||||
- `programs/shine_login_guard/src/dictionaries/premium/*.txt`
|
||||
- `programs/shine_login_guard/src/dictionaries/trademarks/*.txt`
|
||||
|
||||
## Роль в общей схеме
|
||||
|
||||
`shine_users::create_user_pda` вызывает `shine_login_guard` через CPI и продолжает регистрацию только если логин получил класс `0`.
|
||||
|
||||
## Ключи и управление
|
||||
|
||||
На старте удобно считать, что у программы есть отдельный управляющий ключ `key_1`.
|
||||
|
||||
Текущая рекомендация:
|
||||
|
||||
- пока оставить `shine_login_guard` под отдельным ключом;
|
||||
- не передавать ее DAO до стабилизации правил premium/trademark;
|
||||
- позже можно передать upgrade-authority DAO, чтобы изменения словарей и правил проходили через голосование.
|
||||
|
||||
## Счета
|
||||
|
||||
Собственных постоянных PDA-счетов у программы сейчас нет. Для проверки нужен только подписант транзакции в `ClassifyLogin`.
|
||||
@ -1,173 +0,0 @@
|
||||
# `shine_payments`
|
||||
|
||||
## Кратко
|
||||
|
||||
`shine_payments` — третья программа Solana-модуля SHiNE. Она отвечает за vault входящих средств, DAO-казну, покупку тикетов, менеджерские лимиты, очереди выплат и пошаговое исполнение выплат.
|
||||
|
||||
Папка программы: `shine-solana/shine/programs/shine_payments/`.
|
||||
|
||||
## Текущие функции
|
||||
|
||||
1. `init`
|
||||
- Создает основные PDA: `config_pda`, `coef_limit_pda`, `queues_pda`, `inflow_vault_pda`.
|
||||
- Записывает `dao_wallet` и стартовые параметры выплат.
|
||||
|
||||
2. `update_coef_limit`
|
||||
- Обновляет коэффициент выплаты, лимит очереди и награду вызвавшему `step_payout`.
|
||||
- Требует подпись DAO-кошелька из `ConfigState`.
|
||||
|
||||
3. `grant_manager_limits`
|
||||
- DAO выдает менеджеру лимиты на создание тикетов в очередях Q1/Q2.
|
||||
- Создает или обновляет `manager_allowance_pda`.
|
||||
|
||||
4. `buy_ticket`
|
||||
- Покупка тикета с суммой в lamports, пересчетом через Pyth SOL/USD.
|
||||
|
||||
5. `buy_ticket_usd`
|
||||
- Покупка тикета от USD-центов с защитой по максимальному платежу в lamports.
|
||||
|
||||
6. `buy_ticket_sol`
|
||||
- Покупка тикета в lamports с проверкой минимального ожидаемого USD-эквивалента.
|
||||
|
||||
7. `manager_add_ticket`
|
||||
- Менеджер создает тикет за счет выданного ему DAO-лимита.
|
||||
|
||||
8. `step_payout`
|
||||
- Любой подписант может вызвать шаг выплат.
|
||||
- Программа выплачивает следующий тикет, DAO-часть и награду вызывающему.
|
||||
|
||||
9. `change_ticket_recipient`
|
||||
- Текущий получатель тикета может поменять адрес получателя, если тикет еще не следующий на выплату.
|
||||
|
||||
## Аргументы инструкций
|
||||
|
||||
`init` аргументов не принимает.
|
||||
|
||||
`update_coef_limit`:
|
||||
|
||||
- `coef_ppm: u64`
|
||||
- `limit_usd_cents: u64`
|
||||
- `call_reward_lamports: u64`
|
||||
|
||||
`grant_manager_limits`:
|
||||
|
||||
- `manager_wallet: Pubkey`
|
||||
- `add_q1_usd_cents: u64`
|
||||
- `add_q2_usd_cents: u64`
|
||||
|
||||
`buy_ticket`:
|
||||
|
||||
- `amount_lamports: u64`
|
||||
- `recipient_wallet: Pubkey`
|
||||
|
||||
`buy_ticket_usd`:
|
||||
|
||||
- `amount_usd_cents: u64`
|
||||
- `max_pay_lamports: u64`
|
||||
- `recipient_wallet: Pubkey`
|
||||
|
||||
`buy_ticket_sol`:
|
||||
|
||||
- `amount_lamports: u64`
|
||||
- `min_expected_usd_cents: u64`
|
||||
- `recipient_wallet: Pubkey`
|
||||
|
||||
`manager_add_ticket`:
|
||||
|
||||
- `queue_id: u8` — только `1` или `2`
|
||||
- `recipient_wallet: Pubkey`
|
||||
- `payout_usd_cents: u64`
|
||||
|
||||
`change_ticket_recipient`:
|
||||
|
||||
- `new_recipient_wallet: Pubkey`
|
||||
|
||||
## Главные PDA
|
||||
|
||||
1. `config_pda`
|
||||
- Seed: `shine_payments_config`.
|
||||
- Хранит `dao_wallet` и `inflow_vault`.
|
||||
- Размер PDA: `8 + 160` байт.
|
||||
|
||||
2. `coef_limit_pda`
|
||||
- Seed: `shine_payments_coef_limit`.
|
||||
- Хранит коэффициент выплат, лимит и награду `step_payout`.
|
||||
- Размер PDA: `8 + 96` байт.
|
||||
|
||||
3. `queues_pda`
|
||||
- Seed: `shine_payments_queues`.
|
||||
- Хранит агрегаты очередей Q1/Q2.
|
||||
- Размер PDA: `8 + 192` байт.
|
||||
|
||||
4. `inflow_vault_pda`
|
||||
- Seed: `shine_payments_inflow_vault`.
|
||||
- Принимает деньги от `shine_users`.
|
||||
- Из него выполняются выплаты тикетам, DAO и вызывающему `step_payout`.
|
||||
- Размер PDA: `8 + 32` байт.
|
||||
|
||||
5. `ticket_pda`
|
||||
- Seed зависит от очереди и индекса тикета.
|
||||
- Отдельная PDA-запись на каждый тикет.
|
||||
- Q1 seed: `shine_payments_q1_ticket` + `ticket_index`.
|
||||
- Q2 seed: `shine_payments_q2_ticket` + `ticket_index`.
|
||||
- Размер PDA: `8 + 160` байт.
|
||||
|
||||
6. `manager_allowance_pda`
|
||||
- Seed: `shine_p_manager_allow` + адрес менеджера.
|
||||
- Хранит доступный лимит менеджера по Q1/Q2.
|
||||
- Размер PDA: `8 + 128` байт.
|
||||
|
||||
## Текущие параметры
|
||||
|
||||
Параметры initial config из `programs/shine_payments/src/settings.rs`:
|
||||
|
||||
| Поле | Значение | Смысл |
|
||||
| --- | --- | --- |
|
||||
| `START_COEF_PPM` | `5_000_000` | коэффициент 5.0x в ppm-масштабе |
|
||||
| `START_LIMIT_USD_CENTS` | `1_000_000` | стартовый лимит Q1: 10_000 USD |
|
||||
| `START_CALL_REWARD_LAMPORTS` | `8_000_000` | награда вызвавшему `step_payout`, 0.008 SOL |
|
||||
| `MAX_CALL_REWARD_LAMPORTS` | `10_000_000` | максимум награды, 0.01 SOL |
|
||||
| `ORACLE_MAX_AGE_SECS` | `120` | максимальный возраст цены Pyth |
|
||||
|
||||
Для расчетов используется Pyth SOL/USD:
|
||||
|
||||
- feed id: `0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d`
|
||||
- price update account: `7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE`
|
||||
|
||||
## Деньги
|
||||
|
||||
Входы:
|
||||
|
||||
- из `shine_users` в `inflow_vault_pda` при регистрации и увеличении лимита;
|
||||
- от покупателя тикета сразу в `dao_wallet` при `buy_ticket*`.
|
||||
|
||||
Выходы:
|
||||
|
||||
- из `inflow_vault_pda` получателю тикета;
|
||||
- из `inflow_vault_pda` в `dao_wallet`;
|
||||
- из `inflow_vault_pda` вызвавшему `step_payout`;
|
||||
- если очереди пустые, весь доступный остаток `inflow_vault_pda` переводится в DAO.
|
||||
|
||||
## Очереди и выплаты
|
||||
|
||||
Выплаты идут строго пошагово:
|
||||
|
||||
- если есть невыплаченные Q1-тикеты, `step_payout` берет следующий Q1;
|
||||
- если Q1 пустая, берется следующий Q2;
|
||||
- для Q1 DAO-часть равна сумме тикета в USD;
|
||||
- для Q2 DAO-часть равна двойной сумме тикета в USD;
|
||||
- перед выплатой суммы пересчитываются из USD-центов в lamports по Pyth SOL/USD;
|
||||
- если в `inflow_vault_pda` не хватает средств на тикет, DAO-часть и награду вызвавшему, шаг отклоняется.
|
||||
|
||||
`change_ticket_recipient` запрещает менять получателя у тикета, который является следующим на выплату.
|
||||
|
||||
## Ключи и управление
|
||||
|
||||
На старте удобно считать, что у программы есть отдельный управляющий ключ `key_3`.
|
||||
|
||||
Целевая модель:
|
||||
|
||||
- `update_coef_limit` вызывает DAO;
|
||||
- `grant_manager_limits` вызывает DAO;
|
||||
- upgrade-authority программы после проверки передается DAO;
|
||||
- `step_payout` остается открытым для любого подписанта, чтобы выплаты не зависели от одного оператора.
|
||||
@ -1,136 +0,0 @@
|
||||
# `shine_users`
|
||||
|
||||
## Кратко
|
||||
|
||||
`shine_users` — вторая программа Solana-модуля SHiNE. Она отвечает за создание и обновление пользовательской PDA-записи, проверку подписи записи, проверку логина через `shine_login_guard` и оплату регистрации/дополнительного лимита.
|
||||
|
||||
Папка программы: `shine-solana/shine/programs/shine_users/`.
|
||||
|
||||
## Текущие функции
|
||||
|
||||
1. `init_users_economy_config`
|
||||
- Создает PDA с экономическими настройками пользователей.
|
||||
- Записывает стартовую регистрационную комиссию, цену шага лимита и стартовый бонус лимита.
|
||||
|
||||
2. `update_users_economy_config`
|
||||
- Обновляет экономические настройки.
|
||||
- Требует подпись `DAO_AUTHORITY` из общего deploy-конфига.
|
||||
|
||||
3. `create_user_pda`
|
||||
- Проверяет логин через `shine_login_guard`.
|
||||
- Проверяет структуру полей пользователя.
|
||||
- Проверяет подпись записи root-ключом пользователя.
|
||||
- Создает `user_pda` по seed `login=<normalized_login>`.
|
||||
- Переводит оплату регистрации и дополнительного лимита в `shine_payments::inflow_vault_pda`.
|
||||
|
||||
4. `update_user_pda`
|
||||
- Проверяет неизменяемые поля пользователя.
|
||||
- Проверяет `prev_hash`, новую подпись и новое состояние последнего блока.
|
||||
- При необходимости расширяет PDA.
|
||||
- Переводит оплату дополнительного лимита в `shine_payments::inflow_vault_pda`.
|
||||
|
||||
## Аргументы инструкций
|
||||
|
||||
`init_users_economy_config` аргументов не принимает.
|
||||
|
||||
`update_users_economy_config`:
|
||||
|
||||
- `registration_fee_lamports: u64`
|
||||
- `lamports_per_limit_step: u64`
|
||||
- `start_bonus_limit: u64`
|
||||
|
||||
`create_user_pda`:
|
||||
|
||||
- `login: String`
|
||||
- `root_key: Pubkey`
|
||||
- `created_at_ms: u64`
|
||||
- `additional_limit: u64`
|
||||
- `fields: UserMutableFields`
|
||||
- `signature: Vec<u8>`
|
||||
|
||||
`update_user_pda`:
|
||||
|
||||
- `login: String`
|
||||
- `root_key: Pubkey`
|
||||
- `created_at_ms: u64`
|
||||
- `updated_at_ms: u64`
|
||||
- `version: u32`
|
||||
- `prev_hash: Vec<u8>`
|
||||
- `additional_limit: u64`
|
||||
- `fields: UserMutableFields`
|
||||
- `signature: Vec<u8>`
|
||||
|
||||
`UserMutableFields`:
|
||||
|
||||
- `device_key: Pubkey`
|
||||
- `blockchain_public_key: Pubkey`
|
||||
- `blockchain_name: String`
|
||||
- `used_bytes: u64`
|
||||
- `last_block_number: u32`
|
||||
- `last_block_hash: Vec<u8>` — ровно 32 байта
|
||||
- `last_block_signature: Vec<u8>` — ровно 64 байта
|
||||
- `arweave_tx_id: String`
|
||||
- `is_server: bool`
|
||||
- `server_key: Pubkey`
|
||||
- `server_address: String`
|
||||
- `sync_servers: Vec<String>`
|
||||
- `access_servers: Vec<String>`
|
||||
- `trusted_count: u8`
|
||||
|
||||
## Главные PDA
|
||||
|
||||
1. `user_pda`
|
||||
- PDA записи пользователя.
|
||||
- Seed: `login=<normalized_login>`.
|
||||
- Создается отдельно для каждого логина.
|
||||
- Стартовый размер: `768` байт.
|
||||
- При обновлении может расширяться через `realloc`, но один auto-realloc ограничен `10_000` байт.
|
||||
|
||||
2. `users_economy_config_pda`
|
||||
- PDA с настройками экономики.
|
||||
- Seed: `shine_users_economy_config`.
|
||||
- Хранит регистрационную комиссию, цену шага лимита и стартовый бонус.
|
||||
- Размер PDA: `8 + 96` байт.
|
||||
|
||||
## Текущие параметры экономики
|
||||
|
||||
Параметры initial config из `programs/shine_users/src/settings.rs`:
|
||||
|
||||
| Поле | Значение | Смысл |
|
||||
| --- | --- | --- |
|
||||
| `START_REGISTRATION_FEE_LAMPORTS` | `10_000_000` | стартовая комиссия регистрации, 0.01 SOL |
|
||||
| `LIMIT_STEP` | `10_000` | шаг `additional_limit` |
|
||||
| `START_LAMPORTS_PER_LIMIT_STEP` | `100_000` | 0.0001 SOL за один шаг лимита |
|
||||
| `START_BONUS_LIMIT` | `100_000` | стартовый бесплатный лимит при регистрации |
|
||||
|
||||
`additional_limit` в create/update должен быть кратен `LIMIT_STEP`.
|
||||
|
||||
## Связь с другими программами
|
||||
|
||||
`shine_users` зависит от:
|
||||
|
||||
- `shine_login_guard` — для проверки логина при создании пользователя;
|
||||
- `shine_payments` — для вычисления и проверки `inflow_vault_pda`, куда уходят платежи.
|
||||
|
||||
`create_user_pda` делает CPI-вызов `shine_login_guard::classify_login` и принимает только результат `0`. Premium/trademark логины сейчас отклоняются ошибками `PremiumLogin` или `TrademarkLoginRequiresReview`.
|
||||
|
||||
Подпись `user_pda` и подпись состояния последнего блока проверяются через встроенную Solana Ed25519-инструкцию, которая должна идти раньше инструкции `shine_users` в той же транзакции.
|
||||
|
||||
## Деньги
|
||||
|
||||
Деньги из `shine_users` идут только в `inflow_vault_pda` программы `shine_payments`.
|
||||
|
||||
Потоки:
|
||||
|
||||
- `create_user_pda`: регистрационная комиссия + оплата `additional_limit`;
|
||||
- `update_user_pda`: оплата `additional_limit`, если она больше нуля.
|
||||
|
||||
## Ключи и управление
|
||||
|
||||
На старте удобно считать, что у программы есть отдельный управляющий ключ `key_2`.
|
||||
|
||||
Целевая модель:
|
||||
|
||||
- economy-настройки меняет DAO-authority;
|
||||
- upgrade-authority программы после проверки передается DAO;
|
||||
- пользовательские операции `create_user_pda` и `update_user_pda` остаются доступными обычным пользователям при корректных подписях и оплате.
|
||||
@ -1,54 +0,0 @@
|
||||
flowchart LR
|
||||
U[Пользователь / signer]
|
||||
B[Покупатель тикета]
|
||||
M[Менеджер]
|
||||
C[Любой caller step_payout]
|
||||
|
||||
LG[1. shine_login_guard<br/>classify_login]
|
||||
USERS[2. shine_users<br/>create_user_pda / update_user_pda]
|
||||
PAY[3. shine_payments<br/>vault / tickets / payouts]
|
||||
DAO[SHiNE DAO<br/>governance / authority / treasury]
|
||||
|
||||
USERPDA[(user_pda<br/>по login)]
|
||||
ECON[(users_economy_config_pda)]
|
||||
CONFIG[(config_pda)]
|
||||
COEF[(coef_limit_pda)]
|
||||
QUEUES[(queues_pda)]
|
||||
VAULT[(inflow_vault_pda)]
|
||||
TICKET[(ticket_pda)]
|
||||
ALLOW[(manager_allowance_pda)]
|
||||
|
||||
U -->|логин| USERS
|
||||
USERS -->|CPI проверка| LG
|
||||
USERS -->|создает/обновляет| USERPDA
|
||||
USERS -->|читает экономику| ECON
|
||||
U -->|регистрация / лимит| VAULT
|
||||
|
||||
DAO -->|update economy| USERS
|
||||
DAO -->|update coef/limit| PAY
|
||||
DAO -->|grant manager limits| PAY
|
||||
DAO -->|создает/отзывает ключи| DAO
|
||||
|
||||
PAY --> CONFIG
|
||||
PAY --> COEF
|
||||
PAY --> QUEUES
|
||||
PAY --> VAULT
|
||||
PAY --> TICKET
|
||||
PAY --> ALLOW
|
||||
|
||||
B -->|buy_ticket*| PAY
|
||||
B -->|оплата покупки тикета| DAO
|
||||
PAY -->|создает тикет| TICKET
|
||||
|
||||
M -->|manager_add_ticket| PAY
|
||||
ALLOW -->|лимиты Q1/Q2| M
|
||||
|
||||
C -->|step_payout| PAY
|
||||
VAULT -->|выплата тикета| U
|
||||
VAULT -->|DAO-часть| DAO
|
||||
VAULT -->|call reward| C
|
||||
|
||||
DAO -. upgrade authority после передачи .-> USERS
|
||||
DAO -. upgrade authority после передачи .-> PAY
|
||||
DAO -. позже возможно .-> LG
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 234 KiB |
@ -1,139 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1400" height="900" viewBox="0 0 1400 900" role="img" aria-labelledby="title desc">
|
||||
<title id="title">Архитектура Solana-программ SHiNE</title>
|
||||
<desc id="desc">Схема трех программ, DAO, PDA-счетов и движения денег.</desc>
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,6 L9,3 z" fill="#2f3a45"/>
|
||||
</marker>
|
||||
<marker id="moneyArrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,6 L9,3 z" fill="#0a7f62"/>
|
||||
</marker>
|
||||
<style>
|
||||
.bg { fill: #f7f8fa; }
|
||||
.title { font: 700 30px Arial, sans-serif; fill: #1f2933; }
|
||||
.subtitle { font: 400 16px Arial, sans-serif; fill: #52606d; }
|
||||
.box { fill: #ffffff; stroke: #9aa5b1; stroke-width: 2; rx: 8; }
|
||||
.program { fill: #e8f1ff; stroke: #3465a4; }
|
||||
.dao { fill: #fff3d6; stroke: #b7791f; }
|
||||
.pda { fill: #edf7ed; stroke: #2f855a; }
|
||||
.actor { fill: #f3e8ff; stroke: #805ad5; }
|
||||
.txt { font: 700 17px Arial, sans-serif; fill: #1f2933; }
|
||||
.small { font: 400 13px Arial, sans-serif; fill: #3e4c59; }
|
||||
.line { stroke: #2f3a45; stroke-width: 2.2; fill: none; marker-end: url(#arrow); }
|
||||
.money { stroke: #0a7f62; stroke-width: 3; fill: none; marker-end: url(#moneyArrow); }
|
||||
.dashed { stroke-dasharray: 8 7; }
|
||||
.legend { font: 400 14px Arial, sans-serif; fill: #3e4c59; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<rect class="bg" x="0" y="0" width="1400" height="900"/>
|
||||
<text class="title" x="52" y="54">SHiNE Solana: программы, DAO, счета и движение денег</text>
|
||||
<text class="subtitle" x="52" y="82">Текущая модель: три Anchor-программы, DAO/authority как управляющий слой, inflow vault и DAO treasury.</text>
|
||||
|
||||
<rect class="box actor" x="52" y="150" width="210" height="78"/>
|
||||
<text class="txt" x="72" y="181">Пользователь</text>
|
||||
<text class="small" x="72" y="206">signer, root_key, device_key</text>
|
||||
|
||||
<rect class="box actor" x="52" y="310" width="210" height="78"/>
|
||||
<text class="txt" x="72" y="341">Покупатель тикета</text>
|
||||
<text class="small" x="72" y="366">buy_ticket*</text>
|
||||
|
||||
<rect class="box actor" x="52" y="470" width="210" height="78"/>
|
||||
<text class="txt" x="72" y="501">Менеджер</text>
|
||||
<text class="small" x="72" y="526">manager_add_ticket</text>
|
||||
|
||||
<rect class="box actor" x="52" y="630" width="210" height="78"/>
|
||||
<text class="txt" x="72" y="661">Любой caller</text>
|
||||
<text class="small" x="72" y="686">step_payout</text>
|
||||
|
||||
<rect class="box program" x="360" y="126" width="270" height="96"/>
|
||||
<text class="txt" x="382" y="160">1. shine_login_guard</text>
|
||||
<text class="small" x="382" y="186">classify_login</text>
|
||||
<text class="small" x="382" y="205">free / premium / trademark</text>
|
||||
|
||||
<rect class="box program" x="360" y="286" width="270" height="112"/>
|
||||
<text class="txt" x="382" y="320">2. shine_users</text>
|
||||
<text class="small" x="382" y="346">create_user_pda</text>
|
||||
<text class="small" x="382" y="365">update_user_pda</text>
|
||||
<text class="small" x="382" y="384">economy config</text>
|
||||
|
||||
<rect class="box program" x="360" y="518" width="270" height="122"/>
|
||||
<text class="txt" x="382" y="552">3. shine_payments</text>
|
||||
<text class="small" x="382" y="578">vault, tickets, queues</text>
|
||||
<text class="small" x="382" y="597">grant_manager_limits</text>
|
||||
<text class="small" x="382" y="616">step_payout</text>
|
||||
|
||||
<rect class="box dao" x="776" y="126" width="270" height="122"/>
|
||||
<text class="txt" x="798" y="160">SHiNE DAO</text>
|
||||
<text class="small" x="798" y="186">governance / authority</text>
|
||||
<text class="small" x="798" y="205">treasury dao_wallet</text>
|
||||
<text class="small" x="798" y="224">ключи через голосование</text>
|
||||
|
||||
<rect class="box pda" x="776" y="306" width="270" height="84"/>
|
||||
<text class="txt" x="798" y="340">shine_users PDA</text>
|
||||
<text class="small" x="798" y="365">user_pda, economy_config</text>
|
||||
|
||||
<rect class="box pda" x="776" y="500" width="270" height="150"/>
|
||||
<text class="txt" x="798" y="534">shine_payments PDA</text>
|
||||
<text class="small" x="798" y="560">config_pda, coef_limit_pda</text>
|
||||
<text class="small" x="798" y="579">queues_pda</text>
|
||||
<text class="small" x="798" y="598">inflow_vault_pda</text>
|
||||
<text class="small" x="798" y="617">ticket_pda, manager_allowance</text>
|
||||
|
||||
<rect class="box pda" x="1134" y="500" width="214" height="88"/>
|
||||
<text class="txt" x="1156" y="534">inflow_vault</text>
|
||||
<text class="small" x="1156" y="560">деньги регистрации</text>
|
||||
|
||||
<rect class="box dao" x="1134" y="170" width="214" height="88"/>
|
||||
<text class="txt" x="1156" y="204">DAO treasury</text>
|
||||
<text class="small" x="1156" y="230">dao_wallet</text>
|
||||
|
||||
<path class="line" d="M262 189 C300 189, 318 334, 360 334"/>
|
||||
<text class="small" x="270" y="286">регистрация / update</text>
|
||||
|
||||
<path class="line" d="M360 314 C322 250, 320 176, 360 174"/>
|
||||
<text class="small" x="330" y="250">CPI login</text>
|
||||
|
||||
<path class="line" d="M630 342 L776 342"/>
|
||||
<text class="small" x="646" y="329">создает/обновляет</text>
|
||||
|
||||
<path class="money" d="M262 205 C438 432, 1010 390, 1134 530"/>
|
||||
<text class="small" x="430" y="430">регистрация и лимит -> inflow_vault</text>
|
||||
|
||||
<path class="money" d="M262 349 C540 260, 870 244, 1134 214"/>
|
||||
<text class="small" x="538" y="270">покупка тикета -> DAO treasury</text>
|
||||
|
||||
<path class="line" d="M262 509 L360 579"/>
|
||||
<text class="small" x="276" y="540">создать тикет</text>
|
||||
|
||||
<path class="line" d="M630 579 L776 575"/>
|
||||
<text class="small" x="648" y="562">PDA состояния</text>
|
||||
|
||||
<path class="line" d="M1046 575 L1134 548"/>
|
||||
|
||||
<path class="money" d="M1134 560 C970 700, 580 728, 262 669"/>
|
||||
<text class="small" x="650" y="720">call reward caller</text>
|
||||
|
||||
<path class="money" d="M1134 536 C860 754, 426 238, 262 194"/>
|
||||
<text class="small" x="632" y="760">выплата получателю тикета</text>
|
||||
|
||||
<path class="money" d="M1241 500 L1241 258"/>
|
||||
<text class="small" x="1254" y="380">DAO-часть выплат</text>
|
||||
|
||||
<path class="line" d="M776 188 L630 342"/>
|
||||
<text class="small" x="642" y="250">update economy</text>
|
||||
|
||||
<path class="line" d="M776 216 C690 290, 666 516, 630 558"/>
|
||||
<text class="small" x="654" y="438">settings / managers</text>
|
||||
|
||||
<path class="line dashed" d="M910 248 C850 702, 620 720, 520 640"/>
|
||||
<text class="small" x="690" y="690">upgrade-authority: users/payments; login_guard позже</text>
|
||||
|
||||
<rect class="box" x="52" y="808" width="1296" height="54"/>
|
||||
<line x1="74" y1="835" x2="132" y2="835" class="line"/>
|
||||
<text class="legend" x="146" y="840">логические вызовы и управление</text>
|
||||
<line x1="374" y1="835" x2="432" y2="835" class="money"/>
|
||||
<text class="legend" x="446" y="840">движение SOL/lamports</text>
|
||||
<line x1="682" y1="835" x2="740" y2="835" class="line dashed"/>
|
||||
<text class="legend" x="754" y="840">будущая передача upgrade-authority DAO</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.1 KiB |
@ -1,59 +0,0 @@
|
||||
# Деплой SHiNE (шаблон)
|
||||
|
||||
Этот раздел хранит актуальные инструкции по деплою.
|
||||
|
||||
## Базовый сервер
|
||||
|
||||
- SSH: `player@shineup.me`
|
||||
- Домен: `shineup.me`
|
||||
- Базовый путь: `/home/player`
|
||||
|
||||
Для всех рабочих инструкций и скриптов использовать доменное имя `shineup.me`, а не фиксированный IP:
|
||||
|
||||
- актуальный IP должен браться через DNS-резолв на момент подключения;
|
||||
- ручное дублирование IP в документации и deploy-скриптах не поддерживать.
|
||||
|
||||
## Локальные команды
|
||||
|
||||
- Деплой сервера: `./gradlew deployServer`
|
||||
- Деплой UI: `./gradlew deployUI`
|
||||
- Локальный запуск: `./gradlew startLocal`
|
||||
|
||||
## UI-деплой и Caddy (обязательно)
|
||||
|
||||
- Целевая директория UI-деплоя: `/home/player/SHiNE/shine-ui`.
|
||||
- `Caddyfile` на сервере должен смотреть в ту же директорию через `root * /home/player/SHiNE/shine-ui`.
|
||||
- В `deploy_shine-PWA.sh` добавлена проверка: скрипт ищет блок `shineup.me { ... }` (или значение `EXPECTED_CADDY_SITE`) и проверяет `root` внутри этого блока.
|
||||
- Если `root` внутри целевого блока не совпадает, деплой прерывается с ошибкой.
|
||||
- Для ручного обхода проверки (только осознанно): `ALLOW_CADDY_MISMATCH=1 ./gradlew deployUI`.
|
||||
- При необходимости можно явно переопределить путь деплоя:
|
||||
- `REMOTE_UI_DIR=/нужный/путь ./gradlew deployUI`
|
||||
- `EXPECTED_CADDY_UI_ROOT=/нужный/путь ./gradlew deployUI`
|
||||
- `EXPECTED_CADDY_SITE=example.com ./gradlew deployUI`
|
||||
|
||||
## Временные тестовые сайты Solana tickets
|
||||
|
||||
- Для HTML UI программы `shine_payments` используется отдельный временный тестовый сайт.
|
||||
- Основной каталог публикации:
|
||||
- `/home/player/sites/test-solana-tickets.shineup.me`
|
||||
- Рабочие домены:
|
||||
- `https://test-solana-tickets.shineup.me`
|
||||
- `https://test-solana-tickets.shiningpeople.ru`
|
||||
- Назначение:
|
||||
- ручная проверка сценариев покупки билетов;
|
||||
- проверка DAO-инструментов и лимитов менеджеров;
|
||||
- проверка ручного добавления билетов и `step_payout`.
|
||||
- Эти сайты не считать основным UI SHiNE; это отдельная тестовая публикация под Solana-часть.
|
||||
|
||||
### Важно для локального UI (history-router / Ctrl+F5)
|
||||
|
||||
- Локальный UI **обязательно** поднимать только через `./gradlew startLocal`.
|
||||
- Эта задача запускает `scripts/local_spa_server.py`, который делает SPA fallback: любой неизвестный путь (`/m/...`, `/channel/...`) возвращает `index.html`.
|
||||
- Это обязательно для корректной работы `Ctrl+F5` на внутренних роутов без `404`.
|
||||
- Рабочий URL выводится задачей в консоль в формате: `http://localhost:<WEB_PORT>/?localWsPort=<WS_PORT>`.
|
||||
|
||||
## Обязательные правила
|
||||
|
||||
1. Перед серверным деплоем проверить локально.
|
||||
2. При нестандартном деплое (другой хост, другая структура, ручные шаги) обязательно уточнить у пользователя, нужно ли обновить этот шаблон.
|
||||
3. Если деплой-процесс изменился, этот файл и файлы в `servers/` обновлять в том же коммите.
|
||||
@ -1,36 +0,0 @@
|
||||
# Локальный деплой SHiNE-agent-bot-coder (systemd, пользователь ai)
|
||||
|
||||
## Где находится сервис
|
||||
- Папка сервиса: `SHiNE-agent-bot-coder/`
|
||||
- Systemd unit: `SHiNE-agent-bot-coder/scripts/systemd/shine-agent-bot-coder.service`
|
||||
- Скрипт установки: `SHiNE-agent-bot-coder/scripts/systemd/install-local-systemd.sh`
|
||||
|
||||
## Предусловия
|
||||
1. Заполнен `.env` на основе `.env.example`.
|
||||
2. Доступен рабочий Codex CLI:
|
||||
- `/home/ai/.cache/JetBrains/IntelliJIdea2026.1/aia/codex/bin/codex-x86_64-unknown-linux-musl`
|
||||
3. На машине установлен `systemd --user`.
|
||||
|
||||
## Установка
|
||||
Из корня репозитория:
|
||||
|
||||
```bash
|
||||
bash SHiNE-agent-bot-coder/scripts/systemd/install-local-systemd.sh
|
||||
```
|
||||
|
||||
Скрипт:
|
||||
1. проверяет наличие `python3`;
|
||||
2. копирует unit в `~/.config/systemd/user/`;
|
||||
3. делает `systemctl --user daemon-reload`;
|
||||
4. включает автозапуск и стартует сервис.
|
||||
|
||||
## Проверка
|
||||
```bash
|
||||
systemctl --user status shine-agent-bot-coder --no-pager
|
||||
journalctl --user -u shine-agent-bot-coder -f
|
||||
```
|
||||
|
||||
## Перезапуск после изменений
|
||||
```bash
|
||||
systemctl --user restart shine-agent-bot-coder
|
||||
```
|
||||
@ -1,29 +0,0 @@
|
||||
# Сервер `93.170.12.154` — резервный
|
||||
|
||||
- Пользователь: `player`
|
||||
- Каталог SHiNE: `/home/player/SHiNE`
|
||||
- UI исходник (после rsync): `/home/player/SHiNE/SHiNE-UI`
|
||||
- UI публикация для Caddy: `/var/www/shine-ui`
|
||||
- Сервер: `/home/player/SHiNE/SHiNE-server/shine-server.jar`
|
||||
- Данные: `/home/player/SHiNE/SHiNE-server/data/`
|
||||
- `shine.sqlite`
|
||||
- `*.bch`
|
||||
- Логи сервера: `/home/player/SHiNE/SHiNE-server/logs/app.log`
|
||||
|
||||
## Сервисы
|
||||
|
||||
- `shine-server.service` (systemd)
|
||||
- `caddy.service` (systemd)
|
||||
|
||||
## Статус
|
||||
|
||||
- Резервный сервер для SHiNE.
|
||||
- Основной прод-сервер: `shineup.me` (подключение через `player@shineup.me`, IP определяется через DNS).
|
||||
|
||||
## Caddy
|
||||
|
||||
- Конфиг: `/etc/caddy/Caddyfile`
|
||||
- Настройки:
|
||||
- `no-store/no-cache` заголовки;
|
||||
- `try_files {path} /index.html` (SPA fallback);
|
||||
- `reverse_proxy /ws* -> 127.0.0.1:7070`.
|
||||
@ -1,35 +0,0 @@
|
||||
# Сервер `shineup.me` — основной
|
||||
|
||||
- SSH: `player@shineup.me`
|
||||
- Определение IP: через DNS-резолв домена `shineup.me` на момент подключения
|
||||
- Пользователь: `player`
|
||||
- Базовый путь: `/home/player`
|
||||
- Каталог SHiNE: `/home/player/SHiNE`
|
||||
- UI публикация: `/home/player/SHiNE/shine-ui`
|
||||
- Сервер: `/home/player/SHiNE/shine-server/shine-server.jar`
|
||||
- Данные: `/home/player/SHiNE/shine-server/data/`
|
||||
- Логи сервера: `/home/player/SHiNE/shine-server/logs/app.log`
|
||||
|
||||
## Сервисы
|
||||
|
||||
- `shine-server.service` (systemd)
|
||||
- `caddy.service` (systemd)
|
||||
|
||||
## Caddy
|
||||
|
||||
- Активный конфиг (через systemd `ExecStart`): `/home/player/SHiNE/caddy/Caddyfile`
|
||||
- Для UI:
|
||||
- `root * /home/player/SHiNE/shine-ui`
|
||||
- `try_files {path} /index.html` (SPA fallback)
|
||||
- no-cache заголовки
|
||||
- `reverse_proxy /ws* -> 127.0.0.1:7070`
|
||||
|
||||
## Дополнительно
|
||||
|
||||
- Для отдельной админки `shine_payments` используется каталог:
|
||||
- `/home/player/sites/test-solana-tickets.shineup.me`
|
||||
- Эта публикация используется как временный тестовый сайт для сценариев покупки билетов и выплат `shine_payments`.
|
||||
- Домены этой публикации:
|
||||
- `https://test-solana-tickets.shineup.me`
|
||||
- `https://test-solana-tickets.shiningpeople.ru`
|
||||
- Для всех deploy-скриптов и инструкций использовать именно `player@shineup.me`, без жёсткой фиксации IP.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user