SHiNE-server/shine-solana/shine/doc/programs/shine_users.md

563 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Программа `shine_users`
Документ описывает целевое поведение Solana-программы регистрации пользователей SHiNE.
Назначение документа:
- быть источником истины при поддержке текущей реализации;
- позволить заново реализовать программу без Anchor;
- зафиксировать инварианты, форматы и правила проверки.
Если код программы расходится с этим документом, это считается ошибкой: нужно либо исправить код, либо обновить документ в том же изменении.
## 1. Назначение программы
`shine_users` хранит публичную пользовательскую запись SHiNE в Solana PDA и управляет её экономикой.
Программа отвечает за:
- создание `user_pda` по логину;
- обновление `user_pda` без смены логина и root key;
- хранение economy-конфига регистрации;
- взимание комиссии за регистрацию и увеличение лимита;
- проверку Ed25519-подписей `root_key` и `blockchain_public_key`;
- проверку связности новой версии записи с предыдущей через `prev_record_hash`.
Программа не отвечает за:
- хранение приватных ключей;
- проверку существования Arweave tx;
- валидацию того, что логины из `sync_servers` или `access_servers` реально существуют как серверы;
- выполнение SHiNE-блокчейна пользователя;
- хранение серверных auth-сессий.
## 2. Program ID и внешние зависимости
Текущий program id devnet/localnet:
- `FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm`
Внешние зависимости по логике:
- `shine_payments`
- используется только как источник PDA inflow-вольта;
- `shine_login_guard`
- используется для классификации логина через CPI;
- системная программа Solana;
- sysvar `instructions`.
## 3. PDA и seed-правила
### 3.1. Пользовательская PDA
Пользовательская запись строится так:
- seed prefix: `user_login=`
- второй seed: логин в нижнем регистре
- program id: `shine_users`
Формула:
```text
user_pda = PDA(["user_login=", lower(login)], shine_users_program_id)
```
### 3.2. Economy config PDA
PDA экономических настроек:
- seed: `shine_users_economy_config_v2`
Формула:
```text
users_economy_config_pda = PDA(["shine_users_economy_config_v2"], shine_users_program_id)
```
## 4. Состояния программы
### 4.1. `UsersEconomyConfigState`
Хранится в `users_economy_config_pda`.
Поля:
- `version: u8`
- `registration_fee_lamports: u64`
- `lamports_per_limit_step: u64`
- `start_bonus_limit: u64`
Смысл:
- `registration_fee_lamports` — базовая плата за регистрацию;
- `lamports_per_limit_step` — стоимость одного шага лимита;
- `start_bonus_limit` — стартовый бесплатный лимит записи, который получает новый пользователь.
### 4.2. `user_pda`
Формат пользовательской записи описан отдельно:
- [shine-user-pda-format-v.1.0.md](/home/ai/work/SHiNE/SHiNE-server-sha256/shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md)
Этот документ описывает именно логику программы, а не байтовую структуру блока.
## 5. Константы и базовые правила
Базовые значения из текущей логики:
- seed `user_pda`: `user_login=`
- seed economy config: `shine_users_economy_config_v2`
- стартовый размер `user_pda`: `768` байт
- `LIMIT_STEP = 10_000`
- `START_REGISTRATION_FEE_LAMPORTS = 10_000_000`
- `START_LAMPORTS_PER_LIMIT_STEP = 100_000`
- `START_BONUS_LIMIT = 100_000`
Правила:
- `additional_limit` всегда кратен `LIMIT_STEP`;
- `paid_limit_bytes` не может уменьшаться;
- `used_bytes` не может уменьшаться;
- `last_block_number` не может уменьшаться;
- `root_key` после создания не меняется;
- логин после создания не меняется;
- `created_at_ms` после создания не меняется.
## 6. Ключи и подписи
В записи участвуют три ключевых роли:
- `root_key`
- корневая подпись самой записи;
- `device_key`
- текущий плательщик и signer транзакции create/update;
- `blockchain_public_key`
- ключ подтверждения вершины пользовательского SHiNE-блокчейна.
### 6.1. Что программа видит on-chain
Программа работает только с:
- публичными ключами `32` байта;
- подписями `64` байта;
- сообщениями для Ed25519-проверки.
Программа не знает и не должна знать:
- PKCS#8 контейнеры;
- PEM;
- способ хранения приватного ключа на клиенте;
- откуда клиент извлёк `seed32` для `device_key`.
### 6.2. Практика клиентской генерации ключей
Off-chain клиентская логика может хранить приватные ключи в PKCS#8 и извлекать `seed32` для `device`-signer. Это допустимая клиентская реализация, но не часть on-chain формата.
On-chain инвариант только один:
- публичные ключи и подписи должны соответствовать друг другу.
## 7. Логин и login guard
Перед созданием пользователя логин обязан пройти две проверки:
1. базовая syntactic validation внутри `shine_users`;
2. CPI-вызов `shine_login_guard::classify_login`.
### 7.1. Базовая проверка логина
Логин должен:
- быть не пустым;
- быть длиной не больше `20` символов;
- содержать только `A-Z`, `a-z`, `0-9`, `_`.
### 7.2. Классификация через `shine_login_guard`
`shine_users` вызывает `shine_login_guard` и читает `return_data`.
Классы:
- `0` — логин разрешён;
- `1` — premium login, регистрация запрещена автоматически;
- `2` — trademark login, требует отдельного review и не регистрируется автоматически.
Если `shine_login_guard` вернул что-то иное или return_data некорректны, это ошибка.
## 8. Инструкция `init_users_economy_config`
### Назначение
Создать `users_economy_config_pda` со стартовыми параметрами экономики.
### Аккаунты
- signer/payer
- `users_economy_config_pda`
- system program
### Правила
- PDA должна ещё не существовать;
- адрес PDA обязан совпадать с seed `shine_users_economy_config_v2`;
- в PDA записывается стартовый `UsersEconomyConfigState`.
### Бинарный ABI
```text
- tag: u8 = 1
```
## 9. Инструкция `update_users_economy_config`
### Назначение
Изменить экономические параметры регистрации.
### Авторизация
Только `DAO_AUTHORITY`.
### Аккаунты
- signer
- `users_economy_config_pda`
### Правила
- signer должен совпадать с `DAO_AUTHORITY`;
- PDA должна существовать и принадлежать программе;
- `lamports_per_limit_step > 0`.
### Бинарный ABI
```text
- tag: u8 = 2
- registration_fee_lamports: u64 LE
- lamports_per_limit_step: u64 LE
- start_bonus_limit: u64 LE
```
## 10. Инструкция `create_user_pda`
### Назначение
Создать новую пользовательскую запись по логину.
### Кто платит
Плательщик транзакции и signer инструкции:
- `device_key`
Это принципиальное правило:
- `root_key` только подписывает запись;
- `device_key` оплачивает rent/fees/registration flow.
### Аккаунты
- signer = `device_key`
- `user_pda`
- system program
- inflow vault PDA из `shine_payments`
- sysvar `instructions`
- `users_economy_config_pda`
- `shine_login_guard_program`
### Входные данные
- логин
- `root_key`
- `created_at_ms`
- `additional_limit`
- mutable fields записи
- `root` signature по unsigned части
### Бинарный ABI
```text
- tag: u8 = 3
- login: string_u8
- root_key: [u8; 32]
- created_at_ms: u64 LE
- additional_limit: u64 LE
- fields: UserMutableFieldsV1
- root_signature: [u8; 64]
```
### Обязательные проверки
1. Логин валиден по синтаксису.
2. Логин разрешён `shine_login_guard`.
3. `additional_limit % LIMIT_STEP == 0`.
4. inflow vault совпадает с PDA программы `shine_payments`.
5. `user_pda` вычислена правильно и ещё не существует.
6. Поля блокчейна валидны.
7. Поля server/session/trusted валидны по формату.
8. `last_block_signature` соответствует `LastBlockState`.
9. `root signature` соответствует unsigned части записи.
10. Размер сериализованной записи не превышает допустимый стартовый размер PDA или иные ограничения реализации.
### Экономика
При создании:
- пользователь получает `start_bonus_limit`;
- дополнительно может купить `additional_limit`;
- итоговый оплаченный лимит:
```text
paid_limit_bytes = start_bonus_limit + additional_limit
```
Комиссия:
```text
total_fee = registration_fee_lamports + limit_fee(additional_limit)
```
Где:
```text
limit_fee(additional_limit) = (additional_limit / LIMIT_STEP) * lamports_per_limit_step
```
### Результат
- создаётся PDA;
- в неё записывается полная запись `user_pda`;
- средства переводятся в inflow vault `shine_payments`.
## 11. Инструкция `update_user_pda`
### Назначение
Создать новую версию той же пользовательской записи.
### Авторизация
Те же роли:
- signer/fee payer = `device_key`
- подпись записи = `root_key`
- подпись вершины блокчейна = `blockchain_public_key`
### Аккаунты
- signer = `device_key`
- `user_pda`
- system program
- inflow vault PDA из `shine_payments`
- sysvar `instructions`
- `users_economy_config_pda`
### Обязательные проверки
1. PDA существует и принадлежит `shine_users`.
2. Новый логин совпадает со старым.
3. `created_at_ms` совпадает со старым.
4. `root_key` совпадает со старым.
5. `version = old.record_number + 1`.
6. `prev_hash = hash(unsigned_old_record)`.
7. `additional_limit % LIMIT_STEP == 0`.
8. `blockchain_name` и `blockchain_public_key` не меняются.
9. `paid_limit_bytes` не уменьшается.
10. `used_bytes` не уменьшается.
11. `last_block_number` не уменьшается.
12. Если состояние блокчейна изменилось, `last_block_signature` заново проверяется через Ed25519.
13. Новая unsigned часть записи подписана `root_key`.
14. При необходимости PDA может быть расширена через realloc.
### Экономика
При update оплачивается только докупаемый лимит:
```text
topup_fee = limit_fee(additional_limit)
```
Если `additional_limit = 0`, доплата не требуется.
### Бинарный ABI
```text
- tag: u8 = 4
- login: string_u8
- root_key: [u8; 32]
- created_at_ms: u64 LE
- updated_at_ms: u64 LE
- version: u32 LE
- prev_hash: [u8; 32]
- additional_limit: u64 LE
- fields: UserMutableFieldsV1
- root_signature: [u8; 64]
```
## 12. Ed25519-проверки и порядок инструкций
В транзакции должны стоять две встроенные Ed25519-инструкции прямо перед вызовом `shine_users`:
1. подпись `root_key` по unsigned записи;
2. подпись `blockchain_public_key` по `LastBlockState`.
Текущая логика `shine_users` читает их через sysvar `instructions` относительно текущего индекса:
- `-2``root_key`
- `-1``blockchain_public_key`
Это правило порядка является частью контракта между off-chain клиентом и программой.
## 13. LastBlockState
Сообщение для подписи `blockchain_public_key`:
```text
- constant: "SHiNE_LAST_BLOCK"
- login
- blockchain_name
- last_block_number
- last_block_hash[32]
- used_bytes
```
Алгоритм:
```text
message_hash = SHA-256(LastBlockState bytes)
signature = Ed25519(blockchain_private_key, message_hash)
```
## 14. Валидируемые mutable-поля записи
Программа допускает обновление:
- `device_key`
- `used_bytes`
- `last_block_number`
- `last_block_hash`
- `last_block_signature`
- `arweave_tx_id`
- `is_server`
- `server profile`
- `access_servers`
- `sessions_mode`
- `sessions`
- `trusted_count`
- `additional_limit`
Программа не допускает update:
- `login`
- `created_at_ms`
- `root_key`
- `blockchain_name`
- `blockchain_public_key`
- `blockchain_type`
## 15. Правила серверных и сессионных полей
### UserMutableFieldsV1
```text
- device_key: [u8; 32]
- blockchain_public_key: [u8; 32]
- blockchain_name: string_u8
- used_bytes: u64 LE
- last_block_number: u32 LE
- last_block_hash: [u8; 32]
- last_block_signature: [u8; 64]
- arweave_tx_id: string_u8
- is_server: u8
- if is_server = 1:
- address_format_type: u8
- address_format_version: u8
- server_address: string_u8
- sync_servers_count: u8
- sync_servers[sync_servers_count]: string_u8[]
- access_servers_count: u8
- access_servers[access_servers_count]: string_u8[]
- sessions_mode: u8
- sessions_count: u8
- sessions[sessions_count]:
- session_type: u8
- session_version: u8
- session_name: string_u8
- session_pub_key: [u8; 32]
- trusted_count: u8
```
### Server profile
Если `is_server = false`:
- `server_address` должен быть пустой;
- `sync_servers` должен быть пустой.
Если `is_server = true`:
- `server_address` обязателен;
- `sync_servers.len() <= 32`.
### Sessions block
Формат сессий описан в PDA-формате, но логика такая:
- максимум `64` записей;
- `sessions_mode` допускает только `1` и `10`;
- `session_type` допускает `1` и `100`;
- `session_version` сейчас только `1`;
- `session_name` должен содержать только `[A-Za-z0-9_]`;
- `session_name` и `session_pub_key` уникальны внутри списка.
На текущем этапе обычная регистрация пользователя должна продолжать работать с:
- `sessions_mode = 1`
- `sessions = []`
## 16. Realloc поведения PDA
Запись может расти. Если новая сериализованная запись длиннее текущего размера PDA:
- PDA разрешено увеличить через realloc;
- нельзя делать чрезмерный рост одним шагом выше внутреннего лимита реализации;
- перед realloc нужно обеспечить rent для нового размера.
## 17. Ошибки и классы отказа
Программа должна различать как минимум такие классы ошибок:
- неверный логин;
- premium/trademark login;
- неверный PDA адрес;
- PDA уже существует / PDA пустая / PDA не принадлежит программе;
- неверный формат записи;
- неверная подпись root;
- неверная подпись last block state;
- попытка изменить immutable поля;
- неверная версия;
- неверный `prev_hash`;
- попытка уменьшить лимит/used_bytes/block number;
- overflow;
- неверный inflow vault;
- неверный DAO authority для economy config.
## 18. Что должно сохраниться при переписи без Anchor
В текущей чисто-rust реализации уже сохранены:
- те же PDA seed-правила;
- тот же формат `user_pda`;
- ту же экономику регистрации и topup;
- тот же порядок Ed25519-инструкций;
- те же immutable/mutable правила;
- ту же валидацию логина и CPI в `shine_login_guard`;
- ту же зависимость от inflow vault программы `shine_payments`.
Сознательно не сохранялись:
- структура Anchor `Context`;
- Anchor discriminator'ы и Anchor-ABI инструкций;
- старые seed'ы, которые конфликтовали с уже существующим Anchor-состоянием в devnet;
- внутренние helper-функции старой реализации.