Ещё промежуточный комит верии - не работает :)   2
This commit is contained in:
AidarKC
2025-12-17 17:15:52 +03:00
parent aa2caf1f10
commit 29c6e5a0f6
7 changed files with 138 additions and 281 deletions
@@ -8,7 +8,7 @@ public final class Net_AddBlock_new_Request extends Net_Request {
private long blockchainId; // обязателен
private int globalNumber; // обязателен
private String prevGlobalHash; // HEX(64) или "" для нулевого
private String blockBase64; // байты FULL-блока (raw+sig+hash) в Base64
private String blockBytesB64; // байты FULL-блока (raw+sig+hash) в Base64
public String getLogin() { return login; }
public void setLogin(String login) { this.login = login; }
@@ -22,6 +22,6 @@ public final class Net_AddBlock_new_Request extends Net_Request {
public String getPrevGlobalHash() { return prevGlobalHash; }
public void setPrevGlobalHash(String prevGlobalHash) { this.prevGlobalHash = prevGlobalHash; }
public String getBlockBase64() { return blockBase64; }
public void setBlockBase64(String blockBase64) { this.blockBase64 = blockBase64; }
}
public String getBlockBytesB64() { return blockBytesB64; }
public void setBlockBytesB64(String blockBytesB64) { this.blockBytesB64 = blockBytesB64; }
}
@@ -0,0 +1,14 @@
package server.logic.ws_protocol.JSON.handlers.blockchain;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
public final class BlockchainLocks {
private static final ConcurrentHashMap<Long, ReentrantLock> MAP = new ConcurrentHashMap<>();
private BlockchainLocks() {}
public static ReentrantLock lockFor(long blockchainId) {
return MAP.computeIfAbsent(blockchainId, id -> new ReentrantLock(true)); // fair=true
}
}
@@ -8,8 +8,9 @@ import utils.files.FileStoreUtil;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Base64;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
public final class BlockchainStateService_new {
@@ -33,6 +34,13 @@ public final class BlockchainStateService_new {
public static BlockchainStateService_new getInstance() { return INSTANCE; }
private BlockchainStateService_new() {}
// --- MVP: локи в памяти по blockchainId ---
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(
String login,
long blockchainId,
@@ -68,85 +76,78 @@ public final class BlockchainStateService_new {
if (lineIndex < 0 || lineIndex > 7)
return new Result(400, "BAD_LINE_INDEX", null, lineIndex);
Connection conn = SqliteDbController.getInstance().getConnection();
boolean oldAuto = conn.getAutoCommit();
conn.setAutoCommit(false);
ReentrantLock lock = lockFor(blockchainId);
lock.lock();
try (Connection conn = SqliteDbController.getInstance().getConnection()) {
try (Statement st = conn.createStatement()) {
// важно: заранее берём write lock
st.execute("BEGIN IMMEDIATE");
// Транзакция — норм, но БЕЗ "BEGIN IMMEDIATE".
boolean oldAuto = conn.getAutoCommit();
conn.setAutoCommit(false);
BlockchainStateEntry state = BlockchainStateDAO.getInstance().getByBlockchainId(blockchainId);
if (state == null) {
try {
BlockchainStateEntry state =
BlockchainStateDAO.getInstance().getByBlockchainId(conn, blockchainId);
if (state == null) {
conn.rollback();
return new Result(404, "UNKNOWN_BLOCKCHAIN", null, lineIndex);
}
if (!login.equals(state.getUserLogin())) {
conn.rollback();
return new Result(403, "LOGIN_MISMATCH", state, lineIndex);
}
int expectedGlobal = state.getLastGlobalNumber() + 1;
if (globalNumber != expectedGlobal) {
conn.rollback();
return new Result(409, "OUT_OF_SEQUENCE_GLOBAL", state, lineIndex);
}
String dbPrevGlobalHash = nn(state.getLastGlobalHash());
if (!eqHash(prevGlobalHashHex, dbPrevGlobalHash)) {
conn.rollback();
return new Result(409, "GLOBAL_HASH_MISMATCH", state, lineIndex);
}
int expectedLineNumber = state.getLastLineNumber(lineIndex) + 1;
if (block.lineNumber != expectedLineNumber) {
conn.rollback();
return new Result(409, "OUT_OF_SEQUENCE_LINE", state, lineIndex);
}
// prevLineHash (пока просто читаем, дальше пригодится для крипто-проверки)
String dbPrevLineHashHex = nn(state.getLastLineHash(lineIndex));
// TODO crypto check (потом подключим)
// 1) пишем в файл
FileStoreUtil.getInstance().addDataToBlockchain(blockchainId, block.toBytes());
// 2) обновляем state в БД
state.setLastGlobalNumber(globalNumber);
state.setLastGlobalHash(bytesToHex(block.getHash32()));
state.setLastLineNumber(lineIndex, block.lineNumber);
state.setLastLineHash(lineIndex, bytesToHex(block.getHash32()));
state.setSizeBytes(state.getSizeBytes() + fullBytes.length);
state.setUpdatedAtMs(System.currentTimeMillis());
BlockchainStateDAO.getInstance().upsert(conn, state);
conn.commit();
return new Result(200, null, state, lineIndex);
} catch (SQLException e) {
conn.rollback();
return new Result(404, "UNKNOWN_BLOCKCHAIN", null, lineIndex);
throw e;
} finally {
conn.setAutoCommit(oldAuto);
}
// 1) защита от подмены логина
if (!login.equals(state.getUserLogin())) {
conn.rollback();
return new Result(403, "LOGIN_MISMATCH", state, lineIndex);
}
// 2) проверяем ожидаемый global
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
int expectedLineNumber = state.getLastLineNumber(lineIndex) + 1;
if (block.lineNumber != expectedLineNumber) {
conn.rollback();
return new Result(409, "OUT_OF_SEQUENCE_LINE", state, lineIndex);
}
// 5) prevLineHash берём из БД (он хранится!)
String dbPrevLineHashHex = nn(state.getLastLineHash(lineIndex));
// 6) полноценная крипто-проверка (хэш/подпись)
// TODO: тут подключи твой реальный verifier:
// - посчитать preimage по твоим правилам (login + prevGlobalHash32 + prevLineHash32 + rawBytes)
// - сверить sha256(preimage) == block.hash32
// - проверить Ed25519 подпись
//
// Если не ок:
// conn.rollback(); return new Result(422, "CRYPTO_INVALID", state, lineIndex);
// 7) запись блока в файл (append)
FileStoreUtil.getInstance().addDataToBlockchain(blockchainId, block.toBytes());
// 8) апдейт состояния в БД
state.setLastGlobalNumber(globalNumber);
state.setLastGlobalHash(bytesToHex(block.getHash32())); // новый global hash = hash блока
state.setLastLineNumber(lineIndex, block.lineNumber);
// ВАЖНО: line hash тоже логично сделать = hash блока (если так задумано)
state.setLastLineHash(lineIndex, bytesToHex(block.getHash32()));
// size_bytes += len(fullBytes)
state.setSizeBytes(state.getSizeBytes() + fullBytes.length);
state.setUpdatedAtMs(System.currentTimeMillis());
BlockchainStateDAO.getInstance().upsert(state);
conn.commit();
return new Result(200, null, state, lineIndex);
} catch (SQLException e) {
conn.rollback();
// если хочешь красиво: SQLITE_BUSY → 503 RETRY
throw e;
} finally {
conn.setAutoCommit(oldAuto);
lock.unlock();
}
}
@@ -164,4 +165,4 @@ public final class BlockchainStateService_new {
for (byte v : b) sb.append(String.format("%02x", v));
return sb.toString();
}
}
}
@@ -19,7 +19,7 @@ public final class Net_AddBlock_new_Handler implements JsonMessageHandler {
req.getBlockchainId(),
req.getGlobalNumber(),
req.getPrevGlobalHash(),
req.getBlockBase64()
req.getBlockBytesB64()
);
Net_AddBlock_new_Response resp = new Net_AddBlock_new_Response();