Сервер: дочистить legacy-слои и доки

This commit is contained in:
AidarKC
2026-07-27 19:29:10 +04:00
parent 0db3c3af5a
commit 6ed91a105e
33 changed files with 127 additions and 165 deletions
@@ -29,7 +29,7 @@ import java.sql.SQLException;
* отдельным recovery/resync-слоем после успешного commit. * отдельным recovery/resync-слоем после успешного commit.
* *
* Важный смысл текущей реализации: * Важный смысл текущей реализации:
* - мы НЕ трогаем identity-слой (`solana_users`) и НЕ трогаем DM-таблицы; * - мы НЕ трогаем current users слой (`solana_user_pda_current`) и НЕ трогаем DM-таблицы;
* - мы очищаем только блокчейн пользователя и derived-state, который строится из неё; * - мы очищаем только блокчейн пользователя и derived-state, который строится из неё;
* - висячие cross-chain ссылки в чужих blocks допускаются как нормальное поведение системы. * - висячие cross-chain ссылки в чужих blocks допускаются как нормальное поведение системы.
*/ */
@@ -142,7 +142,7 @@ public final class BlockchainStateDAO {
* Строгая вставка state только если записи ещё нет. * Строгая вставка state только если записи ещё нет.
* *
* Нужна для recovery / resync: * Нужна для recovery / resync:
* - identity пользователя уже может существовать в solana_users; * - runtime-проекция пользователя уже может существовать в current users слое;
* - в таком случае нам надо восстановить только blockchain_state; * - в таком случае нам надо восстановить только blockchain_state;
* - если запись уже есть, метод просто ничего не меняет. * - если запись уже есть, метод просто ничего не меняет.
*/ */
@@ -1,7 +1,7 @@
package shine.db.dao; package shine.db.dao;
import shine.db.DbController; import shine.db.DbController;
import shine.db.entities.SignedMessageV2Entry; import shine.db.entities.SignedMessageEntry;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
@@ -11,7 +11,7 @@ import java.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public final class SignedMessagesV2DAO { public final class SignedMessagesDAO {
public enum ApplyStatus { public enum ApplyStatus {
APPLIED, APPLIED,
@@ -24,21 +24,21 @@ public final class SignedMessagesV2DAO {
} }
} }
private static volatile SignedMessagesV2DAO instance; private static volatile SignedMessagesDAO instance;
private final DbController db = DbController.getInstance(); private final DbController db = DbController.getInstance();
private SignedMessagesV2DAO() {} private SignedMessagesDAO() {}
public static SignedMessagesV2DAO getInstance() { public static SignedMessagesDAO getInstance() {
if (instance == null) { if (instance == null) {
synchronized (SignedMessagesV2DAO.class) { synchronized (SignedMessagesDAO.class) {
if (instance == null) instance = new SignedMessagesV2DAO(); if (instance == null) instance = new SignedMessagesDAO();
} }
} }
return instance; return instance;
} }
public ApplyStatus insertIfAbsent(SignedMessageV2Entry e) throws Exception { public ApplyStatus insertIfAbsent(SignedMessageEntry e) throws Exception {
return withBusyRetry(() -> { return withBusyRetry(() -> {
try (Connection c = db.getConnection()) { try (Connection c = db.getConnection()) {
if (isBlockedByConversationDelete(c, e.getFromLogin(), e.getToLogin(), e.getTimeMs())) { if (isBlockedByConversationDelete(c, e.getFromLogin(), e.getToLogin(), e.getTimeMs())) {
@@ -65,7 +65,7 @@ public final class SignedMessagesV2DAO {
}); });
} }
public boolean insertPairBothOrNothing(SignedMessageV2Entry first, SignedMessageV2Entry second) throws Exception { public boolean insertPairBothOrNothing(SignedMessageEntry first, SignedMessageEntry second) throws Exception {
return withBusyRetry(() -> { return withBusyRetry(() -> {
try (Connection c = db.getConnection()) { try (Connection c = db.getConnection()) {
boolean prevAutoCommit = c.getAutoCommit(); boolean prevAutoCommit = c.getAutoCommit();
@@ -94,7 +94,7 @@ public final class SignedMessagesV2DAO {
}); });
} }
public ApplyStatus upsertContentPair(SignedMessageV2Entry incoming, SignedMessageV2Entry outgoing) throws Exception { public ApplyStatus upsertContentPair(SignedMessageEntry incoming, SignedMessageEntry outgoing) throws Exception {
return withBusyRetry(() -> { return withBusyRetry(() -> {
try (Connection c = db.getConnection()) { try (Connection c = db.getConnection()) {
boolean prevAutoCommit = c.getAutoCommit(); boolean prevAutoCommit = c.getAutoCommit();
@@ -135,7 +135,7 @@ public final class SignedMessagesV2DAO {
}); });
} }
public ApplyStatus upsertIncomingCopy(SignedMessageV2Entry incoming) throws Exception { public ApplyStatus upsertIncomingCopy(SignedMessageEntry incoming) throws Exception {
return withBusyRetry(() -> { return withBusyRetry(() -> {
try (Connection c = db.getConnection()) { try (Connection c = db.getConnection()) {
boolean prevAutoCommit = c.getAutoCommit(); boolean prevAutoCommit = c.getAutoCommit();
@@ -172,7 +172,7 @@ public final class SignedMessagesV2DAO {
}); });
} }
public ApplyStatus applyDeleteMessage(SignedMessageV2Entry tombstone) throws Exception { public ApplyStatus applyDeleteMessage(SignedMessageEntry tombstone) throws Exception {
return withBusyRetry(() -> { return withBusyRetry(() -> {
try (Connection c = db.getConnection()) { try (Connection c = db.getConnection()) {
boolean prevAutoCommit = c.getAutoCommit(); boolean prevAutoCommit = c.getAutoCommit();
@@ -203,7 +203,7 @@ public final class SignedMessagesV2DAO {
}); });
} }
public ApplyStatus applyDeleteConversation(SignedMessageV2Entry tombstone) throws Exception { public ApplyStatus applyDeleteConversation(SignedMessageEntry tombstone) throws Exception {
return withBusyRetry(() -> { return withBusyRetry(() -> {
try (Connection c = db.getConnection()) { try (Connection c = db.getConnection()) {
boolean prevAutoCommit = c.getAutoCommit(); boolean prevAutoCommit = c.getAutoCommit();
@@ -231,7 +231,7 @@ public final class SignedMessagesV2DAO {
}); });
} }
public SignedMessageV2Entry getByMessageKey(String messageKey) throws Exception { public SignedMessageEntry getByMessageKey(String messageKey) throws Exception {
try (Connection c = db.getConnection()) { try (Connection c = db.getConnection()) {
String sql = """ String sql = """
SELECT SELECT
@@ -252,7 +252,7 @@ public final class SignedMessagesV2DAO {
} }
} }
public SignedMessageV2Entry getLatestConversationDelete(String fromLogin, String toLogin) throws Exception { public SignedMessageEntry getLatestConversationDelete(String fromLogin, String toLogin) throws Exception {
try (Connection c = db.getConnection()) { try (Connection c = db.getConnection()) {
return getLatestConversationDelete(c, fromLogin, toLogin); return getLatestConversationDelete(c, fromLogin, toLogin);
} }
@@ -314,7 +314,7 @@ public final class SignedMessagesV2DAO {
}); });
} }
public List<SignedMessageV2Entry> listPendingForSession(String login, String sessionId) throws Exception { public List<SignedMessageEntry> listPendingForSession(String login, String sessionId) throws Exception {
return withBusyRetry(() -> { return withBusyRetry(() -> {
try (Connection c = db.getConnection()) { try (Connection c = db.getConnection()) {
String fillSql = """ String fillSql = """
@@ -354,7 +354,7 @@ public final class SignedMessagesV2DAO {
WHERE d.session_id = ? AND d.delivered = 0 WHERE d.session_id = ? AND d.delivered = 0
ORDER BY m.time_ms ASC, m.revision_time_ms ASC, m.reencrypted_at_ms ASC, m.created_at_ms ASC ORDER BY m.time_ms ASC, m.revision_time_ms ASC, m.reencrypted_at_ms ASC, m.created_at_ms ASC
""".formatted(messagesTable()); """.formatted(messagesTable());
List<SignedMessageV2Entry> out = new ArrayList<>(); List<SignedMessageEntry> out = new ArrayList<>();
try (PreparedStatement ps = c.prepareStatement(sql)) { try (PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, sessionId); ps.setString(1, sessionId);
try (ResultSet rs = ps.executeQuery()) { try (ResultSet rs = ps.executeQuery()) {
@@ -366,7 +366,7 @@ public final class SignedMessagesV2DAO {
}); });
} }
public List<SignedMessageV2Entry> listConversationPage( public List<SignedMessageEntry> listConversationPage(
String login, String login,
String peerLogin, String peerLogin,
long beforeTimeMs, long beforeTimeMs,
@@ -395,7 +395,7 @@ public final class SignedMessagesV2DAO {
ORDER BY time_ms DESC, message_key DESC ORDER BY time_ms DESC, message_key DESC
LIMIT ? LIMIT ?
""".formatted(messagesTable()); """.formatted(messagesTable());
List<SignedMessageV2Entry> out = new ArrayList<>(); List<SignedMessageEntry> out = new ArrayList<>();
try (PreparedStatement ps = c.prepareStatement(sql)) { try (PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, login); ps.setString(1, login);
ps.setString(2, login); ps.setString(2, login);
@@ -416,7 +416,7 @@ public final class SignedMessagesV2DAO {
} }
} }
private void upsertMessage(Connection c, SignedMessageV2Entry e) throws SQLException { private void upsertMessage(Connection c, SignedMessageEntry e) throws SQLException {
String sql = """ String sql = """
INSERT INTO %s ( INSERT INTO %s (
message_key, base_key, target_login, from_login, to_login, message_key, base_key, target_login, from_login, to_login,
@@ -448,7 +448,7 @@ public final class SignedMessagesV2DAO {
} }
} }
private void markMessageReadByReceipt(Connection c, SignedMessageV2Entry entry) throws SQLException { private void markMessageReadByReceipt(Connection c, SignedMessageEntry entry) throws SQLException {
if (entry == null) return; if (entry == null) return;
int messageType = entry.getMessageType(); int messageType = entry.getMessageType();
if (messageType != 3 && messageType != 4) return; if (messageType != 3 && messageType != 4) return;
@@ -552,7 +552,7 @@ public final class SignedMessagesV2DAO {
} }
} }
private SignedMessageV2Entry getLatestConversationDelete(Connection c, String fromLogin, String toLogin) throws Exception { private SignedMessageEntry getLatestConversationDelete(Connection c, String fromLogin, String toLogin) throws Exception {
String sql = """ String sql = """
SELECT SELECT
message_key, base_key, target_login, from_login, to_login, message_key, base_key, target_login, from_login, to_login,
@@ -646,7 +646,7 @@ public final class SignedMessagesV2DAO {
} }
} }
private int insertStrict(Connection c, SignedMessageV2Entry e) throws SQLException { private int insertStrict(Connection c, SignedMessageEntry e) throws SQLException {
String sql = """ String sql = """
INSERT INTO %s ( INSERT INTO %s (
message_key, base_key, target_login, from_login, to_login, message_key, base_key, target_login, from_login, to_login,
@@ -661,7 +661,7 @@ public final class SignedMessagesV2DAO {
} }
} }
private void bindSignedMessage(PreparedStatement ps, SignedMessageV2Entry e) throws SQLException { private void bindSignedMessage(PreparedStatement ps, SignedMessageEntry e) throws SQLException {
ps.setString(1, e.getMessageKey()); ps.setString(1, e.getMessageKey());
ps.setString(2, e.getBaseKey()); ps.setString(2, e.getBaseKey());
ps.setString(3, e.getTargetLogin()); ps.setString(3, e.getTargetLogin());
@@ -718,8 +718,8 @@ public final class SignedMessagesV2DAO {
return "signed_messages"; return "signed_messages";
} }
private SignedMessageV2Entry mapRow(ResultSet rs) throws Exception { private SignedMessageEntry mapRow(ResultSet rs) throws Exception {
SignedMessageV2Entry e = new SignedMessageV2Entry(); SignedMessageEntry e = new SignedMessageEntry();
e.setMessageKey(rs.getString("message_key")); e.setMessageKey(rs.getString("message_key"));
e.setBaseKey(rs.getString("base_key")); e.setBaseKey(rs.getString("base_key"));
e.setTargetLogin(rs.getString("target_login")); e.setTargetLogin(rs.getString("target_login"));
@@ -743,7 +743,7 @@ public final class SignedMessagesV2DAO {
} }
private record RevisionMarker(long revisionTimeMs, long reencryptedAtMs) { private record RevisionMarker(long revisionTimeMs, long reencryptedAtMs) {
private static RevisionMarker of(SignedMessageV2Entry entry) { private static RevisionMarker of(SignedMessageEntry entry) {
return new RevisionMarker(entry.getRevisionTimeMs(), entry.getReencryptedAtMs()); return new RevisionMarker(entry.getRevisionTimeMs(), entry.getReencryptedAtMs());
} }
} }
@@ -1,6 +1,6 @@
package shine.db.entities; package shine.db.entities;
public class SignedMessageV2Entry { public class SignedMessageEntry {
private String messageKey; private String messageKey;
private String baseKey; private String baseKey;
private String targetLogin; private String targetLogin;
@@ -25,8 +25,8 @@ public class ConnectionContext {
public static final int AUTH_STATUS_AUTH_IN_PROGRESS = 1; // выполнен challenge (AuthChallenge или SessionChallenge) public static final int AUTH_STATUS_AUTH_IN_PROGRESS = 1; // выполнен challenge (AuthChallenge или SessionChallenge)
public static final int AUTH_STATUS_USER = 2; // авторизованный пользователь public static final int AUTH_STATUS_USER = 2; // авторизованный пользователь
// Полный пользователь из БД (solana_users) // Полный пользователь из runtime БД (current users / solana_user_pda_current)
private CurrentUserEntry solanaUserEntry; private CurrentUserEntry currentUserEntry;
// Активная сессия из БД (active_sessions) // Активная сессия из БД (active_sessions)
private ActiveSessionEntry activeSessionEntry; private ActiveSessionEntry activeSessionEntry;
@@ -89,12 +89,12 @@ public class ConnectionContext {
// --- SolanaUser / ActiveSession --- // --- SolanaUser / ActiveSession ---
public CurrentUserEntry getSolanaUser() { public CurrentUserEntry getCurrentUser() {
return solanaUserEntry; return currentUserEntry;
} }
public void setSolanaUser(CurrentUserEntry solanaUserEntry) { public void setCurrentUser(CurrentUserEntry currentUserEntry) {
this.solanaUserEntry = solanaUserEntry; this.currentUserEntry = currentUserEntry;
} }
public ActiveSessionEntry getActiveSession() { public ActiveSessionEntry getActiveSession() {
@@ -108,7 +108,7 @@ public class ConnectionContext {
// --- Удобный геттер для логина --- // --- Удобный геттер для логина ---
public String getLogin() { public String getLogin() {
return solanaUserEntry != null ? solanaUserEntry.getLogin() : null; return currentUserEntry != null ? currentUserEntry.getLogin() : null;
} }
// --- sessionId --- // --- sessionId ---
@@ -176,7 +176,7 @@ public class ConnectionContext {
} }
public void reset() { public void reset() {
solanaUserEntry = null; currentUserEntry = null;
activeSessionEntry = null; activeSessionEntry = null;
sessionId = null; sessionId = null;
@@ -70,7 +70,7 @@ public class Net_AuthChallenge_Handler implements JsonMessageHandler {
); );
} }
ctx.setSolanaUser(solanaUserEntry); ctx.setCurrentUser(solanaUserEntry);
ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_AUTH_IN_PROGRESS); ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_AUTH_IN_PROGRESS);
byte[] buf = new byte[32]; byte[] buf = new byte[32];
@@ -42,7 +42,7 @@ public class Net_CloseActiveSession_Handler implements JsonMessageHandler {
public Net_Response handle(Net_Request baseReq, ConnectionContext ctx) throws Exception { public Net_Response handle(Net_Request baseReq, ConnectionContext ctx) throws Exception {
Net_CloseActiveSession_Request req = (Net_CloseActiveSession_Request) baseReq; Net_CloseActiveSession_Request req = (Net_CloseActiveSession_Request) baseReq;
if (ctx == null || ctx.getSolanaUser() == null || ctx.getAuthenticationStatus() != ConnectionContext.AUTH_STATUS_USER) { if (ctx == null || ctx.getCurrentUser() == null || ctx.getAuthenticationStatus() != ConnectionContext.AUTH_STATUS_USER) {
return NetExceptionResponseFactory.error( return NetExceptionResponseFactory.error(
req, req,
WireCodes.Status.UNVERIFIED, WireCodes.Status.UNVERIFIED,
@@ -51,7 +51,7 @@ public class Net_CloseActiveSession_Handler implements JsonMessageHandler {
); );
} }
CurrentUserEntry user = ctx.getSolanaUser(); CurrentUserEntry user = ctx.getCurrentUser();
String currentLogin = user.getLogin(); String currentLogin = user.getLogin();
String targetSessionId = req.getSessionId(); String targetSessionId = req.getSessionId();
@@ -58,7 +58,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
Net_CreateAuthSession_Request req = (Net_CreateAuthSession_Request) baseReq; Net_CreateAuthSession_Request req = (Net_CreateAuthSession_Request) baseReq;
if (ctx == null if (ctx == null
|| ctx.getSolanaUser() == null || ctx.getCurrentUser() == null
|| ctx.getAuthNonce() == null || ctx.getAuthNonce() == null
|| ctx.getAuthenticationStatus() != ConnectionContext.AUTH_STATUS_AUTH_IN_PROGRESS) { || ctx.getAuthenticationStatus() != ConnectionContext.AUTH_STATUS_AUTH_IN_PROGRESS) {
@@ -72,7 +72,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
return err; return err;
} }
CurrentUserEntry userFromContext = ctx.getSolanaUser(); CurrentUserEntry userFromContext = ctx.getCurrentUser();
String loginFromContext = userFromContext.getLogin(); String loginFromContext = userFromContext.getLogin();
String loginFromReq = req.getLogin(); String loginFromReq = req.getLogin();
if (loginFromReq == null || loginFromReq.isBlank()) { if (loginFromReq == null || loginFromReq.isBlank()) {
@@ -36,7 +36,7 @@ public class Net_ListSessions_Handler implements JsonMessageHandler {
public Net_Response handle(Net_Request baseReq, ConnectionContext ctx) throws Exception { public Net_Response handle(Net_Request baseReq, ConnectionContext ctx) throws Exception {
Net_ListSessions_Request req = (Net_ListSessions_Request) baseReq; Net_ListSessions_Request req = (Net_ListSessions_Request) baseReq;
if (ctx == null || ctx.getSolanaUser() == null || ctx.getAuthenticationStatus() != ConnectionContext.AUTH_STATUS_USER) { if (ctx == null || ctx.getCurrentUser() == null || ctx.getAuthenticationStatus() != ConnectionContext.AUTH_STATUS_USER) {
return NetExceptionResponseFactory.error( return NetExceptionResponseFactory.error(
req, req,
WireCodes.Status.UNVERIFIED, WireCodes.Status.UNVERIFIED,
@@ -45,7 +45,7 @@ public class Net_ListSessions_Handler implements JsonMessageHandler {
); );
} }
CurrentUserEntry user = ctx.getSolanaUser(); CurrentUserEntry user = ctx.getCurrentUser();
String currentLogin = user.getLogin(); String currentLogin = user.getLogin();
List<ActiveSessionEntry> sessions; List<ActiveSessionEntry> sessions;
@@ -294,7 +294,7 @@ public class Net_SessionLogin_Handler implements JsonMessageHandler {
// ctx // ctx
ctx.setActiveSession(session); ctx.setActiveSession(session);
ctx.setSolanaUser(user); ctx.setCurrentUser(user);
ctx.setSessionId(sessionId); ctx.setSessionId(sessionId);
ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_USER); ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_USER);
@@ -54,7 +54,7 @@ public class Net_GetFriendsLists_Handler implements JsonMessageHandler {
try (Connection c = db.getConnection()) { try (Connection c = db.getConnection()) {
// 1) Канонизируем login через solana_users (NOCASE) // 1) Канонизируем login через current users слой (NOCASE)
String canonicalLogin = findCanonicalLogin(c, loginAnyCase); String canonicalLogin = findCanonicalLogin(c, loginAnyCase);
if (canonicalLogin == null) { if (canonicalLogin == null) {
return NetExceptionResponseFactory.error( return NetExceptionResponseFactory.error(
@@ -17,8 +17,8 @@ import shine.db.entities.CurrentUserEntry;
/** /**
* GetSyncUserProfile server-to-server профиль пользователя для межсерверной синхронизации. * GetSyncUserProfile server-to-server профиль пользователя для межсерверной синхронизации.
* Нужен, чтобы принимающий сервер мог создать локальные solana_users + blockchain_state * Нужен, чтобы принимающий сервер мог создать локальную runtime-проекцию пользователя
* без прямого запроса в Solana RPC. * и blockchain_state без прямого запроса в Solana RPC.
*/ */
public final class Net_GetSyncUserProfile_Handler implements JsonMessageHandler { public final class Net_GetSyncUserProfile_Handler implements JsonMessageHandler {
@@ -8,7 +8,7 @@ import server.logic.ws_protocol.JSON.messages.entyties.Net_AckSessionDelivery_Re
import server.logic.ws_protocol.JSON.messages.entyties.Net_AckSessionDelivery_Response; import server.logic.ws_protocol.JSON.messages.entyties.Net_AckSessionDelivery_Response;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory; import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes; import server.logic.ws_protocol.WireCodes;
import shine.db.dao.SignedMessagesV2DAO; import shine.db.dao.SignedMessagesDAO;
public class Net_AckSessionDelivery_Handler implements JsonMessageHandler { public class Net_AckSessionDelivery_Handler implements JsonMessageHandler {
@Override @Override
@@ -22,7 +22,7 @@ public class Net_AckSessionDelivery_Handler implements JsonMessageHandler {
} }
String messageKey = req.getMessageKey().trim(); String messageKey = req.getMessageKey().trim();
SignedMessagesV2DAO.getInstance().markDelivered(messageKey, ctx.getSessionId(), System.currentTimeMillis()); SignedMessagesDAO.getInstance().markDelivered(messageKey, ctx.getSessionId(), System.currentTimeMillis());
Net_AckSessionDelivery_Response resp = new Net_AckSessionDelivery_Response(); Net_AckSessionDelivery_Response resp = new Net_AckSessionDelivery_Response();
resp.setOp(req.getOp()); resp.setOp(req.getOp());
@@ -9,8 +9,8 @@ import server.logic.ws_protocol.JSON.messages.entyties.Net_DeleteConversation_Re
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory; import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes; import server.logic.ws_protocol.WireCodes;
import server.sync.DmFederationService; import server.sync.DmFederationService;
import shine.db.dao.SignedMessagesV2DAO; import shine.db.dao.SignedMessagesDAO;
import shine.db.entities.SignedMessageV2Entry; import shine.db.entities.SignedMessageEntry;
public class Net_DeleteConversation_Handler implements JsonMessageHandler { public class Net_DeleteConversation_Handler implements JsonMessageHandler {
@Override @Override
@@ -38,8 +38,8 @@ public class Net_DeleteConversation_Handler implements JsonMessageHandler {
return NetExceptionResponseFactory.error(req, status, code, "Сообщение не прошло проверку"); return NetExceptionResponseFactory.error(req, status, code, "Сообщение не прошло проверку");
} }
SignedMessageV2Entry entry = SignedMessagesCore.toEntry(block, "DeleteConversation", null); SignedMessageEntry entry = SignedMessagesCore.toEntry(block, "DeleteConversation", null);
SignedMessagesV2DAO.ApplyStatus status = SignedMessagesV2DAO.getInstance().applyDeleteConversation(entry); SignedMessagesDAO.ApplyStatus status = SignedMessagesDAO.getInstance().applyDeleteConversation(entry);
SignedMessagesRealtime.DeliveryCounters counters = new SignedMessagesRealtime.DeliveryCounters(); SignedMessagesRealtime.DeliveryCounters counters = new SignedMessagesRealtime.DeliveryCounters();
if (status.applied()) { if (status.applied()) {
@@ -9,8 +9,8 @@ import server.logic.ws_protocol.JSON.messages.entyties.Net_DeleteMessage_Respons
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory; import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes; import server.logic.ws_protocol.WireCodes;
import server.sync.DmFederationService; import server.sync.DmFederationService;
import shine.db.dao.SignedMessagesV2DAO; import shine.db.dao.SignedMessagesDAO;
import shine.db.entities.SignedMessageV2Entry; import shine.db.entities.SignedMessageEntry;
public class Net_DeleteMessage_Handler implements JsonMessageHandler { public class Net_DeleteMessage_Handler implements JsonMessageHandler {
@Override @Override
@@ -38,8 +38,8 @@ public class Net_DeleteMessage_Handler implements JsonMessageHandler {
return NetExceptionResponseFactory.error(req, status, code, "Сообщение не прошло проверку"); return NetExceptionResponseFactory.error(req, status, code, "Сообщение не прошло проверку");
} }
SignedMessageV2Entry entry = SignedMessagesCore.toEntry(block, "DeleteMessage", null); SignedMessageEntry entry = SignedMessagesCore.toEntry(block, "DeleteMessage", null);
SignedMessagesV2DAO.ApplyStatus status = SignedMessagesV2DAO.getInstance().applyDeleteMessage(entry); SignedMessagesDAO.ApplyStatus status = SignedMessagesDAO.getInstance().applyDeleteMessage(entry);
SignedMessagesRealtime.DeliveryCounters counters = new SignedMessagesRealtime.DeliveryCounters(); SignedMessagesRealtime.DeliveryCounters counters = new SignedMessagesRealtime.DeliveryCounters();
if (status.applied()) { if (status.applied()) {
@@ -10,8 +10,8 @@ import server.logic.ws_protocol.JSON.messages.entyties.Net_GetDirectMessages_Req
import server.logic.ws_protocol.JSON.messages.entyties.Net_GetDirectMessages_Response; import server.logic.ws_protocol.JSON.messages.entyties.Net_GetDirectMessages_Response;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory; import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes; import server.logic.ws_protocol.WireCodes;
import shine.db.dao.SignedMessagesV2DAO; import shine.db.dao.SignedMessagesDAO;
import shine.db.entities.SignedMessageV2Entry; import shine.db.entities.SignedMessageEntry;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
@@ -43,7 +43,7 @@ public class Net_GetDirectMessages_Handler implements JsonMessageHandler {
String beforeMessageKey = req.getBeforeMessageKey() == null ? "" : req.getBeforeMessageKey().trim(); String beforeMessageKey = req.getBeforeMessageKey() == null ? "" : req.getBeforeMessageKey().trim();
try { try {
List<SignedMessageV2Entry> page = SignedMessagesV2DAO.getInstance().listConversationPage( List<SignedMessageEntry> page = SignedMessagesDAO.getInstance().listConversationPage(
login, login,
peerLogin, peerLogin,
beforeTimeMs, beforeTimeMs,
@@ -66,7 +66,7 @@ public class Net_GetDirectMessages_Handler implements JsonMessageHandler {
resp.setHasMore(hasMore); resp.setHasMore(hasMore);
List<Net_GetDirectMessages_Response.MessageItem> items = new ArrayList<>(); List<Net_GetDirectMessages_Response.MessageItem> items = new ArrayList<>();
for (SignedMessageV2Entry entry : page) { for (SignedMessageEntry entry : page) {
Net_GetDirectMessages_Response.MessageItem item = new Net_GetDirectMessages_Response.MessageItem(); Net_GetDirectMessages_Response.MessageItem item = new Net_GetDirectMessages_Response.MessageItem();
item.setMessageKey(entry.getMessageKey()); item.setMessageKey(entry.getMessageKey());
item.setBaseKey(entry.getBaseKey()); item.setBaseKey(entry.getBaseKey());
@@ -8,8 +8,8 @@ import server.logic.ws_protocol.JSON.messages.entyties.Net_ReceiveIncomingMessag
import server.logic.ws_protocol.JSON.messages.entyties.Net_ReceiveIncomingMessage_Response; import server.logic.ws_protocol.JSON.messages.entyties.Net_ReceiveIncomingMessage_Response;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory; import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes; import server.logic.ws_protocol.WireCodes;
import shine.db.dao.SignedMessagesV2DAO; import shine.db.dao.SignedMessagesDAO;
import shine.db.entities.SignedMessageV2Entry; import shine.db.entities.SignedMessageEntry;
import java.util.Base64; import java.util.Base64;
@@ -40,21 +40,21 @@ public class Net_ReceiveIncomingMessage_Handler implements JsonMessageHandler {
return NetExceptionResponseFactory.error(req, status, code, "Сообщение не прошло проверку"); return NetExceptionResponseFactory.error(req, status, code, "Сообщение не прошло проверку");
} }
final SignedMessageV2Entry entry; final SignedMessageEntry entry;
try { try {
entry = SignedMessagesCore.toEntry(incoming, "ReceiveIncomingMessage", null); entry = SignedMessagesCore.toEntry(incoming, "ReceiveIncomingMessage", null);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, ex.getMessage(), "Некорректный payload подтверждения"); return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, ex.getMessage(), "Некорректный payload подтверждения");
} }
SignedMessagesV2DAO.ApplyStatus status = incoming.isContentType() SignedMessagesDAO.ApplyStatus status = incoming.isContentType()
? SignedMessagesV2DAO.getInstance().upsertIncomingCopy(entry) ? SignedMessagesDAO.getInstance().upsertIncomingCopy(entry)
: SignedMessagesV2DAO.getInstance().insertIfAbsent(entry); : SignedMessagesDAO.getInstance().insertIfAbsent(entry);
SignedMessagesRealtime.DeliveryCounters counters = new SignedMessagesRealtime.DeliveryCounters(); SignedMessagesRealtime.DeliveryCounters counters = new SignedMessagesRealtime.DeliveryCounters();
if (status.applied()) { if (status.applied()) {
counters = SignedMessagesRealtime.deliverToRelevantSessions(entry, incoming); counters = SignedMessagesRealtime.deliverToRelevantSessions(entry, incoming);
} }
if (status == SignedMessagesV2DAO.ApplyStatus.BLOCKED_BY_CONVERSATION_TOMBSTONE) { if (status == SignedMessagesDAO.ApplyStatus.BLOCKED_BY_CONVERSATION_TOMBSTONE) {
bounceConversationDeleteIfKnown(incoming.fromLogin, incoming.toLogin); bounceConversationDeleteIfKnown(incoming.fromLogin, incoming.toLogin);
} }
@@ -74,7 +74,7 @@ public class Net_ReceiveIncomingMessage_Handler implements JsonMessageHandler {
} }
private void bounceConversationDeleteIfKnown(String fromLogin, String toLogin) throws Exception { private void bounceConversationDeleteIfKnown(String fromLogin, String toLogin) throws Exception {
SignedMessageV2Entry tombstone = SignedMessagesV2DAO.getInstance().getLatestConversationDelete(fromLogin, toLogin); SignedMessageEntry tombstone = SignedMessagesDAO.getInstance().getLatestConversationDelete(fromLogin, toLogin);
if (tombstone == null || tombstone.getRawBlock() == null || tombstone.getRawBlock().length == 0) return; if (tombstone == null || tombstone.getRawBlock() == null || tombstone.getRawBlock().length == 0) return;
server.sync.DmFederationService.fanOutDeleteConversation( server.sync.DmFederationService.fanOutDeleteConversation(
tombstone.getFromLogin(), tombstone.getFromLogin(),
@@ -9,8 +9,8 @@ import server.logic.ws_protocol.JSON.messages.entyties.Net_SendMessagePair_Respo
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory; import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes; import server.logic.ws_protocol.WireCodes;
import server.sync.DmFederationService; import server.sync.DmFederationService;
import shine.db.dao.SignedMessagesV2DAO; import shine.db.dao.SignedMessagesDAO;
import shine.db.entities.SignedMessageV2Entry; import shine.db.entities.SignedMessageEntry;
import java.util.Base64; import java.util.Base64;
@@ -41,8 +41,8 @@ public class Net_SendMessagePair_Handler implements JsonMessageHandler {
return NetExceptionResponseFactory.error(req, status, code, "Сообщение не прошло проверку"); return NetExceptionResponseFactory.error(req, status, code, "Сообщение не прошло проверку");
} }
SignedMessageV2Entry incomingEntry; SignedMessageEntry incomingEntry;
SignedMessageV2Entry outgoingEntry; SignedMessageEntry outgoingEntry;
try { try {
String sourceApi = "SendMessagePair"; String sourceApi = "SendMessagePair";
String originSessionId = (ctx != null && !isBlank(ctx.getSessionId())) ? ctx.getSessionId() : null; String originSessionId = (ctx != null && !isBlank(ctx.getSessionId())) ? ctx.getSessionId() : null;
@@ -52,15 +52,15 @@ public class Net_SendMessagePair_Handler implements JsonMessageHandler {
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, ex.getMessage(), "Некорректный payload подтверждения"); return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, ex.getMessage(), "Некорректный payload подтверждения");
} }
SignedMessagesV2DAO.ApplyStatus pairStatus; SignedMessagesDAO.ApplyStatus pairStatus;
if (incoming.isContentType()) { if (incoming.isContentType()) {
pairStatus = SignedMessagesV2DAO.getInstance().upsertContentPair( pairStatus = SignedMessagesDAO.getInstance().upsertContentPair(
incomingEntry, outgoingEntry incomingEntry, outgoingEntry
); );
} else { } else {
pairStatus = SignedMessagesV2DAO.getInstance().insertPairBothOrNothing(incomingEntry, outgoingEntry) pairStatus = SignedMessagesDAO.getInstance().insertPairBothOrNothing(incomingEntry, outgoingEntry)
? SignedMessagesV2DAO.ApplyStatus.APPLIED ? SignedMessagesDAO.ApplyStatus.APPLIED
: SignedMessagesV2DAO.ApplyStatus.DUPLICATE_OR_OLDER; : SignedMessagesDAO.ApplyStatus.DUPLICATE_OR_OLDER;
} }
SignedMessagesRealtime.DeliveryCounters inCounters = new SignedMessagesRealtime.DeliveryCounters(); SignedMessagesRealtime.DeliveryCounters inCounters = new SignedMessagesRealtime.DeliveryCounters();
@@ -77,7 +77,7 @@ public class Net_SendMessagePair_Handler implements JsonMessageHandler {
outCounters = SignedMessagesRealtime.deliverToRelevantSessions(outgoingEntry, outgoing, excludeSessionId); outCounters = SignedMessagesRealtime.deliverToRelevantSessions(outgoingEntry, outgoing, excludeSessionId);
} }
if (pairStatus == SignedMessagesV2DAO.ApplyStatus.BLOCKED_BY_CONVERSATION_TOMBSTONE) { if (pairStatus == SignedMessagesDAO.ApplyStatus.BLOCKED_BY_CONVERSATION_TOMBSTONE) {
bounceConversationDeleteIfKnown(incoming.fromLogin, incoming.toLogin); bounceConversationDeleteIfKnown(incoming.fromLogin, incoming.toLogin);
} }
@@ -107,7 +107,7 @@ public class Net_SendMessagePair_Handler implements JsonMessageHandler {
} }
private void bounceConversationDeleteIfKnown(String fromLogin, String toLogin) throws Exception { private void bounceConversationDeleteIfKnown(String fromLogin, String toLogin) throws Exception {
SignedMessageV2Entry tombstone = SignedMessagesV2DAO.getInstance().getLatestConversationDelete(fromLogin, toLogin); SignedMessageEntry tombstone = SignedMessagesDAO.getInstance().getLatestConversationDelete(fromLogin, toLogin);
if (tombstone == null || tombstone.getRawBlock() == null || tombstone.getRawBlock().length == 0) return; if (tombstone == null || tombstone.getRawBlock() == null || tombstone.getRawBlock().length == 0) return;
DmFederationService.fanOutDeleteConversation( DmFederationService.fanOutDeleteConversation(
tombstone.getFromLogin(), tombstone.getFromLogin(),
@@ -74,7 +74,7 @@ public class Net_SendSignal_Handler implements JsonMessageHandler {
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "TIME_SKEW", "Время клиента отличается от сервера более чем на 30 секунд"); return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "TIME_SKEW", "Время клиента отличается от сервера более чем на 30 секунд");
} }
CurrentUserEntry senderUser = ctx.getSolanaUser(); CurrentUserEntry senderUser = ctx.getCurrentUser();
if (senderUser == null || senderUser.getClientKey() == null || senderUser.getClientKey().isBlank()) { if (senderUser == null || senderUser.getClientKey() == null || senderUser.getClientKey().isBlank()) {
return NetExceptionResponseFactory.error(req, WireCodes.Status.SERVER_DATA_ERROR, "NO_CLIENT_KEY", "Для пользователя не найден client key"); return NetExceptionResponseFactory.error(req, WireCodes.Status.SERVER_DATA_ERROR, "NO_CLIENT_KEY", "Для пользователя не найден client key");
} }
@@ -1,7 +1,7 @@
package server.logic.ws_protocol.JSON.messages; package server.logic.ws_protocol.JSON.messages;
import shine.db.dao.CurrentUsersDAO; import shine.db.dao.CurrentUsersDAO;
import shine.db.entities.SignedMessageV2Entry; import shine.db.entities.SignedMessageEntry;
import shine.db.entities.CurrentUserEntry; import shine.db.entities.CurrentUserEntry;
import utils.crypto.Ed25519Util; import utils.crypto.Ed25519Util;
@@ -96,11 +96,11 @@ final class SignedMessagesCore {
} }
} }
static SignedMessageV2Entry toEntry(SignedMessageBlock block, String sourceApi, String originSessionId) { static SignedMessageEntry toEntry(SignedMessageBlock block, String sourceApi, String originSessionId) {
String baseKey = SignedMessageKeys.baseKey(block.toLogin, block.fromLogin, block.timeMs, block.nonce); String baseKey = SignedMessageKeys.baseKey(block.toLogin, block.fromLogin, block.timeMs, block.nonce);
String messageKey = SignedMessageKeys.messageKey(block.toLogin, block.fromLogin, block.timeMs, block.nonce, block.messageType); String messageKey = SignedMessageKeys.messageKey(block.toLogin, block.fromLogin, block.timeMs, block.nonce, block.messageType);
SignedMessageV2Entry entry = new SignedMessageV2Entry(); SignedMessageEntry entry = new SignedMessageEntry();
entry.setMessageKey(messageKey); entry.setMessageKey(messageKey);
entry.setBaseKey(baseKey); entry.setBaseKey(baseKey);
entry.setTargetLogin(primaryTargetLogin(block)); entry.setTargetLogin(primaryTargetLogin(block));
@@ -9,9 +9,9 @@ import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.push.WebPushSender; import server.logic.ws_protocol.JSON.push.WebPushSender;
import server.logic.ws_protocol.JSON.push.WsEventSender; import server.logic.ws_protocol.JSON.push.WsEventSender;
import shine.db.dao.ActiveSessionsDAO; import shine.db.dao.ActiveSessionsDAO;
import shine.db.dao.SignedMessagesV2DAO; import shine.db.dao.SignedMessagesDAO;
import shine.db.entities.ActiveSessionEntry; import shine.db.entities.ActiveSessionEntry;
import shine.db.entities.SignedMessageV2Entry; import shine.db.entities.SignedMessageEntry;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
@@ -37,12 +37,12 @@ public final class SignedMessagesRealtime {
private SignedMessagesRealtime() {} private SignedMessagesRealtime() {}
static DeliveryCounters deliverToRelevantSessions(SignedMessageV2Entry message, SignedMessageBlock block) throws Exception { static DeliveryCounters deliverToRelevantSessions(SignedMessageEntry message, SignedMessageBlock block) throws Exception {
return deliverToRelevantSessions(message, block, null); return deliverToRelevantSessions(message, block, null);
} }
static DeliveryCounters deliverToRelevantSessions( static DeliveryCounters deliverToRelevantSessions(
SignedMessageV2Entry message, SignedMessageEntry message,
SignedMessageBlock block, SignedMessageBlock block,
String excludeSessionId String excludeSessionId
) throws Exception { ) throws Exception {
@@ -58,7 +58,7 @@ public final class SignedMessagesRealtime {
} }
sessionIdsToTrack.add(sessionId); sessionIdsToTrack.add(sessionId);
} }
SignedMessagesV2DAO.getInstance().ensureDeliveryRows(message.getMessageKey(), sessionIdsToTrack, now); SignedMessagesDAO.getInstance().ensureDeliveryRows(message.getMessageKey(), sessionIdsToTrack, now);
for (ActiveSessionEntry s : sessions) { for (ActiveSessionEntry s : sessions) {
String sessionId = s.getSessionId(); String sessionId = s.getSessionId();
if (excludeSessionId != null && excludeSessionId.equals(sessionId)) { if (excludeSessionId != null && excludeSessionId.equals(sessionId)) {
@@ -97,9 +97,9 @@ public final class SignedMessagesRealtime {
private static void dispatchPendingForSession(String login, String sessionId) { private static void dispatchPendingForSession(String login, String sessionId) {
try { try {
List<SignedMessageV2Entry> pending = SignedMessagesV2DAO.getInstance() List<SignedMessageEntry> pending = SignedMessagesDAO.getInstance()
.listPendingForSession(login, sessionId); .listPendingForSession(login, sessionId);
for (SignedMessageV2Entry e : pending) { for (SignedMessageEntry e : pending) {
sendEventToSessionIfOnline(sessionId, login, e, true); sendEventToSessionIfOnline(sessionId, login, e, true);
} }
} catch (Exception e) { } catch (Exception e) {
@@ -110,7 +110,7 @@ public final class SignedMessagesRealtime {
private static boolean sendEventToSessionIfOnline( private static boolean sendEventToSessionIfOnline(
String sessionId, String sessionId,
String actualTargetLogin, String actualTargetLogin,
SignedMessageV2Entry message, SignedMessageEntry message,
boolean backlog boolean backlog
) { ) {
ConnectionContext targetCtx = ActiveConnectionsRegistry.getInstance().getBySessionId(sessionId); ConnectionContext targetCtx = ActiveConnectionsRegistry.getInstance().getBySessionId(sessionId);
@@ -134,14 +134,14 @@ public final class SignedMessagesRealtime {
return WsEventSender.sendEvent(targetCtx, "SignedMessageArrived", message.getMessageKey(), payload); return WsEventSender.sendEvent(targetCtx, "SignedMessageArrived", message.getMessageKey(), payload);
} }
private static boolean shouldPushNewIncomingMessage(String targetLogin, SignedMessageV2Entry message, SignedMessageBlock block) { private static boolean shouldPushNewIncomingMessage(String targetLogin, SignedMessageEntry message, SignedMessageBlock block) {
if (block == null) return false; if (block == null) return false;
if (message.getMessageType() != SignedMessageBlock.TYPE_INCOMING_TEXT) return false; if (message.getMessageType() != SignedMessageBlock.TYPE_INCOMING_TEXT) return false;
if (!targetLogin.equalsIgnoreCase(message.getToLogin())) return false; if (!targetLogin.equalsIgnoreCase(message.getToLogin())) return false;
return block.revisionTimeMs == 0; return block.revisionTimeMs == 0;
} }
private static boolean pushNewMessageNotification(ActiveSessionEntry session, SignedMessageV2Entry message) { private static boolean pushNewMessageNotification(ActiveSessionEntry session, SignedMessageEntry message) {
try { try {
if (session == null) return false; if (session == null) return false;
if (isBlank(session.getPushEndpoint()) || isBlank(session.getPushP256dhKey()) || isBlank(session.getPushAuthKey())) { if (isBlank(session.getPushEndpoint()) || isBlank(session.getPushP256dhKey()) || isBlank(session.getPushAuthKey())) {
@@ -160,7 +160,7 @@ public final class SignedMessagesRealtime {
} }
} }
private static List<String> targetLoginsForMessage(SignedMessageV2Entry message) { private static List<String> targetLoginsForMessage(SignedMessageEntry message) {
Set<String> out = new LinkedHashSet<>(); Set<String> out = new LinkedHashSet<>();
int type = message.getMessageType(); int type = message.getMessageType();
if (type == SignedMessageBlock.TYPE_INCOMING_TEXT || type == SignedMessageBlock.TYPE_READ_INCOMING) { if (type == SignedMessageBlock.TYPE_INCOMING_TEXT || type == SignedMessageBlock.TYPE_READ_INCOMING) {
@@ -57,7 +57,7 @@ public final class WsConnectionUtils {
final String sessionId = safeString(ctx.getSessionId()); final String sessionId = safeString(ctx.getSessionId());
final int authStatus = safeAuthStatus(ctx); final int authStatus = safeAuthStatus(ctx);
final CurrentUserEntry user = ctx.getSolanaUser(); final CurrentUserEntry user = ctx.getCurrentUser();
final String login = (user != null ? safeString(user.getLogin()) : ""); final String login = (user != null ? safeString(user.getLogin()) : "");
final String activeSessionId = final String activeSessionId =
@@ -20,7 +20,7 @@ solana.users.sync.pollIntervalSeconds=300
# false - брать профиль пользователя напрямую из Solana PDA (обычный режим). # false - брать профиль пользователя напрямую из Solana PDA (обычный режим).
# true - не ходить в Solana RPC, а запрашивать у сервера-партнёра специальный # true - не ходить в Solana RPC, а запрашивать у сервера-партнёра специальный
# sync-профиль пользователя и по нему локально создавать # sync-профиль пользователя и по нему локально создавать
# solana_users + blockchain_state. # current user runtime-проекцию + blockchain_state.
# Эта настройка нужна как временный обход лимитов Solana RPC (например 429), # Эта настройка нужна как временный обход лимитов Solana RPC (например 429),
# чтобы чистый сервер мог восстановить цепочки от партнёра без зависимости # чтобы чистый сервер мог восстановить цепочки от партнёра без зависимости
# от внешнего Solana endpoint. # от внешнего Solana endpoint.
+1 -1
View File
@@ -1,2 +1,2 @@
client.version=1.2.354 client.version=1.2.354
server.version=1.2.335 server.version=1.2.336
+6 -8
View File
@@ -2,13 +2,12 @@
Этот файл описывает раздел API, связанный с проверкой наличия пользователя на сервере и dev/test операциями. Этот файл описывает раздел API, связанный с проверкой наличия пользователя на сервере и dev/test операциями.
Сейчас здесь три метода: Сейчас здесь два метода:
- `AddUser` — операция отключена (регистрация только через Solana);
- `GetUser` — временная серверная проверка существования пользователя и чтение его базовых данных; - `GetUser` — временная серверная проверка существования пользователя и чтение его базовых данных;
- `SearchUsers` — dev/test поиск логинов по префиксу. - `SearchUsers` — dev/test поиск логинов по префиксу.
Регистрация выполняется через Solana (`shine_users`). Сервер при входе может лениво импортировать пользователя из Solana PDA в локальную БД, если записи ещё нет. Регистрация выполняется только через Solana. Старый серверный `AddUser` оставлен лишь как legacy-ответ `410 / ADD_USER_DISABLED` для старых клиентов и больше не считается частью актуального flow.
## Статус документа ## Статус документа
@@ -18,11 +17,11 @@
--- ---
## 1. Операция `AddUser` ## Legacy-операция `AddUser`
### Назначение ### Назначение
Операция отключена. Используется только как явный ответ клиентам старых версий. Операция удалена из текущего клиентского flow. Сервер сохраняет только совместимый ответ для клиентов старых версий.
### Запрос ### Запрос
@@ -62,7 +61,7 @@
--- ---
## 2. Операция `GetUser` ## 1. Операция `GetUser`
### Назначение ### Назначение
@@ -153,7 +152,7 @@
--- ---
## 3. Операция `SearchUsers` ## 2. Операция `SearchUsers`
### Назначение ### Назначение
@@ -195,7 +194,6 @@
## 4. Короткое резюме ## 4. Короткое резюме
- `AddUser` — отключен (`410 / ADD_USER_DISABLED`).
- `GetUser` — проверка существования пользователя на сервере. - `GetUser` — проверка существования пользователя на сервере.
- `SearchUsers` — временный поиск пользователей по префиксу. - `SearchUsers` — временный поиск пользователей по префиксу.
- Регистрация выполняется только через Solana. - Регистрация выполняется только через Solana.
+1 -1
View File
@@ -138,7 +138,7 @@ AUTH_CREATE_SESSION:{login}:{sessionKey}:{storagePwd}:{timeMs}:{authNonce}
Перед проверкой подписи сервер должен: Перед проверкой подписи сервер должен:
1. взять актуальный `solana_users.client_key`; 1. взять актуальный `solana_user_pda_current.client_key`;
2. сравнить его с `payload.clientKey`; 2. сравнить его с `payload.clientKey`;
3. только потом проверять подпись. 3. только потом проверять подпись.
+2 -2
View File
@@ -19,7 +19,7 @@
- `Ping` нужен для регулярной проверки, что соединение всё ещё живо; - `Ping` нужен для регулярной проверки, что соединение всё ещё живо;
- `GetServerInfo` нужен до авторизации и до работы с данными, чтобы клиент понял, что сервер доступен, и показал пользователю краткую карточку этого узла. - `GetServerInfo` нужен до авторизации и до работы с данными, чтобы клиент понял, что сервер доступен, и показал пользователю краткую карточку этого узла.
- `ListBlockchainHeads` нужен для сервер-сервер сверки: партнёр получает список heads по всем цепочкам, сравнивает его со своим состоянием и затем добирает недостающие блоки по диапазону. - `ListBlockchainHeads` нужен для сервер-сервер сверки: партнёр получает список heads по всем цепочкам, сравнивает его со своим состоянием и затем добирает недостающие блоки по диапазону.
- `GetSyncUserProfile` нужен для server-to-server режима, когда принимающий сервер хочет создать у себя локальные `solana_users + blockchain_state` без прямого обращения в Solana. Это используется как временный обход ограничений внешнего Solana RPC. - `GetSyncUserProfile` нужен для server-to-server режима, когда принимающий сервер хочет создать у себя локальную runtime-проекцию пользователя и `blockchain_state` без прямого обращения в Solana.
- `SendSignal` нужен для доверенных межсессионных команд одного пользователя. Первое практическое применение — `remote AddBlock via homeserver session`, но формат задуман как общий transport на вырост. - `SendSignal` нужен для доверенных межсессионных команд одного пользователя. Первое практическое применение — `remote AddBlock via homeserver session`, но формат задуман как общий transport на вырост.
Ниже сначала описаны назначение методов, затем точные форматы запросов и ответов. Ниже сначала описаны назначение методов, затем точные форматы запросов и ответов.
@@ -216,7 +216,7 @@
- `clientKey` - `clientKey`
- `blockchainSizeLimitBytes` - `blockchainSizeLimitBytes`
После этого принимающий сервер может локально создать записи в `solana_users` и `blockchain_state`, а затем уже докачивать блоки через `GetBlockchainBlock`. После этого принимающий сервер может локально создать runtime-проекцию пользователя и запись в `blockchain_state`, а затем уже докачивать блоки через `GetBlockchainBlock`.
Этот запрос доступен без авторизации и предназначен именно для server-to-server sync. Этот запрос доступен без авторизации и предназначен именно для server-to-server sync.
-1
View File
@@ -12,7 +12,6 @@
| Операция | Раздел документации | Кратко | | Операция | Раздел документации | Кратко |
| --- | --- | --- | | --- | --- | --- |
| `AddUser` | `01_User_Registration_API.md` | отключено (`410 / ADD_USER_DISABLED`) |
| `GetUser` | `01_User_Registration_API.md` | чтение/проверка пользователя + server-состояние его блокчейна | | `GetUser` | `01_User_Registration_API.md` | чтение/проверка пользователя + server-состояние его блокчейна |
| `SearchUsers` | `01_User_Registration_API.md` | поиск логинов по префиксу | | `SearchUsers` | `01_User_Registration_API.md` | поиск логинов по префиксу |
| `TestGetFreeAvatarQuota` | `14_Test_Free_Avatar_Upload_API.md` | временный тестовый просмотр остатка бесплатных загрузок аватара | | `TestGetFreeAvatarQuota` | `14_Test_Free_Avatar_Upload_API.md` | временный тестовый просмотр остатка бесплатных загрузок аватара |
+5 -5
View File
@@ -60,7 +60,7 @@
- `ListBlockchainHeads` — список heads всех локальных цепочек партнёра; - `ListBlockchainHeads` — список heads всех локальных цепочек партнёра;
- `GetBlockchainBlock` — чтение одного конкретного блока партнёра; - `GetBlockchainBlock` — чтение одного конкретного блока партнёра;
- `GetSyncUserProfile` — минимальный профиль пользователя для локального создания `solana_users + blockchain_state` без обращения в Solana RPC. - `GetSyncUserProfile` — минимальный профиль пользователя для локального создания runtime-проекции пользователя и `blockchain_state` без обращения в Solana RPC.
### 4.3 Как сейчас работает periodic sync ### 4.3 Как сейчас работает periodic sync
@@ -73,7 +73,7 @@
- локальное состояние; - локальное состояние;
3. если локальная цепочка слабее, сервер по одному блоку вызывает `GetBlockchainBlock`; 3. если локальная цепочка слабее, сервер по одному блоку вызывает `GetBlockchainBlock`;
4. каждый скачанный блок локально применяется через существующий `AddBlock`; 4. каждый скачанный блок локально применяется через существующий `AddBlock`;
5. если у сервера ещё нет локальной записи пользователя/цепочки, перед этим подготавливается локальный `solana_users + blockchain_state`. 5. если у сервера ещё нет локальной записи пользователя/цепочки, перед этим подготавливается локальная runtime-проекция пользователя и `blockchain_state`.
6. если во время replay обнаруживается рассинхрон или на одинаковой высоте удалённая цепочка сильнее, запускается полный resync: 6. если во время replay обнаруживается рассинхрон или на одинаковой высоте удалённая цепочка сильнее, запускается полный resync:
- цепочка помечается in-memory как `resync in progress`; - цепочка помечается in-memory как `resync in progress`;
- создаётся marker-file в `data/`; - создаётся marker-file в `data/`;
@@ -110,7 +110,7 @@ Full resync запускается только тогда, когда:
Важно: Важно:
- full resync не делает умный rollback по одному блоку; - full resync не делает умный rollback по одному блоку;
- full resync не трогает DM-таблицы и `solana_users`; - full resync не трогает DM-таблицы и current users слой;
- висячие cross-chain ссылки считаются допустимым поведением системы. - висячие cross-chain ссылки считаются допустимым поведением системы.
### 4.5 Как работает обычный `AddBlock` и его recovery ### 4.5 Как работает обычный `AddBlock` и его recovery
@@ -150,7 +150,7 @@ Full resync запускается только тогда, когда:
- из `blockchainName` извлекался `login`; - из `blockchainName` извлекался `login`;
- сервер вызывал import пользователя из Solana PDA; - сервер вызывал import пользователя из Solana PDA;
- по данным PDA локально создавались `solana_users + blockchain_state`. - по данным PDA локально создавались runtime-проекция пользователя и `blockchain_state`.
На практике это упёрлось в ограничение внешнего Solana RPC: при чистом старте и массовой подтяжке чужих цепочек сервер мог получать `HTTP 429`. На практике это упёрлось в ограничение внешнего Solana RPC: при чистом старте и массовой подтяжке чужих цепочек сервер мог получать `HTTP 429`.
@@ -159,7 +159,7 @@ Full resync запускается только тогда, когда:
- настройка `sync.importUserProfileFromPartner.enabled=true` - настройка `sync.importUserProfileFromPartner.enabled=true`
- в этом режиме сервер **не ходит в Solana RPC** для создания локальной цепочки во время sync; - в этом режиме сервер **не ходит в Solana RPC** для создания локальной цепочки во время sync;
- вместо этого он запрашивает у сервера-партнёра `GetSyncUserProfile` и создаёт локальную запись по данным партнёра. - вместо этого он запрашивает у сервера-партнёра `GetSyncUserProfile` и создаёт локальную запись по данным партнёра.
- если локальный `solana_users` уже существует, sync восстанавливает только `blockchain_state` и не трогает identity-слой. - если локальная runtime-проекция пользователя уже существует, sync восстанавливает только `blockchain_state` и не трогает user-layer.
Это временная практическая заплатка, чтобы clean-start sync не зависел от rate limit внешнего Solana endpoint. Это временная практическая заплатка, чтобы clean-start sync не зависел от rate limit внешнего Solana endpoint.
@@ -103,7 +103,7 @@
Для DM-проверки сервер использует: Для DM-проверки сервер использует:
- локальную `solana_users` как кэш; - локальную runtime-проекцию пользователей как кэш;
- Solana PDA как источник истины по `clientKey` и `access_servers`. - Solana PDA как источник истины по `clientKey` и `access_servers`.
Если нужного пользователя нет локально, сервер обязан попытаться lazy-import из Solana PDA: Если нужного пользователя нет локально, сервер обязан попытаться lazy-import из Solana PDA:
+1 -5
View File
@@ -9,8 +9,4 @@ shine.db.DatabaseInitializer — проверяет наличие `db_schema_ve
shine.db.entities.* — POJO-модели строк таблиц (без логики, только поля/геттеры/сеттеры + иногда удобные методы вроде getClientKeyByte()). shine.db.entities.* — POJO-модели строк таблиц (без логики, только поля/геттеры/сеттеры + иногда удобные методы вроде getClientKeyByte()).
shine.db.dao.* — DAO по таблицам: ActiveSessionsDAO, CurrentUsersDAO, UserParamsDAO, IpGeoCacheDAO, BlockchainStateDAO, BlocksDAO; плюс сервисные DAO: shine.db.dao.* — DAO по таблицам: ActiveSessionsDAO, CurrentUsersDAO, UserParamsDAO, IpGeoCacheDAO, BlockchainStateDAO, BlocksDAO, SignedMessagesDAO; плюс сервисные DAO под recovery/resync.
UserCreateDAO — атомарная регистрация пользователя в транзакции (BEGIN IMMEDIATE + rollback/commit).
// Временное runtime-решение, позволяющее регистрировать новых пользователей
// атомарно и добавляющее запись в runtime-таблицы сервера.
+1 -1
View File
@@ -42,7 +42,7 @@ auth/ — авторизация и сессии
blockchain/ — AddBlock blockchain/ — AddBlock
tempToTest/ — AddUser (временный, потом уйдёт в блокчейн-логику) tempToTest/ — временные dev/test операции (`GetUser`, `SearchUsers`)
ConnectionContext ConnectionContext
Состояние одного WebSocket-подключения (login, session, authStatus). Состояние одного WebSocket-подключения (login, session, authStatus).
+6 -37
View File
@@ -1037,46 +1037,15 @@ export class AuthService {
} }
async registerUser(login, password) { async registerUser(login, password) {
const cleanLogin = (login || '').trim(); void login;
if (!cleanLogin) throw new Error('Введите логин'); void password;
throw new Error('Серверная регистрация через AddUser удалена. Используйте регистрацию через Solana.');
const isFree = await this.ensureLoginFree(cleanLogin);
if (!isFree) throw new Error('Этот логин уже занят');
const keyBundle = await this.derivePasswordKeyBundle(cleanLogin, password);
const clientPair = keyBundle?.clientPair;
const addResp = await this.ws.request('AddUser', {
login: cleanLogin,
blockchainName: `${cleanLogin}-${BCH_SUFFIX}`,
solanaKey: clientPair.publicKeyB64,
blockchainKey: keyBundle.blockchainPair.publicKeyB64,
clientKey: clientPair.publicKeyB64,
bchLimit: 1000000,
});
if (addResp.status !== 200) throw opError('AddUser', addResp);
const session = await this.createAuthSession(cleanLogin, keyBundle);
return { ...session, keyBundle };
} }
async registerUserWithKeyBundle(login, keyBundle) { async registerUserWithKeyBundle(login, keyBundle) {
const cleanLogin = (login || '').trim(); void login;
if (!cleanLogin) throw new Error('Введите логин'); void keyBundle;
const clientPair = keyBundle?.clientPair; throw new Error('Серверная регистрация через AddUser удалена. Используйте регистрацию через Solana.');
const addResp = await this.ws.request('AddUser', {
login: cleanLogin,
blockchainName: `${cleanLogin}-${BCH_SUFFIX}`,
solanaKey: clientPair.publicKeyB64,
blockchainKey: keyBundle.blockchainPair.publicKeyB64,
clientKey: clientPair.publicKeyB64,
bchLimit: 1000000,
});
if (addResp.status !== 200) throw opError('AddUser', addResp);
const session = await this.createAuthSession(cleanLogin, keyBundle);
return { ...session, keyBundle };
} }
async createSessionForExistingUser(login, password) { async createSessionForExistingUser(login, password) {