Поправить Solana-программу регистрации пользователей
Шаг 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).
This commit is contained in:
parent
0179b25d12
commit
17dc4981c6
@ -216,7 +216,34 @@ UI чата строится на этих типах: текстовые соо
|
|||||||
- при открытии диалога UI автопрокручивает ленту в самый низ;
|
- при открытии диалога 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) должна оставаться атомарной.
|
1. Пара блоков (1/2 или 3/4) должна оставаться атомарной.
|
||||||
2. `messageKey`/`baseKey` формат должен быть совместим с текущей логикой дедупликации и receipt.
|
2. `messageKey`/`baseKey` формат должен быть совместим с текущей логикой дедупликации и receipt.
|
||||||
@ -224,7 +251,7 @@ UI чата строится на этих типах: текстовые соо
|
|||||||
4. Read-receipt не должен отправляться многократно на один и тот же `baseKey`.
|
4. Read-receipt не должен отправляться многократно на один и тот же `baseKey`.
|
||||||
5. Любые изменения DM-логики в коде должны сразу отражаться в этом документе.
|
5. Любые изменения DM-логики в коде должны сразу отражаться в этом документе.
|
||||||
|
|
||||||
## 11) Ключевые файлы реализации
|
## 12) Ключевые файлы реализации
|
||||||
|
|
||||||
- UI:
|
- UI:
|
||||||
- `shine-UI/js/services/auth-service.js`
|
- `shine-UI/js/services/auth-service.js`
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.107
|
client.version=1.2.109
|
||||||
server.version=1.2.100
|
server.version=1.2.101
|
||||||
|
|||||||
@ -898,19 +898,22 @@ export function render({ navigate }) {
|
|||||||
setStatus('Генерация Arweave-кошелька...');
|
setStatus('Генерация Arweave-кошелька...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let wasFirstTimeGeneration = false;
|
||||||
arweaveWalletCtx = await getArweaveWalletFromStoredDeviceKey({
|
arweaveWalletCtx = await getArweaveWalletFromStoredDeviceKey({
|
||||||
...sessionArgsOrThrow(),
|
...sessionArgsOrThrow(),
|
||||||
onStatus: (message) => {
|
onStatus: (message) => {
|
||||||
const text = String(message || '').trim();
|
const text = String(message || '').trim();
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
if (text.includes('впервые получаем Arweave-кошелёк')) {
|
if (text.includes('впервые получаем Arweave-кошелёк')) {
|
||||||
setStatus('Сейчас мы впервые получаем Arweave-кошелёк из вашего приватного device key. Это может занять немного времени. После этого кошелёк будет храниться только в зашифрованном контейнере этого устройства.');
|
wasFirstTimeGeneration = true;
|
||||||
|
setStatus('Подождите — ваш Arweave-ключ вычисляется из device key. Это происходит только один раз, потом будет мгновенно.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setStatus(text);
|
setStatus(text);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (modeToken !== activeModeToken) return;
|
if (modeToken !== activeModeToken) return;
|
||||||
|
if (wasFirstTimeGeneration) setStatus('');
|
||||||
walletAddress = arweaveWalletCtx.address;
|
walletAddress = arweaveWalletCtx.address;
|
||||||
addressEl.textContent = walletAddress;
|
addressEl.textContent = walletAddress;
|
||||||
await refreshBalance();
|
await refreshBalance();
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export const DERIVATION_NAME = 'SAWD-v1';
|
|||||||
export const MASTER_LABEL = 'SHINE/ARWEAVE/RSA4096/SAWD-v1/MASTER';
|
export const MASTER_LABEL = 'SHINE/ARWEAVE/RSA4096/SAWD-v1/MASTER';
|
||||||
export const STREAM_LABEL = 'SHINE/ARWEAVE/RSA4096/SAWD-v1/STREAM';
|
export const STREAM_LABEL = 'SHINE/ARWEAVE/RSA4096/SAWD-v1/STREAM';
|
||||||
export const MR_LABEL = 'SHINE/ARWEAVE/RSA4096/SAWD-v1/MILLER-RABIN';
|
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;
|
export const SMALL_PRIME_LIMIT = 10000;
|
||||||
|
|
||||||
function getSubtle() {
|
function getSubtle() {
|
||||||
|
|||||||
@ -230,7 +230,8 @@ ServerProfileBlock
|
|||||||
- block_type: u8 = 30
|
- block_type: u8 = 30
|
||||||
- block_version: u8 = 0
|
- block_version: u8 = 0
|
||||||
- is_server: u8
|
- 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
|
- server_address: string, только если is_server = 1
|
||||||
- sync_servers_count: u8, только если is_server = 1
|
- sync_servers_count: u8, только если is_server = 1
|
||||||
- sync_servers: string[sync_servers_count], только если is_server = 1
|
- sync_servers: string[sync_servers_count], только если is_server = 1
|
||||||
@ -240,9 +241,11 @@ ServerProfileBlock
|
|||||||
|
|
||||||
- `is_server = 0` означает, что серверных данных нет;
|
- `is_server = 0` означает, что серверных данных нет;
|
||||||
- `is_server = 1` означает, что пользователь публикует серверный профиль;
|
- `is_server = 1` означает, что пользователь публикует серверный профиль;
|
||||||
|
- `address_format_type` — тип формата адреса сервера: `1` = URL-строка (например `https://shineup.me/ws`);
|
||||||
|
- `address_format_version` — версия формата адреса, сейчас `0`;
|
||||||
- `sync_servers_count` максимум `32`;
|
- `sync_servers_count` максимум `32`;
|
||||||
- `server_address` - строковый адрес сервера в формате, который будет отдельно закреплен на уровне приложения;
|
- `server_address` - строковый адрес сервера в соответствии с `address_format_type`;
|
||||||
- `sync_servers` - логины пользователей системы, через которых этот сервер пытается синхронизироваться. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы.
|
- `sync_servers` - логины SHiNE-пользователей, зарегистрированных как серверы, с которыми этот сервер синхронизирует блокчейн и личные сообщения. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы.
|
||||||
|
|
||||||
## 12. AccessServersBlock
|
## 12. AccessServersBlock
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,8 @@ pub struct UserMutableFields {
|
|||||||
pub last_block_signature: Vec<u8>,
|
pub last_block_signature: Vec<u8>,
|
||||||
pub arweave_tx_id: String,
|
pub arweave_tx_id: String,
|
||||||
pub is_server: bool,
|
pub is_server: bool,
|
||||||
pub server_key: Pubkey,
|
pub address_format_type: u8,
|
||||||
|
pub address_format_version: u8,
|
||||||
pub server_address: String,
|
pub server_address: String,
|
||||||
pub sync_servers: Vec<String>,
|
pub sync_servers: Vec<String>,
|
||||||
pub access_servers: Vec<String>,
|
pub access_servers: Vec<String>,
|
||||||
@ -78,7 +79,8 @@ pub struct UserRecord {
|
|||||||
pub device_key: Pubkey,
|
pub device_key: Pubkey,
|
||||||
pub blockchain: BlockchainRecord,
|
pub blockchain: BlockchainRecord,
|
||||||
pub is_server: bool,
|
pub is_server: bool,
|
||||||
pub server_key: Pubkey,
|
pub address_format_type: u8,
|
||||||
|
pub address_format_version: u8,
|
||||||
pub server_address: String,
|
pub server_address: String,
|
||||||
pub sync_servers: Vec<String>,
|
pub sync_servers: Vec<String>,
|
||||||
pub access_servers: Vec<String>,
|
pub access_servers: Vec<String>,
|
||||||
@ -298,7 +300,8 @@ pub fn create_user_pda(ctx: Context<CreateUserPda>, args: CreateUserPdaArgs) ->
|
|||||||
arweave_tx_id: args.fields.arweave_tx_id.clone(),
|
arweave_tx_id: args.fields.arweave_tx_id.clone(),
|
||||||
},
|
},
|
||||||
is_server: args.fields.is_server,
|
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(),
|
server_address: args.fields.server_address.clone(),
|
||||||
sync_servers: args.fields.sync_servers.clone(),
|
sync_servers: args.fields.sync_servers.clone(),
|
||||||
access_servers: args.fields.access_servers.clone(),
|
access_servers: args.fields.access_servers.clone(),
|
||||||
@ -465,7 +468,8 @@ pub fn update_user_pda(ctx: Context<UpdateUserPda>, args: UpdateUserPdaArgs) ->
|
|||||||
arweave_tx_id: args.fields.arweave_tx_id.clone(),
|
arweave_tx_id: args.fields.arweave_tx_id.clone(),
|
||||||
},
|
},
|
||||||
is_server: args.fields.is_server,
|
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(),
|
server_address: args.fields.server_address.clone(),
|
||||||
sync_servers: args.fields.sync_servers.clone(),
|
sync_servers: args.fields.sync_servers.clone(),
|
||||||
access_servers: args.fields.access_servers.clone(),
|
access_servers: args.fields.access_servers.clone(),
|
||||||
@ -606,7 +610,8 @@ fn write_server_profile_block(out: &mut Vec<u8>, record: &UserRecord) -> Result<
|
|||||||
out.push(BLOCK_TYPE_SERVER_PROFILE);
|
out.push(BLOCK_TYPE_SERVER_PROFILE);
|
||||||
out.push(BLOCK_VERSION_0);
|
out.push(BLOCK_VERSION_0);
|
||||||
out.push(1);
|
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)?;
|
write_len_prefixed_string(out, &record.server_address)?;
|
||||||
require!(
|
require!(
|
||||||
record.sync_servers.len() <= MAX_SYNC_SERVERS,
|
record.sync_servers.len() <= MAX_SYNC_SERVERS,
|
||||||
@ -705,7 +710,8 @@ fn deserialize_record_from_pda(raw: &[u8]) -> Result<UserRecord> {
|
|||||||
let mut device_key: Option<Pubkey> = None;
|
let mut device_key: Option<Pubkey> = None;
|
||||||
let mut blockchain: Option<BlockchainRecord> = None;
|
let mut blockchain: Option<BlockchainRecord> = None;
|
||||||
let mut is_server = false;
|
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 server_address = String::new();
|
||||||
let mut sync_servers = Vec::new();
|
let mut sync_servers = Vec::new();
|
||||||
let mut access_servers = Vec::new();
|
let mut access_servers = Vec::new();
|
||||||
@ -737,7 +743,8 @@ fn deserialize_record_from_pda(raw: &[u8]) -> Result<UserRecord> {
|
|||||||
require!(!is_server, ErrCode::InvalidRecordData);
|
require!(!is_server, ErrCode::InvalidRecordData);
|
||||||
is_server = read_u8(useful, &mut cursor)? == 1;
|
is_server = read_u8(useful, &mut cursor)? == 1;
|
||||||
require!(is_server, ErrCode::InvalidRecordData);
|
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)?;
|
server_address = read_len_prefixed_string(useful, &mut cursor)?;
|
||||||
let sync_count = read_u8(useful, &mut cursor)? as usize;
|
let sync_count = read_u8(useful, &mut cursor)? as usize;
|
||||||
require!(sync_count <= MAX_SYNC_SERVERS, ErrCode::InvalidRecordData);
|
require!(sync_count <= MAX_SYNC_SERVERS, ErrCode::InvalidRecordData);
|
||||||
@ -772,7 +779,8 @@ fn deserialize_record_from_pda(raw: &[u8]) -> Result<UserRecord> {
|
|||||||
device_key: device_key.ok_or(error!(ErrCode::InvalidRecordData))?,
|
device_key: device_key.ok_or(error!(ErrCode::InvalidRecordData))?,
|
||||||
blockchain: blockchain.ok_or(error!(ErrCode::InvalidRecordData))?,
|
blockchain: blockchain.ok_or(error!(ErrCode::InvalidRecordData))?,
|
||||||
is_server,
|
is_server,
|
||||||
server_key,
|
address_format_type,
|
||||||
|
address_format_version,
|
||||||
server_address,
|
server_address,
|
||||||
sync_servers,
|
sync_servers,
|
||||||
access_servers,
|
access_servers,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user