Заработало блок добавляется в файл и в статус блокчена в БД!!
Но ещё надо сделать таблицу с записями :)    2
This commit is contained in:
AidarKC 2025-12-17 19:17:10 +03:00
parent e9c11d6b75
commit 4fb6b10a97
4 changed files with 37 additions and 27 deletions

View File

@ -20,7 +20,7 @@ import java.sql.Statement;
* - active_sessions * - active_sessions
* - users_params * - users_params
* - ip_geo_cache * - ip_geo_cache
* - blockchain_state (MVP: одна таблица, линии 0..7 внутри строки) * - blockchain_state (MVP)
*/ */
public class DatabaseInitializer { public class DatabaseInitializer {
@ -160,8 +160,12 @@ public class DatabaseInitializer {
blockchain_id INTEGER NOT NULL PRIMARY KEY, blockchain_id INTEGER NOT NULL PRIMARY KEY,
user_login TEXT NOT NULL, user_login TEXT NOT NULL,
public_key_base64 TEXT NOT NULL, public_key_base64 TEXT NOT NULL,
size_limit INTEGER NOT NULL, size_limit INTEGER NOT NULL,
size_bytes INTEGER NOT NULL, size_bytes INTEGER NOT NULL,
file_size_bytes INTEGER NOT NULL,
last_global_number INTEGER NOT NULL, last_global_number INTEGER NOT NULL,
last_global_hash TEXT NOT NULL, last_global_hash TEXT NOT NULL,
updated_at_ms INTEGER NOT NULL, updated_at_ms INTEGER NOT NULL,
@ -198,4 +202,4 @@ public class DatabaseInitializer {
"""); """);
} }
} }
} }

View File

@ -21,7 +21,6 @@ public final class BlockchainStateDAO {
return instance; return instance;
} }
// старый метод оставим
public BlockchainStateEntry getByBlockchainId(long blockchainId) throws SQLException { public BlockchainStateEntry getByBlockchainId(long blockchainId) throws SQLException {
try (Connection c = db.getConnection()) { try (Connection c = db.getConnection()) {
return getByBlockchainId(c, blockchainId); return getByBlockchainId(c, blockchainId);
@ -36,6 +35,7 @@ public final class BlockchainStateDAO {
public_key_base64, public_key_base64,
size_limit, size_limit,
size_bytes, size_bytes,
file_size_bytes,
last_global_number, last_global_number,
last_global_hash, last_global_hash,
line0_last_number, line0_last_hash, line0_last_number, line0_last_hash,
@ -60,7 +60,6 @@ public final class BlockchainStateDAO {
} }
} }
// старый метод оставим
public void upsert(BlockchainStateEntry e) throws SQLException { public void upsert(BlockchainStateEntry e) throws SQLException {
try (Connection c = db.getConnection()) { try (Connection c = db.getConnection()) {
upsert(c, e); upsert(c, e);
@ -75,6 +74,7 @@ public final class BlockchainStateDAO {
public_key_base64, public_key_base64,
size_limit, size_limit,
size_bytes, size_bytes,
file_size_bytes,
last_global_number, last_global_number,
last_global_hash, last_global_hash,
line0_last_number, line0_last_hash, line0_last_number, line0_last_hash,
@ -104,6 +104,7 @@ public final class BlockchainStateDAO {
public_key_base64 = excluded.public_key_base64, public_key_base64 = excluded.public_key_base64,
size_limit = excluded.size_limit, size_limit = excluded.size_limit,
size_bytes = excluded.size_bytes, size_bytes = excluded.size_bytes,
file_size_bytes = excluded.file_size_bytes,
last_global_number = excluded.last_global_number, last_global_number = excluded.last_global_number,
last_global_hash = excluded.last_global_hash, last_global_hash = excluded.last_global_hash,
line0_last_number = excluded.line0_last_number, line0_last_number = excluded.line0_last_number,
@ -132,6 +133,7 @@ public final class BlockchainStateDAO {
ps.setString(i++, nn(e.getPublicKeyBase64())); ps.setString(i++, nn(e.getPublicKeyBase64()));
ps.setInt(i++, e.getSizeLimit()); ps.setInt(i++, e.getSizeLimit());
ps.setInt(i++, e.getSizeBytes()); ps.setInt(i++, e.getSizeBytes());
ps.setLong(i++, e.getFileSizeBytes());
ps.setInt(i++, e.getLastGlobalNumber()); ps.setInt(i++, e.getLastGlobalNumber());
ps.setString(i++, nn(e.getLastGlobalHash())); ps.setString(i++, nn(e.getLastGlobalHash()));
@ -153,6 +155,7 @@ public final class BlockchainStateDAO {
e.setSizeLimit(rs.getInt("size_limit")); e.setSizeLimit(rs.getInt("size_limit"));
e.setSizeBytes(rs.getInt("size_bytes")); e.setSizeBytes(rs.getInt("size_bytes"));
e.setFileSizeBytes(rs.getLong("file_size_bytes"));
e.setLastGlobalNumber(rs.getInt("last_global_number")); e.setLastGlobalNumber(rs.getInt("last_global_number"));
e.setLastGlobalHash(rs.getString("last_global_hash")); e.setLastGlobalHash(rs.getString("last_global_hash"));

View File

@ -16,6 +16,9 @@ public final class BlockchainStateEntry {
private int sizeLimit; private int sizeLimit;
private int sizeBytes; private int sizeBytes;
/** NEW: размер файла блокчейна в байтах (то, что будем сверять/чинить при старте). */
private long fileSizeBytes;
private int lastGlobalNumber; private int lastGlobalNumber;
private String lastGlobalHash; // HEX(64) либо пустая строка для "нулевого" private String lastGlobalHash; // HEX(64) либо пустая строка для "нулевого"
@ -32,12 +35,12 @@ public final class BlockchainStateEntry {
this.lastGlobalHash = ""; this.lastGlobalHash = "";
} }
// --- удобный конструктор (если хочешь) ---
public BlockchainStateEntry(long blockchainId, public BlockchainStateEntry(long blockchainId,
String userLogin, String userLogin,
String publicKeyBase64, String publicKeyBase64,
int sizeLimit, int sizeLimit,
int sizeBytes, int sizeBytes,
long fileSizeBytes,
int lastGlobalNumber, int lastGlobalNumber,
String lastGlobalHash, String lastGlobalHash,
int[] lastLineNumbers, int[] lastLineNumbers,
@ -48,6 +51,7 @@ public final class BlockchainStateEntry {
this.publicKeyBase64 = publicKeyBase64; this.publicKeyBase64 = publicKeyBase64;
this.sizeLimit = sizeLimit; this.sizeLimit = sizeLimit;
this.sizeBytes = sizeBytes; this.sizeBytes = sizeBytes;
this.fileSizeBytes = fileSizeBytes;
this.lastGlobalNumber = lastGlobalNumber; this.lastGlobalNumber = lastGlobalNumber;
this.lastGlobalHash = lastGlobalHash == null ? "" : lastGlobalHash; this.lastGlobalHash = lastGlobalHash == null ? "" : lastGlobalHash;
@ -65,8 +69,6 @@ public final class BlockchainStateEntry {
this.updatedAtMs = updatedAtMs; this.updatedAtMs = updatedAtMs;
} }
// --- getters / setters ---
public long getBlockchainId() { return blockchainId; } public long getBlockchainId() { return blockchainId; }
public void setBlockchainId(long blockchainId) { this.blockchainId = blockchainId; } public void setBlockchainId(long blockchainId) { this.blockchainId = blockchainId; }
@ -82,6 +84,9 @@ public final class BlockchainStateEntry {
public int getSizeBytes() { return sizeBytes; } public int getSizeBytes() { return sizeBytes; }
public void setSizeBytes(int sizeBytes) { this.sizeBytes = 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 int getLastGlobalNumber() { return lastGlobalNumber; }
public void setLastGlobalNumber(int lastGlobalNumber) { this.lastGlobalNumber = lastGlobalNumber; } public void setLastGlobalNumber(int lastGlobalNumber) { this.lastGlobalNumber = lastGlobalNumber; }

View File

@ -29,6 +29,7 @@ public final class BlockchainStateService_new {
this.stateAfter = stateAfter; this.stateAfter = stateAfter;
this.lineIndex = lineIndex; this.lineIndex = lineIndex;
} }
public boolean isOk() { return reasonCode == null && httpStatus == 200; } public boolean isOk() { return reasonCode == null && httpStatus == 200; }
} }
@ -40,7 +41,6 @@ public final class BlockchainStateService_new {
// Локи по blockchainId (MVP, один сервер) // Локи по blockchainId (MVP, один сервер)
private final ConcurrentMap<Long, ReentrantLock> locks = new ConcurrentHashMap<>(); private final ConcurrentMap<Long, ReentrantLock> locks = new ConcurrentHashMap<>();
private ReentrantLock lockFor(long blockchainId) { private ReentrantLock lockFor(long blockchainId) {
return locks.computeIfAbsent(blockchainId, k -> new ReentrantLock()); return locks.computeIfAbsent(blockchainId, k -> new ReentrantLock());
} }
@ -80,6 +80,8 @@ public final class BlockchainStateService_new {
if (lineIndex < 0 || lineIndex > 7) if (lineIndex < 0 || lineIndex > 7)
return new Result(400, "BAD_LINE_INDEX", null, lineIndex); return new Result(400, "BAD_LINE_INDEX", null, lineIndex);
boolean isHeaderBlock = (globalNumber == 0 && lineIndex == 0 && block.lineNumber == 0);
ReentrantLock lock = lockFor(blockchainId); ReentrantLock lock = lockFor(blockchainId);
lock.lock(); lock.lock();
try (Connection conn = SqliteDbController.getInstance().getConnection()) { try (Connection conn = SqliteDbController.getInstance().getConnection()) {
@ -87,18 +89,14 @@ public final class BlockchainStateService_new {
BlockchainStateDAO stateDao = BlockchainStateDAO.getInstance(); BlockchainStateDAO stateDao = BlockchainStateDAO.getInstance();
SolanaUsersDAO usersDao = SolanaUsersDAO.getInstance(); SolanaUsersDAO usersDao = SolanaUsersDAO.getInstance();
// читаем state В ЭТОМ ЖЕ conn
BlockchainStateEntry state = stateDao.getByBlockchainId(conn, blockchainId); BlockchainStateEntry state = stateDao.getByBlockchainId(conn, blockchainId);
boolean isHeaderBlock = (globalNumber == 0 && lineIndex == 0 && block.lineNumber == 0);
if (state == null) { if (state == null) {
// state отсутствует разрешаем ТОЛЬКО header-блок // state отсутствует разрешаем ТОЛЬКО header-блок
if (!isHeaderBlock) { if (!isHeaderBlock) {
return new Result(404, "UNKNOWN_BLOCKCHAIN", null, lineIndex); return new Result(404, "UNKNOWN_BLOCKCHAIN", null, lineIndex);
} }
// Проверяем пользователя и соответствие bchId
SolanaUserEntry u = usersDao.getByLogin(conn, login); SolanaUserEntry u = usersDao.getByLogin(conn, login);
if (u == null) { if (u == null) {
return new Result(404, "UNKNOWN_USER", null, lineIndex); 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); 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())); state = createInitialState(blockchainId, login, u.getLoginKey(), safeLimit(u.getBchLimit()));
// stateDao.upsert(conn, state); //TODO так здесь наверное его в БД сохранять не надо если всё верно то потом дополненный сохраниться
} else { } else {
// state есть обычная проверка login
if (!login.equals(state.getUserLogin())) { if (!login.equals(state.getUserLogin())) {
return new Result(403, "LOGIN_MISMATCH", state, lineIndex); return new Result(403, "LOGIN_MISMATCH", state, lineIndex);
} }
} }
// Перечитывать не надо, state актуален в переменной.
// expected global // expected global
int expectedGlobal = state.getLastGlobalNumber() + 1; int expectedGlobal = state.getLastGlobalNumber() + 1;
if (globalNumber != expectedGlobal) { if (globalNumber != expectedGlobal) {
return new Result(409, "OUT_OF_SEQUENCE_GLOBAL", state, lineIndex); return new Result(409, "OUT_OF_SEQUENCE_GLOBAL", state, lineIndex);
} }
// prev global hash сверяем // prev global hash
String dbPrevGlobalHash = nn(state.getLastGlobalHash()); String dbPrevGlobalHash = nn(state.getLastGlobalHash());
if (!eqHash(prevGlobalHashHex, dbPrevGlobalHash)) { if (!eqHash(prevGlobalHashHex, dbPrevGlobalHash)) {
return new Result(409, "GLOBAL_HASH_MISMATCH", state, lineIndex); return new Result(409, "GLOBAL_HASH_MISMATCH", state, lineIndex);
@ -144,10 +138,10 @@ public final class BlockchainStateService_new {
// TODO: крипто-проверка (потом подключим) // TODO: крипто-проверка (потом подключим)
// 1) запись блока в файл // 1) запись блока в файл (append-only)
FileStoreUtil.getInstance().addDataToBlockchain(blockchainId, block.toBytes()); FileStoreUtil.getInstance().addDataToBlockchain(blockchainId, block.toBytes());
// 2) апдейт state // 2) апдейт state + запись state в БД
String newHashHex = bytesToHex(block.getHash32()); String newHashHex = bytesToHex(block.getHash32());
state.setLastGlobalNumber(globalNumber); state.setLastGlobalNumber(globalNumber);
@ -156,15 +150,18 @@ public final class BlockchainStateService_new {
state.setLastLineNumber(lineIndex, block.lineNumber); state.setLastLineNumber(lineIndex, block.lineNumber);
state.setLastLineHash(lineIndex, newHashHex); state.setLastLineHash(lineIndex, newHashHex);
// Логический размер (как было)
state.setSizeBytes(state.getSizeBytes() + fullBytes.length); state.setSizeBytes(state.getSizeBytes() + fullBytes.length);
// Новый: размер файла (ровно по твоему правилу)
state.setFileSizeBytes(state.getFileSizeBytes() + fullBytes.length);
state.setUpdatedAtMs(System.currentTimeMillis()); state.setUpdatedAtMs(System.currentTimeMillis());
stateDao.upsert(conn, state); stateDao.upsert(conn, state);
return new Result(200, null, state, lineIndex); return new Result(200, null, state, lineIndex);
} catch (SQLException e) {
throw e;
} finally { } finally {
lock.unlock(); lock.unlock();
} }
@ -180,18 +177,19 @@ public final class BlockchainStateService_new {
s.setPublicKeyBase64(nn(loginKeyBase64)); s.setPublicKeyBase64(nn(loginKeyBase64));
s.setSizeLimit(sizeLimit); s.setSizeLimit(sizeLimit);
s.setSizeBytes(0);
// как ты хочешь: s.setSizeBytes(0);
s.setFileSizeBytes(0);
s.setLastGlobalNumber(-1); s.setLastGlobalNumber(-1);
s.setLastGlobalHash(ZERO64); s.setLastGlobalHash(ZERO64);
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
if (i == 0) { if (i == 0) {
// линия 0: заглавный блок имеет lineNumber=0 // линия 0: заглавный блок имеет lineNumber=0 -> значит "последний" до него = -1
s.setLastLineNumber(i, -1); s.setLastLineNumber(i, -1);
} else { } else {
// остальные линии: первый блок будет lineNumber=1 // остальные линии: первый блок будет lineNumber=1 -> значит "последний" до него = 0
s.setLastLineNumber(i, 0); s.setLastLineNumber(i, 0);
} }
s.setLastLineHash(i, ZERO64); s.setLastLineHash(i, ZERO64);
@ -202,7 +200,7 @@ public final class BlockchainStateService_new {
} }
private static int safeLimit(Integer limit) { 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; return limit;
} }