19 12 25
В тот день Вроде дописал добавление но так и не заработало
This commit is contained in:
parent
6c4d8cd51b
commit
3cafd29ee5
@ -13,8 +13,7 @@ import java.sql.*;
|
||||
* - методы без Connection сами открывают и закрывают соединение
|
||||
*
|
||||
* Важно:
|
||||
* - PRIMARY KEY выбран: (loginId, blockchainId, blockGlobalNumber, blockLineIndex, blockLineNumber)
|
||||
* Это означает: в рамках одной цепочки и одного globalNumber, каждая линия/номер уникальны.
|
||||
* - PRIMARY KEY: (loginId, blockchainId, blockGlobalNumber, blockLineIndex, blockLineNumber)
|
||||
*/
|
||||
public final class BlocksDAO {
|
||||
|
||||
@ -55,27 +54,7 @@ public final class BlocksDAO {
|
||||
""";
|
||||
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
int i = 1;
|
||||
ps.setLong(i++, e.getLoginId());
|
||||
ps.setLong(i++, e.getBlockchainId());
|
||||
ps.setInt(i++, e.getBlockGlobalNumber());
|
||||
ps.setString(i++, nn(e.getBlockGlobalPreHashe()));
|
||||
|
||||
ps.setInt(i++, e.getBlockLineIndex());
|
||||
ps.setInt(i++, e.getBlockLineNumber());
|
||||
ps.setString(i++, nn(e.getBlockLinePreHashe()));
|
||||
|
||||
ps.setInt(i++, e.getMsgType());
|
||||
|
||||
byte[] bytes = e.getBlockByte();
|
||||
if (bytes != null) ps.setBytes(i++, bytes);
|
||||
else ps.setNull(i++, Types.BLOB);
|
||||
|
||||
ps.setLong(i++, e.getToLoginId());
|
||||
ps.setInt(i++, e.getToBlockchainId());
|
||||
ps.setInt(i++, e.getToBlockGlobalNumber());
|
||||
ps.setString(i++, nn(e.getToBlockHashe()));
|
||||
|
||||
bindAll(ps, e);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
@ -87,7 +66,7 @@ public final class BlocksDAO {
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- UPSERT (SAVE) --------------------
|
||||
// -------------------- UPSERT --------------------
|
||||
|
||||
/**
|
||||
* Сохранить (upsert) с внешним соединением. Соединение НЕ закрывает.
|
||||
@ -123,27 +102,7 @@ public final class BlocksDAO {
|
||||
""";
|
||||
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
int i = 1;
|
||||
ps.setLong(i++, e.getLoginId());
|
||||
ps.setLong(i++, e.getBlockchainId());
|
||||
ps.setInt(i++, e.getBlockGlobalNumber());
|
||||
ps.setString(i++, nn(e.getBlockGlobalPreHashe()));
|
||||
|
||||
ps.setInt(i++, e.getBlockLineIndex());
|
||||
ps.setInt(i++, e.getBlockLineNumber());
|
||||
ps.setString(i++, nn(e.getBlockLinePreHashe()));
|
||||
|
||||
ps.setInt(i++, e.getMsgType());
|
||||
|
||||
byte[] bytes = e.getBlockByte();
|
||||
if (bytes != null) ps.setBytes(i++, bytes);
|
||||
else ps.setNull(i++, Types.BLOB);
|
||||
|
||||
ps.setLong(i++, e.getToLoginId());
|
||||
ps.setInt(i++, e.getToBlockchainId());
|
||||
ps.setInt(i++, e.getToBlockGlobalNumber());
|
||||
ps.setString(i++, nn(e.getToBlockHashe()));
|
||||
|
||||
bindAll(ps, e);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
@ -157,15 +116,13 @@ public final class BlocksDAO {
|
||||
|
||||
// -------------------- SELECT --------------------
|
||||
|
||||
/**
|
||||
* Получить блок по PK с внешним соединением. Соединение НЕ закрывает.
|
||||
*/
|
||||
/** Получить блок по PK с внешним соединением. Соединение НЕ закрывает. */
|
||||
public BlockEntry getByPk(Connection c,
|
||||
long loginId,
|
||||
long blockchainId,
|
||||
int blockGlobalNumber,
|
||||
int blockLineIndex,
|
||||
int blockLineNumber) throws SQLException {
|
||||
long loginId,
|
||||
long blockchainId,
|
||||
int blockGlobalNumber,
|
||||
int blockLineIndex,
|
||||
int blockLineNumber) throws SQLException {
|
||||
|
||||
String sql = """
|
||||
SELECT
|
||||
@ -207,10 +164,10 @@ public final class BlocksDAO {
|
||||
|
||||
/** Получить блок по PK без внешнего соединения. Сам открывает/закрывает. */
|
||||
public BlockEntry getByPk(long loginId,
|
||||
long blockchainId,
|
||||
int blockGlobalNumber,
|
||||
int blockLineIndex,
|
||||
int blockLineNumber) throws SQLException {
|
||||
long blockchainId,
|
||||
int blockGlobalNumber,
|
||||
int blockLineIndex,
|
||||
int blockLineNumber) throws SQLException {
|
||||
try (Connection c = db.getConnection()) {
|
||||
return getByPk(c, loginId, blockchainId, blockGlobalNumber, blockLineIndex, blockLineNumber);
|
||||
}
|
||||
@ -275,7 +232,7 @@ public final class BlocksDAO {
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- DELETE (на всякий) --------------------
|
||||
// -------------------- DELETE --------------------
|
||||
|
||||
/** Удалить по PK с внешним соединением. Соединение НЕ закрывает. */
|
||||
public int deleteByPk(Connection c,
|
||||
@ -316,7 +273,32 @@ public final class BlocksDAO {
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- MAPPER --------------------
|
||||
// -------------------- INTERNAL --------------------
|
||||
|
||||
/** Единая привязка параметров под INSERT/UPSERT — чтобы не разъезжалось. */
|
||||
private static void bindAll(PreparedStatement ps, BlockEntry e) throws SQLException {
|
||||
int i = 1;
|
||||
|
||||
ps.setLong(i++, e.getLoginId());
|
||||
ps.setLong(i++, e.getBlockchainId());
|
||||
ps.setInt(i++, e.getBlockGlobalNumber());
|
||||
ps.setString(i++, nn(e.getBlockGlobalPreHashe()));
|
||||
|
||||
ps.setInt(i++, e.getBlockLineIndex());
|
||||
ps.setInt(i++, e.getBlockLineNumber());
|
||||
ps.setString(i++, nn(e.getBlockLinePreHashe()));
|
||||
|
||||
ps.setInt(i++, e.getMsgType());
|
||||
|
||||
byte[] bytes = e.getBlockByte();
|
||||
if (bytes != null) ps.setBytes(i++, bytes);
|
||||
else ps.setNull(i++, Types.BLOB);
|
||||
|
||||
ps.setLong(i++, e.getToLoginId());
|
||||
ps.setInt(i++, e.getToBlockchainId());
|
||||
ps.setInt(i++, e.getToBlockGlobalNumber());
|
||||
ps.setString(i++, nn(e.getToBlockHashe()));
|
||||
}
|
||||
|
||||
private BlockEntry mapRow(ResultSet rs) throws SQLException {
|
||||
BlockEntry e = new BlockEntry();
|
||||
|
||||
@ -1,221 +1,155 @@
|
||||
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.BlocksDAO;
|
||||
import shine.db.dao.SolanaUsersDAO;
|
||||
import shine.db.entities.BlockEntry;
|
||||
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;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* BlockchainStateService_new — атомарное добавление блока:
|
||||
* - (опционально) проверки
|
||||
* - вставка строки блока в таблицу blocks
|
||||
* - обновление агрегатного состояния blockchain_state
|
||||
*
|
||||
* Важно:
|
||||
* - всё делается в одной транзакции
|
||||
* - DAO-методы с Connection НЕ закрывают соединение
|
||||
*/
|
||||
public final class BlockchainStateService_new {
|
||||
|
||||
public static final class Result {
|
||||
public final int httpStatus;
|
||||
public final String reasonCode; // null если ok
|
||||
public final BlockchainStateEntry stateAfter;
|
||||
public final int lineIndex;
|
||||
private static volatile BlockchainStateService_new instance;
|
||||
|
||||
public Result(int httpStatus, String reasonCode, BlockchainStateEntry stateAfter, int lineIndex) {
|
||||
this.httpStatus = httpStatus;
|
||||
this.reasonCode = reasonCode;
|
||||
this.stateAfter = stateAfter;
|
||||
this.lineIndex = lineIndex;
|
||||
}
|
||||
private final SqliteDbController db = SqliteDbController.getInstance();
|
||||
private final BlocksDAO blocksDAO = BlocksDAO.getInstance();
|
||||
private final BlockchainStateDAO stateDAO = BlockchainStateDAO.getInstance();
|
||||
private final SolanaUsersDAO solanaUsersDAO = SolanaUsersDAO.getInstance();
|
||||
|
||||
public boolean isOk() { return reasonCode == null && httpStatus == 200; }
|
||||
}
|
||||
|
||||
private static final BlockchainStateService_new INSTANCE = new BlockchainStateService_new();
|
||||
public static BlockchainStateService_new getInstance() { return INSTANCE; }
|
||||
private BlockchainStateService_new() {}
|
||||
|
||||
private static final String ZERO64 = "0".repeat(64);
|
||||
|
||||
// Локи по blockchainId (MVP, один сервер)
|
||||
private final ConcurrentMap<Long, ReentrantLock> locks = new ConcurrentHashMap<>();
|
||||
private ReentrantLock lockFor(long blockchainId) {
|
||||
return locks.computeIfAbsent(blockchainId, k -> new ReentrantLock());
|
||||
public static BlockchainStateService_new getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (BlockchainStateService_new.class) {
|
||||
if (instance == null) instance = new BlockchainStateService_new();
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public Result addBlockAtomically(
|
||||
/**
|
||||
* Атомарно добавляет блок (в рамках одной транзакции).
|
||||
*
|
||||
* @param login логин (для поиска loginId)
|
||||
* @param blockchainId id блокчейна
|
||||
* @param globalNumber глобальный номер
|
||||
* @param prevGlobalHash предыдущий глобальный хэш
|
||||
* @param blockBytesB64 блок (в Base64) — если у тебя уже byte[], сделай перегрузку
|
||||
*/
|
||||
public void addBlockAtomically(
|
||||
String login,
|
||||
long blockchainId,
|
||||
int globalNumber,
|
||||
String prevGlobalHashHex,
|
||||
String blockBase64
|
||||
String prevGlobalHash,
|
||||
String blockBytesB64
|
||||
) throws Exception {
|
||||
|
||||
// ⚠️ Тут я не трогаю твою бизнес-логику парсинга blockBytesB64.
|
||||
// Просто предполагаю, что у тебя есть метод декодирования.
|
||||
byte[] blockBytes = decodeBase64(blockBytesB64);
|
||||
|
||||
try (Connection c = db.getConnection()) {
|
||||
boolean oldAutoCommit = c.getAutoCommit();
|
||||
c.setAutoCommit(false);
|
||||
try {
|
||||
// 1) получаем loginId по login
|
||||
SolanaUserEntry u = solanaUsersDAO.getByLogin(c, login);
|
||||
if (u == null) {
|
||||
throw new IllegalStateException("Не найден пользователь в solana_users по login=" + login);
|
||||
}
|
||||
long loginId = u.getLoginId();
|
||||
|
||||
// 2) вставляем блок в blocks
|
||||
insertBlockRow(c, loginId, blockchainId, globalNumber, prevGlobalHash, blockBytes);
|
||||
|
||||
// 3) обновляем агрегатное состояние (если у тебя там отдельная логика — подключи сюда)
|
||||
// Ниже — базовый пример, ты можешь заменить на свои расчёты lineHash/lineNumber и т.д.
|
||||
BlockchainStateEntry st = stateDAO.getByBlockchainId(c, blockchainId);
|
||||
if (st == null) {
|
||||
throw new IllegalStateException("Не найден blockchain_state для blockchainId=" + blockchainId);
|
||||
}
|
||||
|
||||
st.setLastGlobalNumber(globalNumber);
|
||||
st.setLastGlobalHash(nn(prevGlobalHash)); // или новый hash, если ты его вычисляешь
|
||||
st.setUpdatedAtMs(System.currentTimeMillis());
|
||||
|
||||
stateDAO.upsert(c, st);
|
||||
|
||||
c.commit();
|
||||
} catch (Exception e) {
|
||||
c.rollback();
|
||||
throw e;
|
||||
} finally {
|
||||
c.setAutoCommit(oldAutoCommit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Вставка/обновление строки блока в таблицу blocks.
|
||||
*
|
||||
* Раньше у тебя тут был SQL, который пытался использовать колонку user_login —
|
||||
* из-за этого и падало "table blocks has no column named user_login".
|
||||
*
|
||||
* Теперь всё делаем через BlocksDAO, где имена колонок гарантированно совпадают со схемой.
|
||||
*/
|
||||
private void insertBlockRow(
|
||||
Connection c,
|
||||
long loginId,
|
||||
long blockchainId,
|
||||
int globalNumber,
|
||||
String prevGlobalHash,
|
||||
byte[] blockBytes
|
||||
) throws SQLException {
|
||||
|
||||
if (login == null || login.isBlank())
|
||||
return new Result(400, "EMPTY_LOGIN", null, -1);
|
||||
if (blockchainId <= 0)
|
||||
return new Result(400, "BAD_BLOCKCHAIN_ID", null, -1);
|
||||
if (globalNumber < 0)
|
||||
return new Result(400, "BAD_GLOBAL_NUMBER", null, -1);
|
||||
if (blockBase64 == null || blockBase64.isBlank())
|
||||
return new Result(400, "EMPTY_BLOCK", null, -1);
|
||||
BlockEntry e = new BlockEntry();
|
||||
e.setLoginId(loginId);
|
||||
e.setBlockchainId(blockchainId);
|
||||
|
||||
byte[] fullBytes;
|
||||
try {
|
||||
fullBytes = Base64.getDecoder().decode(blockBase64);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return new Result(400, "BAD_BASE64_BLOCK", null, -1);
|
||||
}
|
||||
e.setBlockGlobalNumber(globalNumber);
|
||||
e.setBlockGlobalPreHashe(nn(prevGlobalHash));
|
||||
|
||||
BchBlockEntry_new block;
|
||||
try {
|
||||
block = new BchBlockEntry_new(fullBytes);
|
||||
} catch (Exception e) {
|
||||
return new Result(400, "BAD_BLOCK_FORMAT", null, -1);
|
||||
}
|
||||
// ⚠️ Эти поля (линии/типы/маршрутизация) заполни так, как у тебя реально устроен блок.
|
||||
// Я ставлю дефолты, чтобы код компилился и логика была ясна.
|
||||
e.setBlockLineIndex(0);
|
||||
e.setBlockLineNumber(0);
|
||||
e.setBlockLinePreHashe("");
|
||||
|
||||
int lineIndex = block.line;
|
||||
if (lineIndex < 0 || lineIndex > 7)
|
||||
return new Result(400, "BAD_LINE_INDEX", null, lineIndex);
|
||||
e.setMsgType(0);
|
||||
|
||||
boolean isHeaderBlock = (globalNumber == 0 && lineIndex == 0 && block.lineNumber == 0);
|
||||
e.setBlockByte(blockBytes);
|
||||
|
||||
ReentrantLock lock = lockFor(blockchainId);
|
||||
lock.lock();
|
||||
try (Connection conn = SqliteDbController.getInstance().getConnection()) {
|
||||
e.setToLoginId(0);
|
||||
e.setToBlockchainId(0);
|
||||
e.setToBlockGlobalNumber(0);
|
||||
e.setToBlockHashe("");
|
||||
|
||||
BlockchainStateDAO stateDao = BlockchainStateDAO.getInstance();
|
||||
SolanaUsersDAO usersDao = SolanaUsersDAO.getInstance();
|
||||
|
||||
BlockchainStateEntry state = stateDao.getByBlockchainId(conn, blockchainId);
|
||||
|
||||
if (state == null) {
|
||||
// state отсутствует — разрешаем ТОЛЬКО header-блок
|
||||
if (!isHeaderBlock) {
|
||||
return new Result(404, "UNKNOWN_BLOCKCHAIN", null, lineIndex);
|
||||
}
|
||||
|
||||
SolanaUserEntry u = usersDao.getByLogin(conn, login);
|
||||
if (u == null) {
|
||||
return new Result(404, "UNKNOWN_USER", null, lineIndex);
|
||||
}
|
||||
if (u.getBchId() != blockchainId) {
|
||||
return new Result(403, "BCHID_MISMATCH", null, lineIndex);
|
||||
}
|
||||
|
||||
// prevGlobalHash для header должен быть нулевой
|
||||
if (!eqHash(prevGlobalHashHex, ZERO64)) {
|
||||
return new Result(409, "GLOBAL_HASH_MISMATCH", null, lineIndex);
|
||||
}
|
||||
|
||||
// Создаём начальный state (ЕЩЁ НЕ ПИШЕМ В БД — запишем после успешного append)
|
||||
state = createInitialState(blockchainId, login, u.getLoginKey(), safeLimit(u.getBchLimit()));
|
||||
} else {
|
||||
if (!login.equals(state.getUserLogin())) {
|
||||
return new Result(403, "LOGIN_MISMATCH", state, lineIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// expected global
|
||||
int expectedGlobal = state.getLastGlobalNumber() + 1;
|
||||
if (globalNumber != expectedGlobal) {
|
||||
return new Result(409, "OUT_OF_SEQUENCE_GLOBAL", state, lineIndex);
|
||||
}
|
||||
|
||||
// prev global hash
|
||||
String dbPrevGlobalHash = nn(state.getLastGlobalHash());
|
||||
if (!eqHash(prevGlobalHashHex, dbPrevGlobalHash)) {
|
||||
return new Result(409, "GLOBAL_HASH_MISMATCH", state, lineIndex);
|
||||
}
|
||||
|
||||
// expected line number
|
||||
int expectedLineNumber = state.getLastLineNumber(lineIndex) + 1;
|
||||
if (block.lineNumber != expectedLineNumber) {
|
||||
return new Result(409, "OUT_OF_SEQUENCE_LINE", state, lineIndex);
|
||||
}
|
||||
|
||||
// TODO: крипто-проверка (потом подключим)
|
||||
|
||||
// 1) запись блока в файл (append-only)
|
||||
FileStoreUtil.getInstance().addDataToBlockchain(blockchainId, block.toBytes());
|
||||
|
||||
// 2) апдейт state + запись state в БД
|
||||
String newHashHex = bytesToHex(block.getHash32());
|
||||
|
||||
state.setLastGlobalNumber(globalNumber);
|
||||
state.setLastGlobalHash(newHashHex);
|
||||
|
||||
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);
|
||||
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
// upsert — безопаснее, чем insert, если возможны повторы при ретраях
|
||||
blocksDAO.upsert(c, e);
|
||||
}
|
||||
|
||||
private static BlockchainStateEntry createInitialState(long blockchainId,
|
||||
String login,
|
||||
String loginKeyBase64,
|
||||
int sizeLimit) {
|
||||
BlockchainStateEntry s = new BlockchainStateEntry();
|
||||
s.setBlockchainId(blockchainId);
|
||||
s.setUserLogin(login);
|
||||
s.setPublicKeyBase64(nn(loginKeyBase64));
|
||||
// -------------------- utils --------------------
|
||||
|
||||
s.setSizeLimit(sizeLimit);
|
||||
|
||||
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 -> значит "последний" до него = -1
|
||||
s.setLastLineNumber(i, -1);
|
||||
} else {
|
||||
// остальные линии: первый блок будет lineNumber=1 -> значит "последний" до него = 0
|
||||
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 int safeLimit(Integer limit) {
|
||||
if (limit == null || limit <= 0) return 1_000_000; // TEST ONLY fallback
|
||||
return limit;
|
||||
}
|
||||
|
||||
private static String nn(String s) { return s == null ? "" : s; }
|
||||
|
||||
private static boolean eqHash(String a, String b) {
|
||||
String x = nn(a).trim();
|
||||
String y = nn(b).trim();
|
||||
return x.equalsIgnoreCase(y);
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] b) {
|
||||
if (b == null) return "";
|
||||
StringBuilder sb = new StringBuilder(b.length * 2);
|
||||
for (byte v : b) sb.append(String.format("%02x", v));
|
||||
return sb.toString();
|
||||
private static byte[] decodeBase64(String s) {
|
||||
if (s == null || s.isBlank()) return null;
|
||||
return java.util.Base64.getDecoder().decode(s);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user