Сервер: удалить manual import и AddUser слой

This commit is contained in:
AidarKC
2026-07-27 19:06:08 +04:00
parent f74fecddd8
commit 23748504e6
26 changed files with 26 additions and 968 deletions
@@ -10,9 +10,10 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* SolanaUsersDAO — локальная таблица пользователей из Solana. * SolanaUsersDAO — совместимый runtime-доступ к текущему срезу пользователей.
* *
* Таблица: solana_users * Источник:
* - solana_user_pda_current
* *
* Колонки: * Колонки:
* - login TEXT PRIMARY KEY * - login TEXT PRIMARY KEY
@@ -41,40 +42,6 @@ public final class SolanaUsersDAO {
return instance; return instance;
} }
// -------------------- INSERT --------------------
/** Вставка с внешним соединением. Соединение НЕ закрывает. */
public void insert(Connection c, SolanaUserEntry user) throws SQLException {
String sql = """
INSERT INTO solana_users_manual (
login, blockchain_name, solana_key, blockchain_key, client_key, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT (login) DO UPDATE SET
blockchain_name = EXCLUDED.blockchain_name,
solana_key = EXCLUDED.solana_key,
blockchain_key = EXCLUDED.blockchain_key,
client_key = EXCLUDED.client_key,
updated_at_ms = EXCLUDED.updated_at_ms
""";
try (PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, user.getLogin());
ps.setString(2, user.getBlockchainName());
ps.setString(3, user.getSolanaKey());
ps.setString(4, user.getBlockchainKey());
ps.setString(5, user.getClientKey());
ps.setLong(6, System.currentTimeMillis());
ps.executeUpdate();
}
}
/** Вставка без внешнего соединения. Сам открывает/закрывает. */
public void insert(SolanaUserEntry user) throws SQLException {
try (Connection c = db.getConnection()) {
insert(c, user);
}
}
// -------------------- EXISTS -------------------- // -------------------- EXISTS --------------------
/** Проверка существования по login (case-insensitive) с внешним соединением. Соединение НЕ закрывает. */ /** Проверка существования по login (case-insensitive) с внешним соединением. Соединение НЕ закрывает. */
@@ -1,128 +0,0 @@
package shine.db.dao;
import shine.db.DbController;
import shine.db.entities.SolanaUserEntry;
import java.sql.*;
/**
* UserCreateDAO — атомарное добавление пользователя:
* - solana_users (login, blockchain_name, solana_key, blockchain_key, client_key)
* - blockchain_state (blockchain_name, login, blockchain_key, size_limit, ... last_block_number=-1 ...)
*
* ВАЖНО:
* - только INSERT (без перезаписи существующих записей)
* - если login или blockchainName заняты — возвращаем false (пользователь уже есть/занято)
*/
public final class UserCreateDAO {
private static volatile UserCreateDAO instance;
private final DbController db = DbController.getInstance();
private final SolanaUsersDAO usersDao = SolanaUsersDAO.getInstance();
private UserCreateDAO() {}
public static UserCreateDAO getInstance() {
if (instance == null) {
synchronized (UserCreateDAO.class) {
if (instance == null) instance = new UserCreateDAO();
}
}
return instance;
}
/**
* @return true если добавили; false если занято (login уже есть или blockchainName уже существует).
*/
public boolean insertUserWithBlockchain(
String login,
String blockchainName,
String solanaKey,
String blockchainKey,
String clientKey,
long sizeLimit,
long nowMs
) throws SQLException {
try (Connection c = db.getConnection()) {
boolean oldAuto = c.getAutoCommit();
c.setAutoCommit(false);
try {
// 1) solana_users
SolanaUserEntry u = new SolanaUserEntry();
u.setLogin(login);
u.setBlockchainName(blockchainName);
u.setSolanaKey(solanaKey);
u.setBlockchainKey(blockchainKey);
u.setClientKey(clientKey);
usersDao.insert(c, u); // если login занят (NOCASE) или blockchainName (unique) -> constraint
// 2) blockchain_state — строго INSERT, без UPSERT (иначе можно перезаписать существующую цепочку)
insertBlockchainStateStrict(
c,
blockchainName,
login,
blockchainKey,
sizeLimit,
nowMs
);
c.commit();
return true;
} catch (SQLException e) {
c.rollback();
String msg = e.getMessage() == null ? "" : e.getMessage().toLowerCase();
if (msg.contains("constraint")) {
return false;
}
throw e;
} finally {
c.setAutoCommit(oldAuto);
}
}
}
private static void insertBlockchainStateStrict(
Connection c,
String blockchainName,
String login,
String blockchainKey,
long sizeLimit,
long nowMs
) throws SQLException {
String sql = """
INSERT INTO blockchain_state (
blockchain_name,
login,
blockchain_key,
size_limit,
file_size_bytes,
last_block_number,
last_block_hash,
updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""";
try (PreparedStatement ps = c.prepareStatement(sql)) {
int i = 1;
ps.setString(i++, blockchainName);
ps.setString(i++, login);
ps.setString(i++, blockchainKey);
ps.setLong(i++, sizeLimit);
ps.setLong(i++, 0L);
ps.setInt(i++, -1);
ps.setNull(i++, Types.BINARY); // старт: блоков ещё нет
ps.setLong(i++, nowMs);
ps.executeUpdate(); // если blockchainName занят -> constraint (PK)
}
}
}
@@ -17,21 +17,6 @@ public final class SolanaUsersSql {
current_users.blockchain_key AS blockchain_key, current_users.blockchain_key AS blockchain_key,
current_users.client_key AS client_key current_users.client_key AS client_key
FROM solana_user_pda_current current_users FROM solana_user_pda_current current_users
UNION ALL
SELECT
manual.login AS login,
manual.blockchain_name AS blockchain_name,
manual.solana_key AS solana_key,
manual.blockchain_key AS blockchain_key,
manual.client_key AS client_key
FROM solana_users_manual manual
WHERE NOT EXISTS (
SELECT 1
FROM solana_user_pda_current current_users
WHERE LOWER(current_users.login) = LOWER(manual.login)
)
) %s ) %s
""".formatted(alias); """.formatted(alias);
} }
@@ -111,40 +111,6 @@ CREATE TABLE IF NOT EXISTS solana_user_pda_current (
CREATE INDEX IF NOT EXISTS idx_user_pda_current_slot CREATE INDEX IF NOT EXISTS idx_user_pda_current_slot
ON solana_user_pda_current(slot); ON solana_user_pda_current(slot);
CREATE TABLE IF NOT EXISTS solana_users_manual (
login TEXT PRIMARY KEY,
blockchain_name TEXT NOT NULL UNIQUE,
solana_key TEXT NOT NULL,
blockchain_key TEXT NOT NULL,
client_key TEXT NOT NULL,
updated_at_ms BIGINT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_solana_users_manual_login
ON solana_users_manual(login);
CREATE OR REPLACE VIEW solana_users AS
SELECT
current_users.login AS login,
current_users.blockchain_name AS blockchain_name,
current_users.client_key AS solana_key,
current_users.blockchain_key AS blockchain_key,
current_users.client_key AS client_key
FROM solana_user_pda_current current_users
UNION ALL
SELECT
manual.login,
manual.blockchain_name,
manual.solana_key,
manual.blockchain_key,
manual.client_key
FROM solana_users_manual manual
WHERE NOT EXISTS (
SELECT 1
FROM solana_user_pda_current current_users
WHERE LOWER(current_users.login) = LOWER(manual.login)
);
CREATE TABLE IF NOT EXISTS solana_user_pda_history ( CREATE TABLE IF NOT EXISTS solana_user_pda_history (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
tx_signature TEXT NOT NULL, tx_signature TEXT NOT NULL,
@@ -43,9 +43,6 @@ import server.logic.ws_protocol.JSON.handlers.blockchain.Net_GetBlockchainBlock_
import server.logic.ws_protocol.JSON.handlers.blockchain.entyties.Net_AddBlock_Request; import server.logic.ws_protocol.JSON.handlers.blockchain.entyties.Net_AddBlock_Request;
import server.logic.ws_protocol.JSON.handlers.blockchain.entyties.Net_GetBlockchainBlock_Request; import server.logic.ws_protocol.JSON.handlers.blockchain.entyties.Net_GetBlockchainBlock_Request;
import server.logic.ws_protocol.JSON.handlers.tempToTest.Net_AddUser_Handler;
import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_AddUser_Request;
import server.logic.ws_protocol.JSON.handlers.tempToTest.Net_GetUser_Handler; import server.logic.ws_protocol.JSON.handlers.tempToTest.Net_GetUser_Handler;
import server.logic.ws_protocol.JSON.handlers.tempToTest.Net_TestGetFreeAvatarQuota_Handler; import server.logic.ws_protocol.JSON.handlers.tempToTest.Net_TestGetFreeAvatarQuota_Handler;
import server.logic.ws_protocol.JSON.handlers.tempToTest.Net_TestUploadFreeAvatar_Handler; import server.logic.ws_protocol.JSON.handlers.tempToTest.Net_TestUploadFreeAvatar_Handler;
@@ -142,7 +139,6 @@ public final class JsonHandlerRegistry {
} }
private static final Map<String, JsonMessageHandler> HANDLERS = Map.ofEntries( private static final Map<String, JsonMessageHandler> HANDLERS = Map.ofEntries(
Map.entry("AddUser", new Net_AddUser_Handler()),
Map.entry("GetUser", new Net_GetUser_Handler()), Map.entry("GetUser", new Net_GetUser_Handler()),
Map.entry("SearchUsers", new Net_SearchUsers_Handler()), Map.entry("SearchUsers", new Net_SearchUsers_Handler()),
Map.entry("TestGetFreeAvatarQuota", new Net_TestGetFreeAvatarQuota_Handler()), Map.entry("TestGetFreeAvatarQuota", new Net_TestGetFreeAvatarQuota_Handler()),
@@ -223,7 +219,6 @@ public final class JsonHandlerRegistry {
); );
private static final Map<String, Class<? extends Net_Request>> REQUEST_TYPES = Map.ofEntries( private static final Map<String, Class<? extends Net_Request>> REQUEST_TYPES = Map.ofEntries(
Map.entry("AddUser", Net_AddUser_Request.class),
Map.entry("GetUser", Net_GetUser_Request.class), Map.entry("GetUser", Net_GetUser_Request.class),
Map.entry("SearchUsers", Net_SearchUsers_Request.class), Map.entry("SearchUsers", Net_SearchUsers_Request.class),
Map.entry("TestGetFreeAvatarQuota", Net_TestGetFreeAvatarQuota_Request.class), Map.entry("TestGetFreeAvatarQuota", Net_TestGetFreeAvatarQuota_Request.class),
@@ -26,7 +26,7 @@ import java.security.SecureRandom;
* *
* Что делает: * Что делает:
* 1) Проверяет login. * 1) Проверяет login.
* 2) Находит пользователя (solana_users). * 2) Находит пользователя в текущем PDA snapshot.
* 3) Пишет solanaUser в ctx, ставит AUTH_STATUS_AUTH_IN_PROGRESS. * 3) Пишет solanaUser в ctx, ставит AUTH_STATUS_AUTH_IN_PROGRESS.
* 4) Генерирует authNonce (base64url(32)) и сохраняет в ctx.authNonce. * 4) Генерирует authNonce (base64url(32)) и сохраняет в ctx.authNonce.
*/ */
@@ -61,25 +61,9 @@ public class Net_AuthChallenge_Handler implements JsonMessageHandler {
} }
SolanaUserEntry solanaUserEntry = SolanaUsersDAO.getInstance().getByLogin(login); SolanaUserEntry solanaUserEntry = SolanaUsersDAO.getInstance().getByLogin(login);
if (solanaUserEntry == null) {
try {
solanaUserEntry = SolanaUserPdaImportService.findOrImportByLogin(login);
if (solanaUserEntry != null) {
log.info("AuthChallenge: пользователь {} импортирован из Solana PDA", solanaUserEntry.getLogin());
}
} catch (Exception e) {
log.error("AuthChallenge: ошибка lazy-import пользователя {} из Solana", login, e);
return NetExceptionResponseFactory.error(
req,
WireCodes.Status.SERVER_DATA_ERROR,
"SOLANA_IMPORT_FAILED",
"Ошибка проверки пользователя в Solana"
);
}
}
if (solanaUserEntry == null) { if (solanaUserEntry == null) {
return NetExceptionResponseFactory.error( return NetExceptionResponseFactory.error(
req, req,
WireCodes.Status.UNVERIFIED, WireCodes.Status.UNVERIFIED,
"UNKNOWN_USER", "UNKNOWN_USER",
"Пользователь с таким логином не найден" "Пользователь с таким логином не найден"
@@ -4,9 +4,6 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import shine.db.dao.SolanaUsersDAO;
import shine.db.dao.UserCreateDAO;
import shine.db.entities.SolanaUserEntry;
import utils.config.SolanaProgramsConfig; import utils.config.SolanaProgramsConfig;
import java.net.URI; import java.net.URI;
@@ -22,8 +19,7 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
/** /**
* Lazy-import пользователя из Solana PDA в локальную БД сервера. * Чтение пользовательских и серверных PDA из Solana.
* Используется при входе, если в solana_users нет записи по login.
*/ */
public final class SolanaUserPdaImportService { public final class SolanaUserPdaImportService {
@@ -34,34 +30,6 @@ public final class SolanaUserPdaImportService {
private SolanaUserPdaImportService() {} private SolanaUserPdaImportService() {}
public static SolanaUserEntry findOrImportByLogin(String loginRaw) throws Exception {
String login = normalizeLogin(loginRaw);
if (login == null) return null;
SolanaUsersDAO usersDao = SolanaUsersDAO.getInstance();
SolanaUserEntry existing = usersDao.getByLogin(login);
if (existing != null) return existing;
ParsedSolanaUser parsed = fetchFromSolana(login);
if (parsed == null) return null;
long now = System.currentTimeMillis();
long sizeLimit = parsed.paidLimitBytes > 0 ? parsed.paidLimitBytes : 100_000L;
boolean inserted = UserCreateDAO.getInstance().insertUserWithBlockchain(
parsed.login,
parsed.blockchainName,
parsed.clientKeyB64, // в текущей модели solanaKey = clientKey
parsed.blockchainKeyB64,
parsed.clientKeyB64,
sizeLimit,
now
);
if (!inserted) {
return usersDao.getByLogin(login);
}
return usersDao.getByLogin(login);
}
public static SessionTypeCheckResult checkSessionTypeAgainstPda(String loginRaw, String sessionKeyApi, int requestedSessionType) throws Exception { public static SessionTypeCheckResult checkSessionTypeAgainstPda(String loginRaw, String sessionKeyApi, int requestedSessionType) throws Exception {
String login = normalizeLogin(loginRaw); String login = normalizeLogin(loginRaw);
if (login == null) return SessionTypeCheckResult.noRecord(); if (login == null) return SessionTypeCheckResult.noRecord();
@@ -33,46 +33,10 @@
} }
} }
1. Добавление пользователя (AddUser) 1. Пользователь уже существует в актуальном PDA snapshot
Назначение: создать локальную запись пользователя с двумя ключами — solanaKey и clientKey. Назначение: перед началом auth-flow сервер уже должен видеть пользователя
в актуальном `solana_user_pda_current`.
📤 Запрос клиента
{
"op": "AddUser",
"requestId": "req-1",
"payload": {
"login": "anya4",
"loginId": 100212,
"bchId": 4222,
"solanaKey": "BASE64_LOGIN_KEY",
"clientKey": "BASE64_DEVICE_KEY",
"bchLimit": 1000000
}
}
🖥 Действия сервера
Проверяет корректность данных.
Вставляет запись в таблицу:
CREATE TABLE solana_users (
login TEXT NOT NULL,
loginId INTEGER PRIMARY KEY,
bchId INTEGER NOT NULL,
solanaKey TEXT,
clientKey TEXT,
bchLimit INTEGER
);
📥 Ответ
{
"op": "AddUser",
"requestId": "req-1",
"status": 200,
"payload": { "ok": true }
}
2. Шаг 1 — запрос временного пароля сессии 2. Шаг 1 — запрос временного пароля сессии
AuthChallenge AuthChallenge
@@ -173,4 +137,4 @@ pushAuthKey TEXT
} }
📘 Итоговая схема создания сессии 📘 Итоговая схема создания сессии
AddUser → AuthChallenge → CreateAuthSession → Session Created PDA snapshot ready → AuthChallenge → CreateAuthSession → Session Created
@@ -1,26 +0,0 @@
package server.logic.ws_protocol.JSON.handlers.tempToTest;
import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_AddUser_Request;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
/**
* AddUser отключен: регистрация работает через Solana.
*/
public class Net_AddUser_Handler implements JsonMessageHandler {
@Override
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) {
Net_AddUser_Request req = (Net_AddUser_Request) baseRequest;
return NetExceptionResponseFactory.error(
req,
410,
"ADD_USER_DISABLED",
"Серверная регистрация AddUser отключена. Используйте регистрацию через Solana."
);
}
}
@@ -5,7 +5,6 @@ import org.slf4j.LoggerFactory;
import server.logic.ws_protocol.JSON.ConnectionContext; import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.Net_Request; import server.logic.ws_protocol.JSON.entyties.Net_Request;
import server.logic.ws_protocol.JSON.entyties.Net_Response; import server.logic.ws_protocol.JSON.entyties.Net_Response;
import server.logic.ws_protocol.JSON.handlers.auth.SolanaUserPdaImportService;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler; import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_GetUser_Request; import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_GetUser_Request;
import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_GetUser_Response; import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_GetUser_Response;
@@ -43,9 +42,6 @@ public class Net_GetUser_Handler implements JsonMessageHandler {
try { try {
SolanaUserEntry u = usersDAO.getByLogin(req.getLogin()); SolanaUserEntry u = usersDAO.getByLogin(req.getLogin());
if (u == null) {
u = SolanaUserPdaImportService.findOrImportByLogin(req.getLogin());
}
Net_GetUser_Response resp = new Net_GetUser_Response(); Net_GetUser_Response resp = new Net_GetUser_Response();
resp.setOp(req.getOp()); resp.setOp(req.getOp());
@@ -1,58 +0,0 @@
package server.logic.ws_protocol.JSON.handlers.tempToTest.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
/**
* Запрос AddUser временная/тестовая регистрация локального пользователя.
*
* Клиент отправляет:
*
* {
* "op": "AddUser",
* "requestId": "test-add-1",
* "payload": {
* "login": "anya",
* "blockchainName": "anya-001",
* "solanaKey": "base64-ed25519-public-key-login",
* "blockchainKey": "base64-ed25519-public-key-blockchain",
* "clientKey": "base64-ed25519-public-key-device",
* "bchLimit": 1000000
* }
* }
*
* Все поля лежат внутри payload.
*/
public class Net_AddUser_Request extends Net_Request {
private String login;
private String blockchainName;
/** Ключ пользователя Solana (публичный ключ логина) */
private String solanaKey;
/** Ключ блокчейна (публичный ключ блокчейна) */
private String blockchainKey;
/** Ключ устройства (публичный ключ устройства) */
private String clientKey;
private Integer bchLimit;
public String getLogin() { return login; }
public void setLogin(String login) { this.login = login; }
public String getBlockchainName() { return blockchainName; }
public void setBlockchainName(String blockchainName) { this.blockchainName = blockchainName; }
public String getSolanaKey() { return solanaKey; }
public void setSolanaKey(String solanaKey) { this.solanaKey = solanaKey; }
public String getBlockchainKey() { return blockchainKey; }
public void setBlockchainKey(String blockchainKey) { this.blockchainKey = blockchainKey; }
public String getClientKey() { return clientKey; }
public void setClientKey(String clientKey) { this.clientKey = clientKey; }
public Integer getBchLimit() { return bchLimit; }
public void setBchLimit(Integer bchLimit) { this.bchLimit = bchLimit; }
}
@@ -1,21 +0,0 @@
// file: server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_AddUser_Response.java
package server.logic.ws_protocol.JSON.handlers.tempToTest.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
/**
* Успешный ответ на AddUser.
*
* Сейчас дополнительных полей нет достаточно status=200.
*
* Пример:
* {
* "op": "AddUser",
* "requestId": "test-add-1",
* "status": 200,
* "payload": { }
* }
*/
public class Net_AddUser_Response extends Net_Response {
// При необходимости сюда можно добавить, например, флаг created/updated и т.п.
}
@@ -1,6 +1,5 @@
package server.logic.ws_protocol.JSON.messages; package server.logic.ws_protocol.JSON.messages;
import server.logic.ws_protocol.JSON.handlers.auth.SolanaUserPdaImportService;
import shine.db.dao.SolanaUsersDAO; import shine.db.dao.SolanaUsersDAO;
import shine.db.entities.SignedMessageV2Entry; import shine.db.entities.SignedMessageV2Entry;
import shine.db.entities.SolanaUserEntry; import shine.db.entities.SolanaUserEntry;
@@ -62,10 +61,7 @@ final class SignedMessagesCore {
} }
private static SolanaUserEntry resolveUser(String login) throws Exception { private static SolanaUserEntry resolveUser(String login) throws Exception {
SolanaUsersDAO usersDao = SolanaUsersDAO.getInstance(); return SolanaUsersDAO.getInstance().getByLogin(login);
SolanaUserEntry user = usersDao.getByLogin(login);
if (user != null) return user;
return SolanaUserPdaImportService.findOrImportByLogin(login);
} }
static void validatePair(SignedMessageBlock incoming, SignedMessageBlock outgoing) { static void validatePair(SignedMessageBlock incoming, SignedMessageBlock outgoing) {
@@ -4,15 +4,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import server.logic.ws_protocol.JSON.entyties.Net_Exception_Response; import server.logic.ws_protocol.JSON.entyties.Net_Exception_Response;
import server.logic.ws_protocol.JSON.entyties.Net_Response; import server.logic.ws_protocol.JSON.entyties.Net_Response;
import server.logic.ws_protocol.JSON.handlers.auth.SolanaUserPdaImportService;
import server.logic.ws_protocol.JSON.handlers.blockchain.Net_AddBlock_Handler; import server.logic.ws_protocol.JSON.handlers.blockchain.Net_AddBlock_Handler;
import server.logic.ws_protocol.JSON.handlers.blockchain.Net_AddBlock_Handler_utils.BlockchainLocks; import server.logic.ws_protocol.JSON.handlers.blockchain.Net_AddBlock_Handler_utils.BlockchainLocks;
import server.logic.ws_protocol.JSON.handlers.blockchain.entyties.Net_AddBlock_Request; import server.logic.ws_protocol.JSON.handlers.blockchain.entyties.Net_AddBlock_Request;
import shine.db.dao.BlockchainResyncCleanupDAO; import shine.db.dao.BlockchainResyncCleanupDAO;
import shine.db.dao.BlockchainStateDAO; import shine.db.dao.BlockchainStateDAO;
import shine.db.dao.SyncServersDAO; import shine.db.dao.SyncServersDAO;
import shine.db.dao.UserCreateDAO;
import shine.db.dao.SolanaUsersDAO;
import shine.db.dao.SolanaUserPdaCurrentDAO; import shine.db.dao.SolanaUserPdaCurrentDAO;
import shine.db.entities.BlockchainStateEntry; import shine.db.entities.BlockchainStateEntry;
import shine.db.entities.SolanaUserPdaCurrentEntry; import shine.db.entities.SolanaUserPdaCurrentEntry;
@@ -20,7 +17,6 @@ import shine.db.entities.SyncServerEntry;
import server.sync.BlockchainResyncGuard; import server.sync.BlockchainResyncGuard;
import utils.files.FileStoreUtil; import utils.files.FileStoreUtil;
import utils.blockchain.BlockchainNameUtil; import utils.blockchain.BlockchainNameUtil;
import utils.config.AppConfig;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.List; import java.util.List;
@@ -55,12 +51,9 @@ public final class PeriodicBlockchainSyncService {
private static final Net_AddBlock_Handler ADD_BLOCK_HANDLER = new Net_AddBlock_Handler(); private static final Net_AddBlock_Handler ADD_BLOCK_HANDLER = new Net_AddBlock_Handler();
private static final BlockchainStateDAO STATE_DAO = BlockchainStateDAO.getInstance(); private static final BlockchainStateDAO STATE_DAO = BlockchainStateDAO.getInstance();
private static final SyncServersDAO SYNC_SERVERS_DAO = SyncServersDAO.getInstance(); private static final SyncServersDAO SYNC_SERVERS_DAO = SyncServersDAO.getInstance();
private static final UserCreateDAO USER_CREATE_DAO = UserCreateDAO.getInstance();
private static final BlockchainResyncCleanupDAO RESYNC_CLEANUP_DAO = BlockchainResyncCleanupDAO.getInstance(); private static final BlockchainResyncCleanupDAO RESYNC_CLEANUP_DAO = BlockchainResyncCleanupDAO.getInstance();
private static final FileStoreUtil FILE_STORE = FileStoreUtil.getInstance(); private static final FileStoreUtil FILE_STORE = FileStoreUtil.getInstance();
private static final SolanaUsersDAO SOLANA_USERS_DAO = SolanaUsersDAO.getInstance();
private static final SolanaUserPdaCurrentDAO SOLANA_USER_PDA_CURRENT_DAO = SolanaUserPdaCurrentDAO.getInstance(); private static final SolanaUserPdaCurrentDAO SOLANA_USER_PDA_CURRENT_DAO = SolanaUserPdaCurrentDAO.getInstance();
private static final String CONFIG_IMPORT_PROFILE_FROM_PARTNER = "sync.importUserProfileFromPartner.enabled";
private PeriodicBlockchainSyncService() {} private PeriodicBlockchainSyncService() {}
@@ -370,11 +363,7 @@ public final class PeriodicBlockchainSyncService {
if (login == null || login.isBlank()) { if (login == null || login.isBlank()) {
return false; return false;
} }
if (AppConfig.getInstance().getBoolean(CONFIG_IMPORT_PROFILE_FROM_PARTNER, false)) { return false;
return importUserProfileFromPartner(partner, login);
}
SolanaUserPdaImportService.findOrImportByLogin(login);
return STATE_DAO.getByBlockchainName(blockchainName) != null;
} catch (Exception e) { } catch (Exception e) {
log.warn("Periodic blockchain sync: failed to ensure local chain exists for blockchainName={} reason={}", log.warn("Periodic blockchain sync: failed to ensure local chain exists for blockchainName={} reason={}",
blockchainName, String.valueOf(e)); blockchainName, String.valueOf(e));
@@ -416,67 +405,6 @@ public final class PeriodicBlockchainSyncService {
return keyBytes != null && keyBytes.length == 32; return keyBytes != null && keyBytes.length == 32;
} }
private static boolean importUserProfileFromPartner(SyncServerEntry partner, String login) throws Exception {
if (partner == null || partner.getServerAddress() == null || partner.getServerAddress().isBlank()) {
return false;
}
RemoteBlockchainSyncClient.RemoteSyncUserProfile profile =
REMOTE.getSyncUserProfile(partner.getServerAddress(), login);
if (profile == null) {
log.warn("Periodic blockchain sync: partner has no sync profile for login={} partner={}",
login, normalize(partner.getLogin()));
return false;
}
long now = System.currentTimeMillis();
long sizeLimit = profile.blockchainSizeLimitBytes() > 0 ? profile.blockchainSizeLimitBytes() : 100_000L;
BlockchainStateEntry state = buildStateFromProfile(profile, sizeLimit, now);
if (SOLANA_USERS_DAO.existsByLogin(profile.login())) {
STATE_DAO.insertIfMissing(state);
return STATE_DAO.getByBlockchainName(profile.blockchainName()) != null;
}
boolean inserted = USER_CREATE_DAO.insertUserWithBlockchain(
profile.login(),
profile.blockchainName(),
profile.solanaKey(),
profile.blockchainKey(),
profile.clientKey(),
sizeLimit,
now
);
if (inserted) {
return STATE_DAO.getByBlockchainName(profile.blockchainName()) != null;
}
// Если пользователь уже успел существовать локально, но chain_state отсутствует,
// добиваем только state и не пытаемся пересоздать identity.
if (SOLANA_USERS_DAO.existsByLogin(profile.login())) {
STATE_DAO.insertIfMissing(state);
return STATE_DAO.getByBlockchainName(profile.blockchainName()) != null;
}
return false;
}
private static BlockchainStateEntry buildStateFromProfile(RemoteBlockchainSyncClient.RemoteSyncUserProfile profile,
long sizeLimit,
long nowMs) {
BlockchainStateEntry state = new BlockchainStateEntry();
state.setBlockchainName(profile.blockchainName());
state.setLogin(profile.login());
state.setBlockchainKey(profile.blockchainKey());
state.setSizeLimit(sizeLimit);
state.setFileSizeBytes(0L);
state.setLastBlockNumber(-1);
state.setLastBlockHash(null);
state.setUpdatedAtMs(nowMs);
return state;
}
private static String normalize(String value) { private static String normalize(String value) {
if (value == null) return null; if (value == null) return null;
String s = value.trim().toLowerCase(Locale.ROOT); String s = value.trim().toLowerCase(Locale.ROOT);
@@ -1,365 +0,0 @@
package test.it.cases;
import test.it.utils.TestConfig;
import test.it.utils.TestIds;
import test.it.utils.json.JsonBuilders;
import test.it.utils.json.JsonParsers;
import test.it.utils.log.TestResult;
import test.it.utils.ws.WsSession;
import java.time.Duration;
import java.util.List;
import static org.junit.jupiter.api.Assertions.fail;
/**
* IT_01_AddUser
* Создаёт 3 пользователей: TestUser1/2/3 (200 OK или 409 USER_ALREADY_EXISTS).
*
* Обновление:
* - теперь AddUser может вернуть 409 не только USER_ALREADY_EXISTS,
* но и BLOCKCHAIN_ALREADY_EXISTS / BLOCKCHAIN_STATE_ALREADY_EXISTS.
* - дополнительно проверяем GetUser (status=200 всегда).
* - добавлен SearchUsers: поиск по префиксу (первые 3 символа).
*/
public class IT_01_AddUser {
public static void main(String[] args) {
String summary = run();
System.out.println(summary);
}
public static String run() {
TestResult r = new TestResult("IT_01_AddUser");
Duration t = Duration.ofSeconds(5);
try (WsSession ws = WsSession.open()) {
checkPingAndServerInfo(r, ws, t);
r.ok("AddUser USER1: " + TestConfig.LOGIN());
String resp1 = ws.call("AddUser#USER1", JsonBuilders.addUser(TestConfig.LOGIN()), t);
checkAddUser200or409(r, resp1);
checkGetUserMustExist(r, ws, TestConfig.LOGIN(), t);
r.ok("AddUser USER2: " + TestConfig.LOGIN2());
String resp2 = ws.call("AddUser#USER2", JsonBuilders.addUser(TestConfig.LOGIN2()), t);
checkAddUser200or409(r, resp2);
checkGetUserMustExist(r, ws, TestConfig.LOGIN2(), t);
r.ok("AddUser USER3: " + TestConfig.LOGIN3());
String resp3 = ws.call("AddUser#USER3", JsonBuilders.addUser(TestConfig.LOGIN3()), t);
checkAddUser200or409(r, resp3);
checkGetUserMustExist(r, ws, TestConfig.LOGIN3(), t);
// Доп: проверяем case-insensitive поиск в GetUser
String mixed = mixCase(TestConfig.LOGIN());
r.ok("GetUser case-insensitive: запрос=" + mixed + " (должен найти " + TestConfig.LOGIN() + ")");
checkGetUserMustExist(r, ws, mixed, t);
// Доп: проверяем "не существует" (но status=200)
String missing = "NoSuchUser_987654321";
r.ok("GetUser missing: " + missing);
checkGetUserMustNotExist(r, ws, missing, t);
// SearchUsers: один раз ищем по первым трём символам логина USER1
String prefix3 = first3(TestConfig.LOGIN());
String prefix3Mixed = mixCase(prefix3);
r.ok("SearchUsers: prefix(3)='" + prefix3Mixed + "' (должен вернуть список и содержать " + TestConfig.LOGIN() + ")");
checkSearchUsersMustContain(r, ws, prefix3Mixed, TestConfig.LOGIN(), t);
checkNegativeRequests(r, ws, t);
} catch (Throwable e) {
r.fail("IT_01_AddUser упал: " + e.getMessage());
}
return r.summaryLine();
}
private static void checkPingAndServerInfo(TestResult r, WsSession ws, Duration t) {
String pingResp = ws.call("Ping", JsonBuilders.ping(System.currentTimeMillis()), t);
if (JsonParsers.status(pingResp) != 200 || !Boolean.TRUE.equals(JsonParsers.ok(pingResp))) {
r.fail("Ping: ожидали status=200 и ok=true, resp=" + pingResp);
fail("Ping unexpected response");
}
Long serverTs = JsonParsers.pingTs(pingResp);
if (serverTs == null || serverTs <= 0) {
r.fail("Ping: сервер не вернул ts, resp=" + pingResp);
fail("Ping missing ts");
}
r.ok("Ping: ok, serverTs=" + serverTs);
String infoResp = ws.call("GetServerInfo", JsonBuilders.getServerInfo(), t);
if (JsonParsers.status(infoResp) != 200 || !Boolean.TRUE.equals(JsonParsers.ok(infoResp))) {
r.fail("GetServerInfo: ожидали status=200 и ok=true, resp=" + infoResp);
fail("GetServerInfo unexpected response");
}
if (!JsonParsers.payloadIsObject(infoResp)) {
r.fail("GetServerInfo: payload должен быть объектом, resp=" + infoResp);
fail("GetServerInfo payload is not object");
}
r.ok("GetServerInfo: ok, url='" + safe(JsonParsers.payloadText(infoResp, "url"))
+ "', version='" + safe(JsonParsers.payloadText(infoResp, "version"))
+ "', physicalRegion='" + safe(JsonParsers.payloadText(infoResp, "physicalRegion"))
+ "', description='" + safe(JsonParsers.payloadText(infoResp, "description"))
+ "', origin='" + safe(JsonParsers.payloadText(infoResp, "origin"))
+ "', extraInfo='" + safe(JsonParsers.payloadText(infoResp, "extraInfo")) + "'");
}
private static void checkAddUser200or409(TestResult r, String resp) {
int st = JsonParsers.status(resp);
if (st == 200) {
r.ok("AddUser: status=200 (создан)");
return;
}
if (st == 409) {
String code = JsonParsers.errorCode(resp);
// раньше был только USER_ALREADY_EXISTS, теперь добавились ещё варианты
if ("USER_ALREADY_EXISTS".equals(code)) {
r.ok("AddUser: status=409 USER_ALREADY_EXISTS (уже был)");
return;
}
if ("BLOCKCHAIN_ALREADY_EXISTS".equals(code)) {
r.ok("AddUser: status=409 BLOCKCHAIN_ALREADY_EXISTS (blockchainName уже занят)");
return;
}
if ("BLOCKCHAIN_STATE_ALREADY_EXISTS".equals(code)) {
r.ok("AddUser: status=409 BLOCKCHAIN_STATE_ALREADY_EXISTS (blockchain_state уже есть)");
return;
}
r.fail("AddUser: status=409 но code=" + code + ", resp=" + resp);
fail("AddUser unexpected 409 code=" + code);
}
r.fail("AddUser: неожиданный status=" + st + ", resp=" + resp);
fail("AddUser unexpected status=" + st);
}
private static void checkGetUserMustExist(TestResult r, WsSession ws, String loginQuery, Duration t) {
String resp = ws.call("GetUser#" + loginQuery, JsonBuilders.getUser(loginQuery), t);
int st = JsonParsers.status(resp);
if (st != 200) {
r.fail("GetUser: ожидали status=200, получили " + st + ", resp=" + resp);
fail("GetUser unexpected status=" + st);
}
Boolean exists = JsonParsers.exists(resp);
if (exists == null || !exists) {
r.fail("GetUser: ожидали exists=true, resp=" + resp);
fail("GetUser expected exists=true");
}
// Проверяем, что сервер возвращает данные
String login = JsonParsers.userLogin(resp);
String blockchainName = JsonParsers.userBlockchainName(resp);
String solanaKey = JsonParsers.userSolanaKey(resp);
String blockchainKey = JsonParsers.userBlockchainKey(resp);
String clientKey = JsonParsers.userClientKey(resp);
if (isBlank(login) || isBlank(blockchainName) || isBlank(solanaKey) || isBlank(blockchainKey) || isBlank(clientKey)) {
r.fail("GetUser: exists=true, но поля пустые/неполные, resp=" + resp);
fail("GetUser returned incomplete user data");
}
// ВАЖНО:
// Поиск делается без учета регистра, но login/blockchainName должны вернуться как в БД.
// Для тех логинов, которые мы создаем в тесте, это ровно TestConfig.LOGIN*().
// Поэтому если запрос был смешанный регистр сравниваем не с loginQuery, а с "каноничным" логином из конфига.
String canonical = canonicalLogin(loginQuery);
if (canonical != null) {
if (!login.equals(canonical)) {
r.fail("GetUser: login должен вернуться как в БД. expected=" + canonical + ", got=" + login + ", resp=" + resp);
fail("GetUser wrong login case");
}
String expectedBch = TestConfig.getBlockchainName(canonical);
if (!blockchainName.equals(expectedBch)) {
r.fail("GetUser: blockchainName должен вернуться как в БД. expected=" + expectedBch + ", got=" + blockchainName + ", resp=" + resp);
fail("GetUser wrong blockchainName");
}
// ключи должны совпадать с теми, что AddUser использует при регистрации
String expSol = TestConfig.solanaPublicKeyB64(canonical);
String expBchKey = TestConfig.blockchainPublicKeyB64(canonical);
String expDev = TestConfig.clientPublicKeyB64(canonical);
if (!solanaKey.equals(expSol)) {
r.fail("GetUser: solanaKey mismatch, resp=" + resp);
fail("GetUser solanaKey mismatch");
}
if (!blockchainKey.equals(expBchKey)) {
r.fail("GetUser: blockchainKey mismatch, resp=" + resp);
fail("GetUser blockchainKey mismatch");
}
if (!clientKey.equals(expDev)) {
r.fail("GetUser: clientKey mismatch, resp=" + resp);
fail("GetUser clientKey mismatch");
}
}
r.ok("GetUser: exists=true, login=" + login + ", blockchainName=" + blockchainName);
}
private static void checkGetUserMustNotExist(TestResult r, WsSession ws, String loginQuery, Duration t) {
String resp = ws.call("GetUser#" + loginQuery, JsonBuilders.getUser(loginQuery), t);
int st = JsonParsers.status(resp);
if (st != 200) {
r.fail("GetUser(not exist): ожидали status=200, получили " + st + ", resp=" + resp);
fail("GetUser(not exist) unexpected status=" + st);
}
Boolean exists = JsonParsers.exists(resp);
if (exists == null) {
r.fail("GetUser(not exist): payload.exists отсутствует, resp=" + resp);
fail("GetUser(not exist) missing exists");
}
if (exists) {
r.fail("GetUser(not exist): ожидали exists=false, resp=" + resp);
fail("GetUser(not exist) expected exists=false");
}
r.ok("GetUser: exists=false (ok)");
}
private static void checkSearchUsersMustContain(TestResult r, WsSession ws, String prefix, String expectedLogin, Duration t) {
String resp = ws.call("SearchUsers#" + prefix, JsonBuilders.searchUsers(prefix), t);
int st = JsonParsers.status(resp);
if (st != 200) {
r.fail("SearchUsers: ожидали status=200, получили " + st + ", resp=" + resp);
fail("SearchUsers unexpected status=" + st);
}
List<String> logins = JsonParsers.searchLogins(resp);
if (logins == null || logins.isEmpty()) {
r.fail("SearchUsers: ожидали непустой список, resp=" + resp);
fail("SearchUsers expected non-empty list");
}
// ВАЖНО: ожидаемый логин должен быть в ответе в регистре БД (каноничный expectedLogin)
boolean found = false;
for (String s : logins) {
if (expectedLogin.equals(s)) {
found = true;
break;
}
}
if (!found) {
r.fail("SearchUsers: ожидаемый логин не найден. expected=" + expectedLogin + ", got=" + logins + ", resp=" + resp);
fail("SearchUsers expected login not found");
}
r.ok("SearchUsers: ok, prefix=" + prefix + ", results=" + logins.size() + ", contains=" + expectedLogin);
}
private static void checkNegativeRequests(TestResult r, WsSession ws, Duration t) {
String badAddUserReqId = TestIds.next("bad-adduser");
String badAddUser = """
{
"op": "AddUser",
"requestId": "%s",
"payload": {
"login": "",
"blockchainName": "%s",
"solanaKey": "%s",
"blockchainKey": "%s",
"clientKey": "%s",
"bchLimit": %d
}
}
""".formatted(
badAddUserReqId,
TestConfig.BCH_NAME(),
TestConfig.SOLANA_PUBKEY_B64(),
TestConfig.BLOCKCHAIN_PUBKEY_B64(),
TestConfig.DEVICE_PUBKEY_B64(),
TestConfig.TEST_BCH_LIMIT
);
String badAddUserResp = ws.call("AddUser#NEGATIVE", badAddUser, t);
assertErrorFormat(badAddUserResp, "AddUser", badAddUserReqId, "BAD_FIELDS");
r.ok("Negative AddUser: error format OK");
String badGetUserReqId = TestIds.next("bad-getuser");
String badGetUser = """
{
"op": "GetUser",
"requestId": "%s",
"payload": {
"login": ""
}
}
""".formatted(badGetUserReqId);
String badGetUserResp = ws.call("GetUser#NEGATIVE", badGetUser, t);
assertErrorFormat(badGetUserResp, "GetUser", badGetUserReqId, "BAD_FIELDS");
r.ok("Negative GetUser: error format OK");
String badSearchReqId = TestIds.next("bad-searchusers");
String badSearch = """
{
"op": "SearchUsers",
"requestId": "%s",
"payload": {
"prefix": ""
}
}
""".formatted(badSearchReqId);
String badSearchResp = ws.call("SearchUsers#NEGATIVE", badSearch, t);
assertErrorFormat(badSearchResp, "SearchUsers", badSearchReqId, "BAD_FIELDS");
r.ok("Negative SearchUsers: error format OK");
}
private static void assertErrorFormat(String resp, String op, String requestId, String code) {
int status = JsonParsers.status(resp);
if (status >= 200 && status < 300) fail("Expected non-2xx status: " + resp);
if (!Boolean.FALSE.equals(JsonParsers.ok(resp))) fail("Expected ok=false: " + resp);
if (!op.equals(JsonParsers.op(resp))) fail("Unexpected op: " + resp);
if (!requestId.equals(JsonParsers.requestId(resp))) fail("Unexpected requestId: " + resp);
if (!code.equals(JsonParsers.errorCode(resp))) fail("Unexpected error code: " + resp);
if (!JsonParsers.payloadIsObject(resp)) fail("payload must be object: " + resp);
if (JsonParsers.payloadSize(resp) != 0) fail("error payload must be empty object: " + resp);
if (isBlank(JsonParsers.message(resp))) fail("error message must be present: " + resp);
}
private static String canonicalLogin(String anyCaseLogin) {
if (anyCaseLogin == null) return null;
String x = anyCaseLogin.trim();
if (x.isEmpty()) return null;
// Привязка только к нашим тестовым логинам, чтобы не гадать.
if (x.equalsIgnoreCase(TestConfig.LOGIN())) return TestConfig.LOGIN();
if (x.equalsIgnoreCase(TestConfig.LOGIN2())) return TestConfig.LOGIN2();
if (x.equalsIgnoreCase(TestConfig.LOGIN3())) return TestConfig.LOGIN3();
return null;
}
private static String mixCase(String s) {
if (s == null) return null;
String x = s.trim();
if (x.length() < 2) return x;
// простой "микс" без рандома, чтобы тест был детерминированный
return Character.toUpperCase(x.charAt(0)) + x.substring(1).toLowerCase();
}
private static String first3(String s) {
if (s == null) return "";
String x = s.trim();
if (x.length() <= 3) return x;
return x.substring(0, 3);
}
private static boolean isBlank(String s) {
return s == null || s.trim().isEmpty();
}
private static String safe(String s) {
return s == null ? "" : s;
}
}
@@ -32,8 +32,7 @@ public class IT_02_Sessions {
private static final String LOGIN = TestConfig.LOGIN(); private static final String LOGIN = TestConfig.LOGIN();
public static void main(String[] args) { public static void main(String[] args) {
TestLog.info("Standalone: этот тест требует заранее созданных пользователей -> сначала запускаю IT_01_AddUser"); TestLog.info("Standalone: этот тест требует заранее существующего пользователя в актуальном PDA snapshot");
System.out.println(IT_01_AddUser.run());
String summary = run(); String summary = run();
System.out.println(summary); System.out.println(summary);
} }
@@ -34,8 +34,7 @@ import static org.junit.jupiter.api.Assertions.*;
public class IT_03_AddBlock_NoAuth { public class IT_03_AddBlock_NoAuth {
public static void main(String[] args) { public static void main(String[] args) {
TestLog.info("Standalone: этот тест требует заранее созданных пользователей -> запускаю IT_01_AddUser"); TestLog.info("Standalone: этот тест требует заранее существующих пользователей в актуальном PDA snapshot");
System.out.println(IT_01_AddUser.run());
String summary = run(); String summary = run();
System.out.println(summary); System.out.println(summary);
} }
@@ -28,8 +28,7 @@ public class IT_04_UserParams_NoAuth {
private static final ObjectMapper M = new ObjectMapper(); private static final ObjectMapper M = new ObjectMapper();
public static void main(String[] args) { public static void main(String[] args) {
TestLog.info("Standalone: этот тест требует заранее созданных пользователей -> сначала запускаю IT_01_AddUser"); TestLog.info("Standalone: этот тест требует заранее существующего пользователя в актуальном PDA snapshot");
System.out.println(IT_01_AddUser.run());
String summary = run(); String summary = run();
System.out.println(summary); System.out.println(summary);
} }
@@ -283,4 +282,4 @@ public class IT_04_UserParams_NoAuth {
return null; return null;
} }
} }
} }
@@ -8,7 +8,6 @@ import test.it.utils.log.TestResult;
import test.it.utils.ws.WsSession; import test.it.utils.ws.WsSession;
import utils.crypto.Ed25519Util; import utils.crypto.Ed25519Util;
import shine.db.dao.SolanaUsersDAO; import shine.db.dao.SolanaUsersDAO;
import shine.db.entities.SolanaUserEntry;
import utils.crypto.HashSHA256Util; import utils.crypto.HashSHA256Util;
import java.time.Duration; import java.time.Duration;
@@ -22,7 +21,7 @@ public class IT_07_EspPairing {
private static final String LOGIN = TestConfig.LOGIN(); private static final String LOGIN = TestConfig.LOGIN();
public static void main(String[] args) { public static void main(String[] args) {
TestLog.info("Standalone: при необходимости локально создаю тестового пользователя напрямую в БД"); TestLog.info("Standalone: этот тест требует заранее существующего пользователя в актуальном PDA snapshot");
String summary = run(); String summary = run();
System.out.println(summary); System.out.println(summary);
} }
@@ -211,13 +210,7 @@ public class IT_07_EspPairing {
if (SolanaUsersDAO.getInstance().existsByLogin(LOGIN)) { if (SolanaUsersDAO.getInstance().existsByLogin(LOGIN)) {
return; return;
} }
SolanaUserEntry entry = new SolanaUserEntry(); throw new IllegalStateException("Тестовый пользователь отсутствует в актуальном PDA snapshot: " + LOGIN);
entry.setLogin(LOGIN);
entry.setBlockchainName(TestConfig.getBlockchainName(LOGIN));
entry.setSolanaKey(TestConfig.solanaPublicKeyB64(LOGIN));
entry.setBlockchainKey(TestConfig.blockchainPublicKeyB64(LOGIN));
entry.setClientKey(TestConfig.clientPublicKeyB64(LOGIN));
SolanaUsersDAO.getInstance().insert(entry);
} }
private static String derivePairingHash(String login, String password) { private static String derivePairingHash(String login, String password) {
@@ -7,8 +7,6 @@ import blockchain.body.TextBody;
import blockchain.body.UserParamBody; import blockchain.body.UserParamBody;
import test.it.blockchain.AddBlockSender; import test.it.blockchain.AddBlockSender;
import test.it.blockchain.ChainState; import test.it.blockchain.ChainState;
import test.it.utils.TestIds;
import test.it.utils.json.JsonParsers;
import test.it.utils.log.TestResult; import test.it.utils.log.TestResult;
import test.it.utils.ws.WsSession; import test.it.utils.ws.WsSession;
import utils.crypto.Ed25519Util; import utils.crypto.Ed25519Util;
@@ -25,7 +23,8 @@ import java.util.Objects;
/** /**
* Вспомогательные функции для массового заполнения тестовой соц-сети через API. * Вспомогательные функции для массового заполнения тестовой соц-сети через API.
* Вся запись состояния делается только через AddUser / AddBlock. * Пользователи должны уже существовать в актуальном PDA snapshot сервера.
* Вся запись блокчейн-состояния делается через AddBlock.
*/ */
public final class SeedDataPopulationHelper { public final class SeedDataPopulationHelper {
@@ -52,7 +51,6 @@ public final class SeedDataPopulationHelper {
} }
public void createUsersAndHeaders(List<UserSpec> users) { public void createUsersAndHeaders(List<UserSpec> users) {
for (UserSpec user : users) createUserViaApi(user.login);
for (UserSpec user : users) initHeader(user.login); for (UserSpec user : users) initHeader(user.login);
} }
@@ -159,48 +157,6 @@ public final class SeedDataPopulationHelper {
), timeout); ), timeout);
} }
private void createUserViaApi(String login) {
String requestId = TestIds.next("seed_adduser");
String req = """
{
"op": "AddUser",
"requestId": "%s",
"payload": {
"login": "%s",
"blockchainName": "%s",
"solanaKey": "%s",
"blockchainKey": "%s",
"clientKey": "%s",
"bchLimit": 50000000
}
}
""".formatted(
requestId,
login,
bch(login),
keys.solanaPublicB64,
keys.blockchainPublicB64,
keys.clientPublicB64
);
String resp = ws.call("AddUser#" + login, req, timeout);
int status = JsonParsers.status(resp);
if (status == 200) {
result.ok("AddUser " + login + ": created");
return;
}
if (status == 409) {
String code = JsonParsers.errorCode(resp);
if ("USER_ALREADY_EXISTS".equals(code)
|| "BLOCKCHAIN_ALREADY_EXISTS".equals(code)
|| "BLOCKCHAIN_STATE_ALREADY_EXISTS".equals(code)) {
result.ok("AddUser " + login + ": already exists (" + code + ")");
return;
}
}
throw new IllegalStateException("AddUser failed for " + login + ": status=" + status + ", resp=" + resp);
}
private void initHeader(String login) { private void initHeader(String login) {
ChainState state = new ChainState(); ChainState state = new ChainState();
AddBlockSender sender = new AddBlockSender(ws, state, login, bch(login), keys.blockchainPrivate32); AddBlockSender sender = new AddBlockSender(ws, state, login, bch(login), keys.blockchainPrivate32);
@@ -12,7 +12,8 @@ import static org.junit.jupiter.api.Assertions.fail;
* Seed_TestDataPopulation * Seed_TestDataPopulation
* *
* Заполняет тестовую соц-сеть через API: * Заполняет тестовую соц-сеть через API:
* - пользователи + HEADER через AddUser/AddBlock; * - пользователи должны уже существовать в актуальном PDA snapshot;
* - HEADER и остальные блоки пишутся через AddBlock;
* - параметры профиля (имя, фамилия, пол, адрес, web, phone, official, shine) через AddBlock USER_PARAM; * - параметры профиля (имя, фамилия, пол, адрес, web, phone, official, shine) через AddBlock USER_PARAM;
* - связи (close friend/contact/follow/parent/child/sibling) через AddBlock CONNECTION; * - связи (close friend/contact/follow/parent/child/sibling) через AddBlock CONNECTION;
* - посты через AddBlock TEXT_POST. * - посты через AddBlock TEXT_POST.
@@ -34,7 +35,7 @@ public class Seed_TestDataPopulation {
SeedDataPopulationHelper seed = new SeedDataPopulationHelper(ws, result, timeout, PASSWORD); SeedDataPopulationHelper seed = new SeedDataPopulationHelper(ws, result, timeout, PASSWORD);
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// 1) Пользователи и профили // 1) Заголовки и профили
// ----------------------------------------------------------------- // -----------------------------------------------------------------
List<SeedDataPopulationHelper.UserSpec> users = buildUsers(); List<SeedDataPopulationHelper.UserSpec> users = buildUsers();
seed.createUsersAndHeaders(users); seed.createUsersAndHeaders(users);
@@ -1,6 +1,5 @@
package test.it.runner; package test.it.runner;
import test.it.cases.IT_01_AddUser;
import test.it.cases.IT_00_TechnicalRequests; import test.it.cases.IT_00_TechnicalRequests;
import test.it.cases.IT_02_Sessions; import test.it.cases.IT_02_Sessions;
import test.it.cases.IT_03_AddBlock_NoAuth; import test.it.cases.IT_03_AddBlock_NoAuth;
@@ -44,9 +43,6 @@ public class IT_RunAllMain {
String s0 = IT_00_TechnicalRequests.run(); summaries.add(s0); String s0 = IT_00_TechnicalRequests.run(); summaries.add(s0);
if (s0.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); } if (s0.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); }
String s1 = IT_01_AddUser.run(); summaries.add(s1);
if (s1.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); }
String s2 = IT_02_Sessions.run(); summaries.add(s2); String s2 = IT_02_Sessions.run(); summaries.add(s2);
if (s2.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); } if (s2.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); }
@@ -2,7 +2,6 @@ package test.it.suite;
import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.api.Suite;
import test.it.cases.IT_01_AddUser;
import test.it.cases.IT_02_Sessions; import test.it.cases.IT_02_Sessions;
import test.it.cases.IT_03_AddBlock_NoAuth; import test.it.cases.IT_03_AddBlock_NoAuth;
import test.it.cases.Seed_TestDataPopulation; import test.it.cases.Seed_TestDataPopulation;
@@ -15,7 +14,6 @@ import test.it.cases.Seed_TestDataPopulation;
*/ */
@Suite @Suite
@SelectClasses({ @SelectClasses({
IT_01_AddUser.class,
IT_02_Sessions.class, IT_02_Sessions.class,
IT_03_AddBlock_NoAuth.class, IT_03_AddBlock_NoAuth.class,
Seed_TestDataPopulation.class Seed_TestDataPopulation.class
@@ -126,12 +126,12 @@ public final class TestConfig {
public static String BCH_NAME2() { return getBlockchainName(LOGIN2()); } public static String BCH_NAME2() { return getBlockchainName(LOGIN2()); }
public static String BCH_NAME3() { return getBlockchainName(LOGIN3()); } public static String BCH_NAME3() { return getBlockchainName(LOGIN3()); }
/** solanaKey для AddUser: публичный ключ Solana-пользователя */ /** публичный ключ Solana-пользователя */
public static String SOLANA_PUBKEY_B64() { return solanaPublicKeyB64(LOGIN()); } public static String SOLANA_PUBKEY_B64() { return solanaPublicKeyB64(LOGIN()); }
public static String SOLANA2_PUBKEY_B64() { return solanaPublicKeyB64(LOGIN2()); } public static String SOLANA2_PUBKEY_B64() { return solanaPublicKeyB64(LOGIN2()); }
public static String SOLANA3_PUBKEY_B64() { return solanaPublicKeyB64(LOGIN3()); } public static String SOLANA3_PUBKEY_B64() { return solanaPublicKeyB64(LOGIN3()); }
/** blockchainKey для AddUser: публичный ключ блокчейна */ /** публичный ключ блокчейна */
public static String BLOCKCHAIN_PUBKEY_B64() { return blockchainPublicKeyB64(LOGIN()); } public static String BLOCKCHAIN_PUBKEY_B64() { return blockchainPublicKeyB64(LOGIN()); }
public static String BLOCKCHAIN2_PUBKEY_B64() { return blockchainPublicKeyB64(LOGIN2()); } public static String BLOCKCHAIN2_PUBKEY_B64() { return blockchainPublicKeyB64(LOGIN2()); }
public static String BLOCKCHAIN3_PUBKEY_B64() { return blockchainPublicKeyB64(LOGIN3()); } public static String BLOCKCHAIN3_PUBKEY_B64() { return blockchainPublicKeyB64(LOGIN3()); }
@@ -11,40 +11,6 @@ import java.util.Base64;
public final class JsonBuilders { public final class JsonBuilders {
private JsonBuilders() {} private JsonBuilders() {}
// ---------------- AddUser ----------------
public static String addUser(String login) {
String requestId = TestIds.next("adduser");
String blockchainName = TestConfig.getBlockchainName(login);
String solanaKeyB64 = TestConfig.solanaPublicKeyB64(login);
String blockchainKeyB64 = TestConfig.blockchainPublicKeyB64(login);
String clientKeyB64 = TestConfig.clientPublicKeyB64(login);
return """
{
"op": "AddUser",
"requestId": "%s",
"payload": {
"login": "%s",
"blockchainName": "%s",
"solanaKey": "%s",
"blockchainKey": "%s",
"clientKey": "%s",
"bchLimit": %d
}
}
""".formatted(
requestId,
login,
blockchainName,
solanaKeyB64,
blockchainKeyB64,
clientKeyB64,
TestConfig.TEST_BCH_LIMIT
);
}
// ---------------- GetUser ---------------- // ---------------- GetUser ----------------
public static String getUser(String login) { public static String getUser(String login) {
+1 -1
View File
@@ -1,2 +1,2 @@
client.version=1.2.354 client.version=1.2.354
server.version=1.2.333 server.version=1.2.334