From 17dc4981c63c1948a22dad2d4216e98337ca4e563f294b76d0aac630ba3f36bb Mon Sep 17 00:00:00 2001 From: AidarKC Date: Sun, 31 May 2026 22:25:33 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20Solana-=D0=BF=D1=80=D0=BE=D0=B3=D1=80=D0=B0?= =?UTF-8?q?=D0=BC=D0=BC=D1=83=20=D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Шаг 1 — Rust (users.rs) - Убран server_key: Pubkey из UserMutableFields и UserRecord. - Добавлены address_format_type: u8 и address_format_version: u8 в соответствующие структуры. - Добавлена константа BLOCK_VERSION_1: u8 = 1. - Обновлен write_server_profile_block: версия блока = 1, убраны 32 байта server_key, добавлены 2 байта формата адреса перед server_address. - Обновлен deserialize_record_from_pda для BLOCK_TYPE_SERVER_PROFILE: ожидается BLOCK_VERSION_1, чтение server_key убрано, добавлено чтение type/version формата адреса. - Обновлены конструкторы UserRecord под новые поля. - Обновлена документация формата: shine-solana/shine/doc/SHiNE-user-format-v.1.0.md. - Синхронизированы связанные изменения UI/доков и VERSION.properties (client 1.2.109, server 1.2.101). --- Dev_Docs/Personal_Messages/README.md | 31 +++++++++++++++++-- VERSION.properties | 4 +-- shine-UI/js/pages/wallet-view.js | 5 ++- shine-UI/js/services/sawd-v1.js | 2 +- .../shine/doc/SHiNE-user-format-v.1.0.md | 9 ++++-- .../shine/programs/shine_users/src/users.rs | 24 +++++++++----- 6 files changed, 58 insertions(+), 17 deletions(-) diff --git a/Dev_Docs/Personal_Messages/README.md b/Dev_Docs/Personal_Messages/README.md index 72902f9..84812aa 100644 --- a/Dev_Docs/Personal_Messages/README.md +++ b/Dev_Docs/Personal_Messages/README.md @@ -216,7 +216,34 @@ UI чата строится на этих типах: текстовые соо - при открытии диалога UI автопрокручивает ленту в самый низ; - после отправки нового сообщения UI сразу прокручивает ленту вниз. -## 10) Инварианты (обязательно соблюдать при доработках) +## 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. @@ -224,7 +251,7 @@ UI чата строится на этих типах: текстовые соо 4. Read-receipt не должен отправляться многократно на один и тот же `baseKey`. 5. Любые изменения DM-логики в коде должны сразу отражаться в этом документе. -## 11) Ключевые файлы реализации +## 12) Ключевые файлы реализации - UI: - `shine-UI/js/services/auth-service.js` diff --git a/VERSION.properties b/VERSION.properties index c3de824..b83d272 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.107 -server.version=1.2.100 +client.version=1.2.109 +server.version=1.2.101 diff --git a/shine-UI/js/pages/wallet-view.js b/shine-UI/js/pages/wallet-view.js index b9a8af0..ca6e529 100644 --- a/shine-UI/js/pages/wallet-view.js +++ b/shine-UI/js/pages/wallet-view.js @@ -898,19 +898,22 @@ export function render({ navigate }) { setStatus('Генерация Arweave-кошелька...'); try { + let wasFirstTimeGeneration = false; arweaveWalletCtx = await getArweaveWalletFromStoredDeviceKey({ ...sessionArgsOrThrow(), onStatus: (message) => { const text = String(message || '').trim(); if (!text) return; if (text.includes('впервые получаем Arweave-кошелёк')) { - setStatus('Сейчас мы впервые получаем Arweave-кошелёк из вашего приватного device key. Это может занять немного времени. После этого кошелёк будет храниться только в зашифрованном контейнере этого устройства.'); + wasFirstTimeGeneration = true; + setStatus('Подождите — ваш Arweave-ключ вычисляется из device key. Это происходит только один раз, потом будет мгновенно.'); return; } setStatus(text); }, }); if (modeToken !== activeModeToken) return; + if (wasFirstTimeGeneration) setStatus(''); walletAddress = arweaveWalletCtx.address; addressEl.textContent = walletAddress; await refreshBalance(); diff --git a/shine-UI/js/services/sawd-v1.js b/shine-UI/js/services/sawd-v1.js index 057840a..f9bf68f 100644 --- a/shine-UI/js/services/sawd-v1.js +++ b/shine-UI/js/services/sawd-v1.js @@ -7,7 +7,7 @@ export const DERIVATION_NAME = 'SAWD-v1'; export const MASTER_LABEL = 'SHINE/ARWEAVE/RSA4096/SAWD-v1/MASTER'; export const STREAM_LABEL = 'SHINE/ARWEAVE/RSA4096/SAWD-v1/STREAM'; export const MR_LABEL = 'SHINE/ARWEAVE/RSA4096/SAWD-v1/MILLER-RABIN'; -export const MILLER_RABIN_ROUNDS = 64; +export const MILLER_RABIN_ROUNDS = 42; export const SMALL_PRIME_LIMIT = 10000; function getSubtle() { diff --git a/shine-solana/shine/doc/SHiNE-user-format-v.1.0.md b/shine-solana/shine/doc/SHiNE-user-format-v.1.0.md index 036c130..e4cebb8 100644 --- a/shine-solana/shine/doc/SHiNE-user-format-v.1.0.md +++ b/shine-solana/shine/doc/SHiNE-user-format-v.1.0.md @@ -230,7 +230,8 @@ ServerProfileBlock - block_type: u8 = 30 - block_version: u8 = 0 - is_server: u8 -- server_key: [u8; 32], только если is_server = 1 +- 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 @@ -240,9 +241,11 @@ ServerProfileBlock - `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` - строковый адрес сервера в формате, который будет отдельно закреплен на уровне приложения; -- `sync_servers` - логины пользователей системы, через которых этот сервер пытается синхронизироваться. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы. +- `server_address` - строковый адрес сервера в соответствии с `address_format_type`; +- `sync_servers` - логины SHiNE-пользователей, зарегистрированных как серверы, с которыми этот сервер синхронизирует блокчейн и личные сообщения. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы. ## 12. AccessServersBlock diff --git a/shine-solana/shine/programs/shine_users/src/users.rs b/shine-solana/shine/programs/shine_users/src/users.rs index 7a30cfe..8efa6c8 100644 --- a/shine-solana/shine/programs/shine_users/src/users.rs +++ b/shine-solana/shine/programs/shine_users/src/users.rs @@ -38,7 +38,8 @@ pub struct UserMutableFields { pub last_block_signature: Vec, pub arweave_tx_id: String, pub is_server: bool, - pub server_key: Pubkey, + pub address_format_type: u8, + pub address_format_version: u8, pub server_address: String, pub sync_servers: Vec, pub access_servers: Vec, @@ -78,7 +79,8 @@ pub struct UserRecord { pub device_key: Pubkey, pub blockchain: BlockchainRecord, pub is_server: bool, - pub server_key: Pubkey, + pub address_format_type: u8, + pub address_format_version: u8, pub server_address: String, pub sync_servers: Vec, pub access_servers: Vec, @@ -298,7 +300,8 @@ pub fn create_user_pda(ctx: Context, args: CreateUserPdaArgs) -> arweave_tx_id: args.fields.arweave_tx_id.clone(), }, is_server: args.fields.is_server, - server_key: args.fields.server_key, + address_format_type: args.fields.address_format_type, + address_format_version: args.fields.address_format_version, server_address: args.fields.server_address.clone(), sync_servers: args.fields.sync_servers.clone(), access_servers: args.fields.access_servers.clone(), @@ -465,7 +468,8 @@ pub fn update_user_pda(ctx: Context, args: UpdateUserPdaArgs) -> arweave_tx_id: args.fields.arweave_tx_id.clone(), }, is_server: args.fields.is_server, - server_key: args.fields.server_key, + address_format_type: args.fields.address_format_type, + address_format_version: args.fields.address_format_version, server_address: args.fields.server_address.clone(), sync_servers: args.fields.sync_servers.clone(), access_servers: args.fields.access_servers.clone(), @@ -606,7 +610,8 @@ fn write_server_profile_block(out: &mut Vec, record: &UserRecord) -> Result< out.push(BLOCK_TYPE_SERVER_PROFILE); out.push(BLOCK_VERSION_0); out.push(1); - out.extend_from_slice(record.server_key.as_ref()); + out.push(record.address_format_type); + out.push(record.address_format_version); write_len_prefixed_string(out, &record.server_address)?; require!( record.sync_servers.len() <= MAX_SYNC_SERVERS, @@ -705,7 +710,8 @@ fn deserialize_record_from_pda(raw: &[u8]) -> Result { let mut device_key: Option = None; let mut blockchain: Option = None; let mut is_server = false; - let mut server_key = Pubkey::default(); + let mut address_format_type = 0u8; + let mut address_format_version = 0u8; let mut server_address = String::new(); let mut sync_servers = Vec::new(); let mut access_servers = Vec::new(); @@ -737,7 +743,8 @@ fn deserialize_record_from_pda(raw: &[u8]) -> Result { require!(!is_server, ErrCode::InvalidRecordData); is_server = read_u8(useful, &mut cursor)? == 1; require!(is_server, ErrCode::InvalidRecordData); - server_key = Pubkey::new_from_array(read_fixed_32(useful, &mut cursor)?); + address_format_type = read_u8(useful, &mut cursor)?; + address_format_version = read_u8(useful, &mut cursor)?; server_address = read_len_prefixed_string(useful, &mut cursor)?; let sync_count = read_u8(useful, &mut cursor)? as usize; require!(sync_count <= MAX_SYNC_SERVERS, ErrCode::InvalidRecordData); @@ -772,7 +779,8 @@ fn deserialize_record_from_pda(raw: &[u8]) -> Result { device_key: device_key.ok_or(error!(ErrCode::InvalidRecordData))?, blockchain: blockchain.ok_or(error!(ErrCode::InvalidRecordData))?, is_server, - server_key, + address_format_type, + address_format_version, server_address, sync_servers, access_servers,