17 12 25
Ещё промежуточный комит верии - не работает :) 3
This commit is contained in:
parent
29c6e5a0f6
commit
45a862b11f
@ -1,12 +1,12 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.blockchain;
|
||||
|
||||
import blockchain_new.BchBlockEntry_new;
|
||||
import shine.db.SqliteDbController;
|
||||
import shine.db.dao.BlockchainStateDAO;
|
||||
import shine.db.dao.SolanaUsersDAO;
|
||||
import shine.db.entities.BlockchainStateEntry;
|
||||
import shine.db.entities.SolanaUserEntry;
|
||||
import utils.files.FileStoreUtil;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Base64;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@ -34,14 +34,23 @@ public final class BlockchainStateService_new {
|
||||
public static BlockchainStateService_new getInstance() { return INSTANCE; }
|
||||
private BlockchainStateService_new() {}
|
||||
|
||||
// --- MVP: локи в памяти по blockchainId ---
|
||||
// ===== locks per blockchainId (MVP: один сервер) =====
|
||||
private static final ConcurrentHashMap<Long, ReentrantLock> LOCKS = new ConcurrentHashMap<>();
|
||||
|
||||
private static ReentrantLock lockFor(long blockchainId) {
|
||||
return LOCKS.computeIfAbsent(blockchainId, id -> new ReentrantLock());
|
||||
}
|
||||
|
||||
public Result addBlockAtomically(
|
||||
// ===== constants =====
|
||||
private static final String ZERO64 = "0".repeat(64);
|
||||
|
||||
// MVP: “заглавный блок”
|
||||
// (пока без парсинга тела, просто по номеру)
|
||||
private static boolean isHeaderBlock(int globalNumber, int lineNumber) {
|
||||
return globalNumber == 0 && lineNumber == 0;
|
||||
}
|
||||
|
||||
public Result addBlock(
|
||||
String login,
|
||||
long blockchainId,
|
||||
int globalNumber,
|
||||
@ -78,79 +87,141 @@ public final class BlockchainStateService_new {
|
||||
|
||||
ReentrantLock lock = lockFor(blockchainId);
|
||||
lock.lock();
|
||||
try (Connection conn = SqliteDbController.getInstance().getConnection()) {
|
||||
|
||||
// Транзакция — норм, но БЕЗ "BEGIN IMMEDIATE".
|
||||
boolean oldAuto = conn.getAutoCommit();
|
||||
conn.setAutoCommit(false);
|
||||
|
||||
try {
|
||||
BlockchainStateEntry state =
|
||||
BlockchainStateDAO.getInstance().getByBlockchainId(conn, blockchainId);
|
||||
BlockchainStateEntry state = BlockchainStateDAO.getInstance().getByBlockchainId(blockchainId);
|
||||
|
||||
// ===== GENESIS ветка: state ещё нет =====
|
||||
if (state == null) {
|
||||
conn.rollback();
|
||||
// разрешаем только заглавный блок
|
||||
if (!isHeaderBlock(globalNumber, block.lineNumber)) {
|
||||
return new Result(404, "UNKNOWN_BLOCKCHAIN", null, lineIndex);
|
||||
}
|
||||
|
||||
// создаём первичное состояние (last_global=-1, hash=ZERO64, lines=0/ZERO64)
|
||||
state = createInitialStateFromUser(login, blockchainId);
|
||||
if (state == null) {
|
||||
// нет такого юзера / не его bchId
|
||||
return new Result(404, "UNKNOWN_BLOCKCHAIN", null, lineIndex);
|
||||
}
|
||||
|
||||
// сохраняем стартовую строку
|
||||
BlockchainStateDAO.getInstance().upsert(state);
|
||||
}
|
||||
|
||||
// 1) защита от подмены логина
|
||||
if (!login.equals(state.getUserLogin())) {
|
||||
conn.rollback();
|
||||
return new Result(403, "LOGIN_MISMATCH", state, lineIndex);
|
||||
}
|
||||
|
||||
// 2) expected global: last_global + 1 (у нас last_global стартует -1)
|
||||
int expectedGlobal = state.getLastGlobalNumber() + 1;
|
||||
if (globalNumber != expectedGlobal) {
|
||||
conn.rollback();
|
||||
return new Result(409, "OUT_OF_SEQUENCE_GLOBAL", state, lineIndex);
|
||||
}
|
||||
|
||||
// 3) prev global hash
|
||||
String dbPrevGlobalHash = nn(state.getLastGlobalHash());
|
||||
if (!eqHash(prevGlobalHashHex, dbPrevGlobalHash)) {
|
||||
conn.rollback();
|
||||
return new Result(409, "GLOBAL_HASH_MISMATCH", state, lineIndex);
|
||||
}
|
||||
|
||||
// 4) lineNumber
|
||||
// Нормально: первый “обычный” блок по линии должен быть lineNumber=1 при lastLine=0
|
||||
// Исключение: заглавный блок имеет lineNumber=0
|
||||
int expectedLineNumber = state.getLastLineNumber(lineIndex) + 1;
|
||||
boolean header = isHeaderBlock(globalNumber, block.lineNumber);
|
||||
|
||||
if (!header) {
|
||||
if (block.lineNumber != expectedLineNumber) {
|
||||
conn.rollback();
|
||||
return new Result(409, "OUT_OF_SEQUENCE_LINE", state, lineIndex);
|
||||
}
|
||||
} else {
|
||||
// заглавный блок допускаем только если текущий lastLineNumber == 0 и пришёл 0
|
||||
if (state.getLastLineNumber(lineIndex) != 0 || block.lineNumber != 0) {
|
||||
return new Result(409, "BAD_HEADER_LINE_NUMBER", state, lineIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// prevLineHash (пока просто читаем, дальше пригодится для крипто-проверки)
|
||||
// 5) prevLineHash берём из БД (пока просто читаем)
|
||||
String dbPrevLineHashHex = nn(state.getLastLineHash(lineIndex));
|
||||
// (можешь позже сравнивать с тем, что внутри блока, если там есть prevLineHash)
|
||||
|
||||
// TODO crypto check (потом подключим)
|
||||
// 6) крипто-проверка (позже)
|
||||
// TODO:
|
||||
// - восстановить preimage
|
||||
// - sha256(preimage) == block.hash32
|
||||
// - Ed25519 verify signature
|
||||
// если не ок: return new Result(422, "CRYPTO_INVALID", state, lineIndex);
|
||||
|
||||
// 1) пишем в файл
|
||||
// 7) запись блока в файл
|
||||
FileStoreUtil.getInstance().addDataToBlockchain(blockchainId, block.toBytes());
|
||||
|
||||
// 2) обновляем state в БД
|
||||
// 8) апдейт состояния
|
||||
state.setLastGlobalNumber(globalNumber);
|
||||
state.setLastGlobalHash(bytesToHex(block.getHash32()));
|
||||
|
||||
// line number:
|
||||
// - для заглавного блока оставляем 0
|
||||
// - для остальных двигаем как обычно
|
||||
if (!header) {
|
||||
state.setLastLineNumber(lineIndex, block.lineNumber);
|
||||
} else {
|
||||
state.setLastLineNumber(lineIndex, 0);
|
||||
}
|
||||
|
||||
// line hash обновляем в любом случае (так проще для цепочки)
|
||||
state.setLastLineHash(lineIndex, bytesToHex(block.getHash32()));
|
||||
|
||||
state.setSizeBytes(state.getSizeBytes() + fullBytes.length);
|
||||
state.setUpdatedAtMs(System.currentTimeMillis());
|
||||
|
||||
BlockchainStateDAO.getInstance().upsert(conn, state);
|
||||
BlockchainStateDAO.getInstance().upsert(state);
|
||||
|
||||
conn.commit();
|
||||
return new Result(200, null, state, lineIndex);
|
||||
|
||||
} catch (SQLException e) {
|
||||
conn.rollback();
|
||||
throw e;
|
||||
} finally {
|
||||
conn.setAutoCommit(oldAuto);
|
||||
}
|
||||
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Создаёт стартовое состояние по данным пользователя:
|
||||
* - проверяем, что login существует и что bchId совпадает с blockchainId
|
||||
* - public_key_base64 берём из loginKey
|
||||
*/
|
||||
private static BlockchainStateEntry createInitialStateFromUser(String login, long blockchainId) throws SQLException {
|
||||
SolanaUserEntry u = SolanaUsersDAO.getInstance().getByLogin(login);
|
||||
if (u == null) return null;
|
||||
if (u.getBchId() != blockchainId) return null;
|
||||
|
||||
BlockchainStateEntry s = new BlockchainStateEntry();
|
||||
s.setBlockchainId(blockchainId);
|
||||
s.setUserLogin(login);
|
||||
|
||||
// публичный ключ для блокчейна = loginKey (как ты и хочешь)
|
||||
s.setPublicKeyBase64(nn(u.getLoginKey()));
|
||||
|
||||
// лимит (пока тестовый / из пользователя)
|
||||
int limit = (u.getBchLimit() != null) ? u.getBchLimit() : 1_000_000;
|
||||
s.setSizeLimit(limit);
|
||||
|
||||
s.setSizeBytes(0);
|
||||
|
||||
// ВАЖНО: стартовые значения
|
||||
s.setLastGlobalNumber(-1);
|
||||
s.setLastGlobalHash(ZERO64);
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
s.setLastLineNumber(i, 0);
|
||||
s.setLastLineHash(i, ZERO64);
|
||||
}
|
||||
|
||||
s.setUpdatedAtMs(System.currentTimeMillis());
|
||||
return s;
|
||||
}
|
||||
|
||||
private static String nn(String s) { return s == null ? "" : s; }
|
||||
|
||||
private static boolean eqHash(String a, String b) {
|
||||
|
||||
@ -14,7 +14,7 @@ public final class Net_AddBlock_new_Handler implements JsonMessageHandler {
|
||||
public Net_Response handle(Net_Request baseReq, ConnectionContext ctx) throws Exception {
|
||||
Net_AddBlock_new_Request req = (Net_AddBlock_new_Request) baseReq;
|
||||
|
||||
var r = BlockchainStateService_new.getInstance().addBlockAtomically(
|
||||
var r = BlockchainStateService_new.getInstance().addBlock(
|
||||
req.getLogin(),
|
||||
req.getBlockchainId(),
|
||||
req.getGlobalNumber(),
|
||||
@ -32,8 +32,6 @@ public final class Net_AddBlock_new_Handler implements JsonMessageHandler {
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
resp.setReasonCode(null);
|
||||
} else {
|
||||
// 409 / 422 / 403 / 404...
|
||||
// у тебя WireCodes.Status — это “HTTP-подобное”? тогда маппим:
|
||||
resp.setStatus(r.httpStatus);
|
||||
resp.setReasonCode(r.reasonCode);
|
||||
}
|
||||
|
||||
@ -10,56 +10,45 @@ import server.logic.ws_protocol.JSON.entyties.tempToTest.Net_AddUser_Response;
|
||||
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.dao.BlockchainStateDAO;
|
||||
import shine.db.dao.SolanaUsersDAO;
|
||||
import shine.db.entities.BlockchainStateEntry;
|
||||
import shine.db.entities.SolanaUserEntry;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Временный хэндлер AddUser (тестовая регистрация локального пользователя).
|
||||
*
|
||||
* Ожидаемый запрос (все поля в payload):
|
||||
* {
|
||||
* "op": "AddUser",
|
||||
* "requestId": "...",
|
||||
* "payload": {
|
||||
* "login": "anya",
|
||||
* "loginId": 100211,
|
||||
* "bchId": 4222,
|
||||
* "loginKey": "base64-pubkey-login",
|
||||
* "deviceKey": "base64-pubkey-device",
|
||||
* "bchLimit": 1000000
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* При успехе:
|
||||
* - пользователь сохраняется в таблицу solana_users;
|
||||
* - возвращается status=200 и пустой payload.
|
||||
*/
|
||||
public class Net_AddUser_Handler implements JsonMessageHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Net_AddUser_Handler.class);
|
||||
|
||||
// ====== TEST CONST (пока так) ======
|
||||
private static final int TEST_BCH_LIMIT = 1_000_000;
|
||||
|
||||
private static final String ZERO64 = "0".repeat(64);
|
||||
|
||||
@Override
|
||||
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) throws Exception {
|
||||
Net_AddUser_Request req = (Net_AddUser_Request) baseRequest;
|
||||
|
||||
// Одна общая проверка всех ключевых полей
|
||||
if (req.getLogin() == null || req.getLogin().isBlank()
|
||||
|| req.getLoginKey() == null || req.getLoginKey().isBlank()
|
||||
|| req.getDeviceKey() == null || req.getDeviceKey().isBlank()
|
||||
|| req.getBchLimit() == null) {
|
||||
|| req.getDeviceKey() == null || req.getDeviceKey().isBlank()) {
|
||||
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.BAD_REQUEST,
|
||||
"BAD_FIELDS",
|
||||
"Некорректные или пустые поля: login, loginKey, deviceKey, bchLimit"
|
||||
"Некорректные или пустые поля: login, loginKey, deviceKey"
|
||||
);
|
||||
}
|
||||
|
||||
// bchLimit: если клиент не прислал — ставим тестовую константу
|
||||
Integer limit = req.getBchLimit();
|
||||
if (limit == null || limit <= 0) limit = TEST_BCH_LIMIT;
|
||||
|
||||
try {
|
||||
SolanaUsersDAO dao = SolanaUsersDAO.getInstance();
|
||||
SolanaUsersDAO users = SolanaUsersDAO.getInstance();
|
||||
BlockchainStateDAO stateDao = BlockchainStateDAO.getInstance();
|
||||
|
||||
SolanaUserEntry user = new SolanaUserEntry(
|
||||
req.getLoginId(),
|
||||
@ -67,21 +56,47 @@ public class Net_AddUser_Handler implements JsonMessageHandler {
|
||||
req.getBchId(),
|
||||
req.getLoginKey(),
|
||||
req.getDeviceKey(),
|
||||
req.getBchLimit()
|
||||
limit
|
||||
);
|
||||
|
||||
dao.insert(user);
|
||||
users.insert(user);
|
||||
|
||||
// Создаём стартовую запись blockchain_state
|
||||
BlockchainStateEntry s = new BlockchainStateEntry();
|
||||
s.setBlockchainId(req.getBchId());
|
||||
s.setUserLogin(req.getLogin());
|
||||
|
||||
// В блокчейн-стейте храним loginKey как основной pubkey
|
||||
s.setPublicKeyBase64(req.getLoginKey());
|
||||
|
||||
s.setSizeLimit(limit);
|
||||
s.setSizeBytes(0);
|
||||
|
||||
// ВАЖНО: твои стартовые значения
|
||||
s.setLastGlobalNumber(-1);
|
||||
s.setLastGlobalHash(ZERO64);
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
s.setLastLineNumber(i, 0);
|
||||
s.setLastLineHash(i, ZERO64);
|
||||
}
|
||||
|
||||
s.setUpdatedAtMs(System.currentTimeMillis());
|
||||
|
||||
stateDao.upsert(s);
|
||||
|
||||
Net_AddUser_Response resp = new Net_AddUser_Response();
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
// payload станет {} через JsonInboundProcessor
|
||||
log.info("✅ Пользователь добавлен: login={}, loginId={}", req.getLogin(), req.getLoginId());
|
||||
|
||||
log.info("✅ AddUser ok: login={}, loginId={}, bchId={}, limit={}",
|
||||
req.getLogin(), req.getLoginId(), req.getBchId(), limit);
|
||||
|
||||
return resp;
|
||||
|
||||
} catch (SQLException e) {
|
||||
log.error("❌ Ошибка при вставке пользователя в БД", e);
|
||||
log.error("❌ DB error in AddUser", e);
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.SERVER_DATA_ERROR,
|
||||
@ -89,7 +104,7 @@ public class Net_AddUser_Handler implements JsonMessageHandler {
|
||||
"Ошибка доступа к базе данных"
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.error("❌ Неожиданная ошибка в AddUser", e);
|
||||
log.error("❌ Internal error in AddUser", e);
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.INTERNAL_ERROR,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user