diff --git a/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java b/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java index aa3fef3..5212d0d 100644 --- a/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java +++ b/shine-server-db/src/main/java/shine/db/DatabaseInitializer.java @@ -20,7 +20,7 @@ import java.sql.Statement; * - active_sessions * - users_params * - ip_geo_cache - * - blockchain_state (MVP: одна таблица, линии 0..7 внутри строки) + * - blockchain_state (MVP) */ public class DatabaseInitializer { @@ -160,8 +160,12 @@ public class DatabaseInitializer { blockchain_id INTEGER NOT NULL PRIMARY KEY, user_login TEXT NOT NULL, public_key_base64 TEXT NOT NULL, + size_limit INTEGER NOT NULL, size_bytes INTEGER NOT NULL, + + file_size_bytes INTEGER NOT NULL, + last_global_number INTEGER NOT NULL, last_global_hash TEXT NOT NULL, updated_at_ms INTEGER NOT NULL, @@ -198,4 +202,4 @@ public class DatabaseInitializer { """); } } -} +} \ No newline at end of file diff --git a/shine-server-db/src/main/java/shine/db/dao/BlockchainStateDAO.java b/shine-server-db/src/main/java/shine/db/dao/BlockchainStateDAO.java index 022d8f1..b85bb3b 100644 --- a/shine-server-db/src/main/java/shine/db/dao/BlockchainStateDAO.java +++ b/shine-server-db/src/main/java/shine/db/dao/BlockchainStateDAO.java @@ -21,7 +21,6 @@ public final class BlockchainStateDAO { return instance; } - // старый метод оставим public BlockchainStateEntry getByBlockchainId(long blockchainId) throws SQLException { try (Connection c = db.getConnection()) { return getByBlockchainId(c, blockchainId); @@ -36,6 +35,7 @@ public final class BlockchainStateDAO { public_key_base64, size_limit, size_bytes, + file_size_bytes, last_global_number, last_global_hash, line0_last_number, line0_last_hash, @@ -60,7 +60,6 @@ public final class BlockchainStateDAO { } } - // старый метод оставим public void upsert(BlockchainStateEntry e) throws SQLException { try (Connection c = db.getConnection()) { upsert(c, e); @@ -75,6 +74,7 @@ public final class BlockchainStateDAO { public_key_base64, size_limit, size_bytes, + file_size_bytes, last_global_number, last_global_hash, line0_last_number, line0_last_hash, @@ -104,6 +104,7 @@ public final class BlockchainStateDAO { public_key_base64 = excluded.public_key_base64, size_limit = excluded.size_limit, size_bytes = excluded.size_bytes, + file_size_bytes = excluded.file_size_bytes, last_global_number = excluded.last_global_number, last_global_hash = excluded.last_global_hash, line0_last_number = excluded.line0_last_number, @@ -132,6 +133,7 @@ public final class BlockchainStateDAO { ps.setString(i++, nn(e.getPublicKeyBase64())); ps.setInt(i++, e.getSizeLimit()); ps.setInt(i++, e.getSizeBytes()); + ps.setLong(i++, e.getFileSizeBytes()); ps.setInt(i++, e.getLastGlobalNumber()); ps.setString(i++, nn(e.getLastGlobalHash())); @@ -153,6 +155,7 @@ public final class BlockchainStateDAO { e.setSizeLimit(rs.getInt("size_limit")); e.setSizeBytes(rs.getInt("size_bytes")); + e.setFileSizeBytes(rs.getLong("file_size_bytes")); e.setLastGlobalNumber(rs.getInt("last_global_number")); e.setLastGlobalHash(rs.getString("last_global_hash")); diff --git a/shine-server-db/src/main/java/shine/db/entities/BlockchainStateEntry.java b/shine-server-db/src/main/java/shine/db/entities/BlockchainStateEntry.java index 79a180c..cb06b99 100644 --- a/shine-server-db/src/main/java/shine/db/entities/BlockchainStateEntry.java +++ b/shine-server-db/src/main/java/shine/db/entities/BlockchainStateEntry.java @@ -16,6 +16,9 @@ public final class BlockchainStateEntry { private int sizeLimit; private int sizeBytes; + /** NEW: размер файла блокчейна в байтах (то, что будем сверять/чинить при старте). */ + private long fileSizeBytes; + private int lastGlobalNumber; private String lastGlobalHash; // HEX(64) либо пустая строка для "нулевого" @@ -32,12 +35,12 @@ public final class BlockchainStateEntry { this.lastGlobalHash = ""; } - // --- удобный конструктор (если хочешь) --- public BlockchainStateEntry(long blockchainId, String userLogin, String publicKeyBase64, int sizeLimit, int sizeBytes, + long fileSizeBytes, int lastGlobalNumber, String lastGlobalHash, int[] lastLineNumbers, @@ -48,6 +51,7 @@ public final class BlockchainStateEntry { this.publicKeyBase64 = publicKeyBase64; this.sizeLimit = sizeLimit; this.sizeBytes = sizeBytes; + this.fileSizeBytes = fileSizeBytes; this.lastGlobalNumber = lastGlobalNumber; this.lastGlobalHash = lastGlobalHash == null ? "" : lastGlobalHash; @@ -65,8 +69,6 @@ public final class BlockchainStateEntry { this.updatedAtMs = updatedAtMs; } - // --- getters / setters --- - public long getBlockchainId() { return blockchainId; } public void setBlockchainId(long blockchainId) { this.blockchainId = blockchainId; } @@ -82,6 +84,9 @@ public final class BlockchainStateEntry { public int getSizeBytes() { return sizeBytes; } public void setSizeBytes(int sizeBytes) { this.sizeBytes = sizeBytes; } + public long getFileSizeBytes() { return fileSizeBytes; } + public void setFileSizeBytes(long fileSizeBytes) { this.fileSizeBytes = fileSizeBytes; } + public int getLastGlobalNumber() { return lastGlobalNumber; } public void setLastGlobalNumber(int lastGlobalNumber) { this.lastGlobalNumber = lastGlobalNumber; } diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/BlockchainStateService_new.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/BlockchainStateService_new.java index 37e3b42..bee65e4 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/BlockchainStateService_new.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/BlockchainStateService_new.java @@ -29,6 +29,7 @@ public final class BlockchainStateService_new { this.stateAfter = stateAfter; this.lineIndex = lineIndex; } + public boolean isOk() { return reasonCode == null && httpStatus == 200; } } @@ -40,7 +41,6 @@ public final class BlockchainStateService_new { // Локи по blockchainId (MVP, один сервер) private final ConcurrentMap locks = new ConcurrentHashMap<>(); - private ReentrantLock lockFor(long blockchainId) { return locks.computeIfAbsent(blockchainId, k -> new ReentrantLock()); } @@ -80,6 +80,8 @@ public final class BlockchainStateService_new { if (lineIndex < 0 || lineIndex > 7) return new Result(400, "BAD_LINE_INDEX", null, lineIndex); + boolean isHeaderBlock = (globalNumber == 0 && lineIndex == 0 && block.lineNumber == 0); + ReentrantLock lock = lockFor(blockchainId); lock.lock(); try (Connection conn = SqliteDbController.getInstance().getConnection()) { @@ -87,18 +89,14 @@ public final class BlockchainStateService_new { BlockchainStateDAO stateDao = BlockchainStateDAO.getInstance(); SolanaUsersDAO usersDao = SolanaUsersDAO.getInstance(); - // читаем state В ЭТОМ ЖЕ conn BlockchainStateEntry state = stateDao.getByBlockchainId(conn, blockchainId); - boolean isHeaderBlock = (globalNumber == 0 && lineIndex == 0 && block.lineNumber == 0); - if (state == null) { // state отсутствует — разрешаем ТОЛЬКО header-блок if (!isHeaderBlock) { return new Result(404, "UNKNOWN_BLOCKCHAIN", null, lineIndex); } - // Проверяем пользователя и соответствие bchId SolanaUserEntry u = usersDao.getByLogin(conn, login); if (u == null) { return new Result(404, "UNKNOWN_USER", null, lineIndex); @@ -112,25 +110,21 @@ public final class BlockchainStateService_new { return new Result(409, "GLOBAL_HASH_MISMATCH", null, lineIndex); } - // Создаём “нулевой” state ДО записи header (last_global_number = -1) + // Создаём начальный state (ЕЩЁ НЕ ПИШЕМ В БД — запишем после успешного append) state = createInitialState(blockchainId, login, u.getLoginKey(), safeLimit(u.getBchLimit())); -// stateDao.upsert(conn, state); //TODO так здесь наверное его в БД сохранять не надо если всё верно то потом дополненный сохраниться } else { - // state есть — обычная проверка login if (!login.equals(state.getUserLogin())) { return new Result(403, "LOGIN_MISMATCH", state, lineIndex); } } - // Перечитывать не надо, state актуален в переменной. - // expected global int expectedGlobal = state.getLastGlobalNumber() + 1; if (globalNumber != expectedGlobal) { return new Result(409, "OUT_OF_SEQUENCE_GLOBAL", state, lineIndex); } - // prev global hash сверяем + // prev global hash String dbPrevGlobalHash = nn(state.getLastGlobalHash()); if (!eqHash(prevGlobalHashHex, dbPrevGlobalHash)) { return new Result(409, "GLOBAL_HASH_MISMATCH", state, lineIndex); @@ -144,10 +138,10 @@ public final class BlockchainStateService_new { // TODO: крипто-проверка (потом подключим) - // 1) запись блока в файл + // 1) запись блока в файл (append-only) FileStoreUtil.getInstance().addDataToBlockchain(blockchainId, block.toBytes()); - // 2) апдейт state + // 2) апдейт state + запись state в БД String newHashHex = bytesToHex(block.getHash32()); state.setLastGlobalNumber(globalNumber); @@ -156,15 +150,18 @@ public final class BlockchainStateService_new { state.setLastLineNumber(lineIndex, block.lineNumber); state.setLastLineHash(lineIndex, newHashHex); + // Логический размер (как было) state.setSizeBytes(state.getSizeBytes() + fullBytes.length); + + // Новый: размер файла (ровно по твоему правилу) + state.setFileSizeBytes(state.getFileSizeBytes() + fullBytes.length); + state.setUpdatedAtMs(System.currentTimeMillis()); stateDao.upsert(conn, state); return new Result(200, null, state, lineIndex); - } catch (SQLException e) { - throw e; } finally { lock.unlock(); } @@ -180,18 +177,19 @@ public final class BlockchainStateService_new { s.setPublicKeyBase64(nn(loginKeyBase64)); s.setSizeLimit(sizeLimit); - s.setSizeBytes(0); - // как ты хочешь: + s.setSizeBytes(0); + s.setFileSizeBytes(0); + s.setLastGlobalNumber(-1); s.setLastGlobalHash(ZERO64); for (int i = 0; i < 8; i++) { if (i == 0) { - // линия 0: заглавный блок имеет lineNumber=0 + // линия 0: заглавный блок имеет lineNumber=0 -> значит "последний" до него = -1 s.setLastLineNumber(i, -1); } else { - // остальные линии: первый блок будет lineNumber=1 + // остальные линии: первый блок будет lineNumber=1 -> значит "последний" до него = 0 s.setLastLineNumber(i, 0); } s.setLastLineHash(i, ZERO64); @@ -202,7 +200,7 @@ public final class BlockchainStateService_new { } private static int safeLimit(Integer limit) { - if (limit == null || limit <= 0) return 1_000_000; // fallback (test) + if (limit == null || limit <= 0) return 1_000_000; // TEST ONLY fallback return limit; }