Дорабатываю добавление блоков. Промежуточный комит2.Омталось созранение стате в бд поправить
This commit is contained in:
AidarKC 2025-12-25 14:32:58 +03:00
parent e1b2c62231
commit d460ea2952
5 changed files with 364 additions and 226 deletions

View File

@ -6,40 +6,29 @@ import java.util.Objects;
/** /**
* =============================================================== * ===============================================================
* FileStoreUtil синглтон-утилита для записи/дозаписи/чтения файлов. * FileStoreUtil утилита работы с файлами в папке data/.
* --------------------------------------------------------------- *
* Где хранит: * Теперь поддерживает:
* Все файлы размещаются в внешней папке DATA_DIR = "data" (в корне запуска). * - основной файл блокчейна: <blockchainName>.bch
* Папка создаётся автоматически при первом обращении. * - временный файл блокчейна: <blockchainName>.tmp_bch
*. *
* Что умеет: * Важное:
* newFile(String fileName, byte[] data) * - validateSimpleFileName() запрещает path traversal.
* - создаёт/переписывает файл с именем fileName и записывает data. * - atomicReplaceBlockchainFile(): пытается сделать ATOMIC_MOVE (если ФС поддерживает),
* addDataToFile(String fileName, byte[] data) * иначе делает обычный REPLACE_EXISTING move.
* - дописывает data в конец файла (создаст файл, если его ещё нет).
* readAllDataFromFile(String fileName)
* - читает весь файл целиком и возвращает содержимое в виде byte[].
*.
* Обёртки под «блокчейны»:
* newBlockchain(long blockchainId, byte[] data)
* addDataToBlockchain(long blockchainId, byte[] data)
* readAllDataFromBlockchain(long blockchainId)
* - те же операции, но имя файла формируется из blockchainId и расширения ".bch".
*.
* Безопасность имён:
* Внутри утилиты есть простая валидация имени файла: запрещены разделители путей,
* чтобы исключить выход из каталога data (path traversal).
*.
* Совместимость: Java 17.
* =============================================================== * ===============================================================
*/ */
public final class FileStoreUtil { public final class FileStoreUtil {
/** Базовая папка для хранения всех файлов (создаётся автоматически). */ /** Базовая папка для хранения всех файлов (создаётся автоматически). */
public static final String DATA_DIR_NAME = "data"; public static final String DATA_DIR_NAME = "data";
/** Расширение файлов «блокчейнов». */
/** Расширение основного файла блокчейна. */
public static final String BLOCKCHAIN_FILE_EXTENSION = ".bch"; public static final String BLOCKCHAIN_FILE_EXTENSION = ".bch";
/** Расширение временного файла (старое+новое). */
public static final String BLOCKCHAIN_TMP_EXTENSION = ".tmp_bch";
private static final FileStoreUtil INSTANCE = new FileStoreUtil(); private static final FileStoreUtil INSTANCE = new FileStoreUtil();
private final Path dataDirPath; private final Path dataDirPath;
@ -49,56 +38,40 @@ public final class FileStoreUtil {
ensureDataDirExists(); ensureDataDirExists();
} }
/** Получить единственный экземпляр утилиты. */
public static FileStoreUtil getInstance() { public static FileStoreUtil getInstance() {
return INSTANCE; return INSTANCE;
} }
// =============================================================== /* ===================================================================== */
// ОБЩИЕ МЕТОДЫ РАБОТЫ С ФАЙЛОМ /* ======================== Базовые операции =========================== */
// =============================================================== /* ===================================================================== */
/**
* Создать/переписать файл и записать в него массив байт.
* @param fileName имя файла (без каталогов)
* @param data содержимое
* @throws IllegalArgumentException при неверном имени или null-данных
* @throws IllegalStateException при ошибках ввода/вывода
*/
public void newFile(String fileName, byte[] data) { public void newFile(String fileName, byte[] data) {
Objects.requireNonNull(data, "Данные не должны быть null"); Objects.requireNonNull(data, "data == null");
Path target = resolveSafe(fileName); Path target = resolveSafe(fileName);
try { try {
Files.write(target, data, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); Files.write(target, data,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.WRITE);
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Не удалось записать файл: " + target, e); throw new IllegalStateException("Не удалось записать файл: " + target, e);
} }
} }
/**
* Дозаписать массив байт в конец файла (создаст файл, если отсутствует).
* @param fileName имя файла (без каталогов)
* @param data добавляемые данные
* @throws IllegalArgumentException при неверном имени или null-данных
* @throws IllegalStateException при ошибках ввода/вывода
*/
public void addDataToFile(String fileName, byte[] data) { public void addDataToFile(String fileName, byte[] data) {
Objects.requireNonNull(data, "Данные не должны быть null"); Objects.requireNonNull(data, "data == null");
Path target = resolveSafe(fileName); Path target = resolveSafe(fileName);
try { try {
Files.write(target, data, Files.write(target, data,
StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND); StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.APPEND);
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Не удалось дописать файл: " + target, e); throw new IllegalStateException("Не удалось дописать файл: " + target, e);
} }
} }
/**
* Прочитать весь файл в память и вернуть как byte[].
* @param fileName имя файла (без каталогов)
* @return содержимое файла
* @throws IllegalStateException если файл не существует или ошибка ввода/вывода
*/
public byte[] readAllDataFromFile(String fileName) { public byte[] readAllDataFromFile(String fileName) {
Path target = resolveSafe(fileName); Path target = resolveSafe(fileName);
if (!Files.exists(target)) { if (!Files.exists(target)) {
@ -111,37 +84,91 @@ public final class FileStoreUtil {
} }
} }
// =============================================================== public boolean exists(String fileName) {
// ОБЁРТКИ ДЛЯ «БЛОКЧЕЙН-ФАЙЛОВ» Path target = resolveSafe(fileName);
// =============================================================== return Files.exists(target);
}
/** public long size(String fileName) {
* Обёртка над newFile: имя формируется из blockchainId + ".bch". Path target = resolveSafe(fileName);
*/ try {
public void newBlockchain(long blockchainId, byte[] data) { return Files.size(target);
String fileName = buildBlockchainFileName(blockchainId); } catch (IOException e) {
newFile(fileName, data); throw new IllegalStateException("Не удалось получить размер файла: " + target, e);
}
}
/* ===================================================================== */
/* ===================== Блокчейн-файлы по имени ======================= */
/* ===================================================================== */
/** <blockchainName>.bch */
public String buildBlockchainFileName(String blockchainName) {
validateSimpleFileName(blockchainName);
return blockchainName + BLOCKCHAIN_FILE_EXTENSION;
}
/** <blockchainName>.tmp_bch */
public String buildBlockchainTmpFileName(String blockchainName) {
validateSimpleFileName(blockchainName);
return blockchainName + BLOCKCHAIN_TMP_EXTENSION;
}
public Path resolveBlockchainPath(String blockchainName) {
return resolveSafe(buildBlockchainFileName(blockchainName));
}
public Path resolveBlockchainTmpPath(String blockchainName) {
return resolveSafe(buildBlockchainTmpFileName(blockchainName));
}
public byte[] readBlockchain(String blockchainName) {
return readAllDataFromFile(buildBlockchainFileName(blockchainName));
}
public void writeBlockchainTmp(String blockchainName, byte[] data) {
newFile(buildBlockchainTmpFileName(blockchainName), data);
} }
/** /**
* Обёртка над addDataToFile: имя формируется из blockchainId + ".bch". * Атомарно заменить основной файл блокчейна временным:
* <name>.tmp_bch -> <name>.bch
*
* Стратегия:
* 1) Пытаемся Files.move(..., ATOMIC_MOVE, REPLACE_EXISTING)
* 2) Если ATOMIC_MOVE не поддерживается делаем move с REPLACE_EXISTING без атомарности
*
* Важный нюанс:
* - атомарность гарантируется только в пределах одной файловой системы.
*/ */
public void addDataToBlockchain(long blockchainId, byte[] data) { public void atomicReplaceBlockchainFile(String blockchainName) {
String fileName = buildBlockchainFileName(blockchainId); Path tmp = resolveBlockchainTmpPath(blockchainName);
addDataToFile(fileName, data); Path main = resolveBlockchainPath(blockchainName);
if (!Files.exists(tmp)) {
throw new IllegalStateException("TMP-файл не найден: " + tmp);
} }
/** try {
* Обёртка над readAllDataFromFile: имя формируется из blockchainId + ".bch". // 1) Пытаемся атомарный move
*/ Files.move(tmp, main,
public byte[] readAllDataFromBlockchain(long blockchainId) { StandardCopyOption.REPLACE_EXISTING,
String fileName = buildBlockchainFileName(blockchainId); StandardCopyOption.ATOMIC_MOVE);
return readAllDataFromFile(fileName); } catch (AtomicMoveNotSupportedException e) {
// 2) Если ФС не поддерживает атомарный move делаем обычный replace
try {
Files.move(tmp, main, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException ex) {
throw new IllegalStateException("Не удалось заменить файл блокчейна (non-atomic): " + main, ex);
}
} catch (IOException e) {
throw new IllegalStateException("Не удалось заменить файл блокчейна (atomic): " + main, e);
}
} }
// =============================================================== /* ===================================================================== */
// ВСПОМОГАТЕЛЬНЫЕ /* ============================ Helpers ================================= */
// =============================================================== /* ===================================================================== */
private void ensureDataDirExists() { private void ensureDataDirExists() {
try { try {
@ -153,20 +180,21 @@ public final class FileStoreUtil {
} }
} }
/**
* Безопасно собрать путь внутри каталога data, запретив подстановку каталогов в имени файла.
*/
private Path resolveSafe(String fileName) { private Path resolveSafe(String fileName) {
validateSimpleFileName(fileName); validateSimpleFileName(fileName);
return dataDirPath.resolve(fileName); return dataDirPath.resolve(fileName);
} }
/** /**
* Простейшая валидация имени файла: * Валидация "простого имени":
* запретить разделители путей и возврат на родительский каталог. * - запрещаем слэши, обратные слэши, ".."
* - запрещаем пустоту
*
* Важно: сюда у нас попадает и blockchainName (как часть имени файла),
* поэтому blockchainName должен быть "простым": без путей.
*/ */
private void validateSimpleFileName(String fileName) { private void validateSimpleFileName(String fileName) {
Objects.requireNonNull(fileName, "Имя файла не должно быть null"); Objects.requireNonNull(fileName, "fileName == null");
if (fileName.isBlank()) { if (fileName.isBlank()) {
throw new IllegalArgumentException("Имя файла не должно быть пустым"); throw new IllegalArgumentException("Имя файла не должно быть пустым");
} }
@ -174,12 +202,4 @@ public final class FileStoreUtil {
throw new IllegalArgumentException("Недопустимое имя файла: " + fileName); throw new IllegalArgumentException("Недопустимое имя файла: " + fileName);
} }
} }
/**
* Построить имя «блокчейн-файла» из идентификатора и расширения .bch.
* Пример: 12345 "12345.bch"
*/
private String buildBlockchainFileName(long blockchainId) {
return Long.toString(blockchainId) + BLOCKCHAIN_FILE_EXTENSION;
}
} }

View File

@ -1,59 +1,59 @@
package utils.files; //package utils.files;
//
import java.nio.charset.StandardCharsets; //import java.nio.charset.StandardCharsets;
import java.util.Arrays; //import java.util.Arrays;
//
/** ///**
* =============================================================== // * ===============================================================
* FileStoreUtilSelfTest запускаемый тест утилиты FileStoreUtil. // * FileStoreUtilSelfTest запускаемый тест утилиты FileStoreUtil.
* --------------------------------------------------------------- // * ---------------------------------------------------------------
* Сценарий: // * Сценарий:
* 1) Создаём «блокчейн-файл» для id=20251021 с начальными данными. // * 1) Создаём «блокчейн-файл» для id=20251021 с начальными данными.
* 2) Дозаписываем ещё порцию данных. // * 2) Дозаписываем ещё порцию данных.
* 3) Читаем целиком и печатаем длину + превью. // * 3) Читаем целиком и печатаем длину + превью.
*. // *.
* Ожидаемый итог: // * Ожидаемый итог:
* В папке "data" появится файл "20251021.bch" // * В папке "data" появится файл "20251021.bch"
* В консоли будет длина содержимого и небольшой превью-дамп. // * В консоли будет длина содержимого и небольшой превью-дамп.
* =============================================================== // * ===============================================================
*/ // */
public class FileStoreUtilSelfTest { //public class FileStoreUtilSelfTest {
//
public static void main(String[] args) { // public static void main(String[] args) {
System.out.println("=== FileStoreUtil self-test ==="); // System.out.println("=== FileStoreUtil self-test ===");
//
FileStoreUtil fs = FileStoreUtil.getInstance(); // FileStoreUtil fs = FileStoreUtil.getInstance();
//
long blockchainId = 20251021L; // long blockchainId = 20251021L;
//
byte[] part1 = "Hello ".getBytes(StandardCharsets.UTF_8); // byte[] part1 = "Hello ".getBytes(StandardCharsets.UTF_8);
byte[] part2 = "Blockchain!".getBytes(StandardCharsets.UTF_8); // byte[] part2 = "Blockchain!".getBytes(StandardCharsets.UTF_8);
//
// 1) создаём новый файл для «блокчейна» // // 1) создаём новый файл для «блокчейна»
fs.newBlockchain(blockchainId, part1); // fs.newBlockchain(blockchainId, part1);
//
// 2) дозаписываем данные // // 2) дозаписываем данные
fs.addDataToBlockchain(blockchainId, part2); // fs.addDataToBlockchain(blockchainId, part2);
//
// 3) читаем всё содержимое и показываем превью // // 3) читаем всё содержимое и показываем превью
byte[] all = fs.readAllDataFromBlockchain(blockchainId); // byte[] all = fs.readAllDataFromBlockchain(blockchainId);
System.out.println("Total bytes read: " + all.length); // System.out.println("Total bytes read: " + all.length);
System.out.println("Preview (UTF-8): " + new String(all, StandardCharsets.UTF_8)); // System.out.println("Preview (UTF-8): " + new String(all, StandardCharsets.UTF_8));
//
// небольшой hex-дамп первых 32 байт (для наглядности) // // небольшой hex-дамп первых 32 байт (для наглядности)
System.out.println("Preview (HEX 32B): " + toHexPreview(all, 32)); // System.out.println("Preview (HEX 32B): " + toHexPreview(all, 32));
//
System.out.println("✅ Self-test passed (файл: data/" + blockchainId + FileStoreUtil.BLOCKCHAIN_FILE_EXTENSION + ")"); // System.out.println("✅ Self-test passed (файл: data/" + blockchainId + FileStoreUtil.BLOCKCHAIN_FILE_EXTENSION + ")");
} // }
//
private static String toHexPreview(byte[] data, int max) { // private static String toHexPreview(byte[] data, int max) {
int n = Math.min(data.length, max); // int n = Math.min(data.length, max);
StringBuilder sb = new StringBuilder(n * 2); // StringBuilder sb = new StringBuilder(n * 2);
for (int i = 0; i < n; i++) { // for (int i = 0; i < n; i++) {
sb.append(String.format("%02X", data[i])); // sb.append(String.format("%02X", data[i]));
if (i + 1 < n) sb.append(' '); // if (i + 1 < n) sb.append(' ');
} // }
if (data.length > n) sb.append(" ..."); // if (data.length > n) sb.append(" ...");
return sb.toString(); // return sb.toString();
} // }
} //}

View File

@ -6,29 +6,46 @@ import shine.db.dao.BlockchainStateDAO;
import shine.db.dao.BlocksDAO; import shine.db.dao.BlocksDAO;
import shine.db.entities.BlockEntry; import shine.db.entities.BlockEntry;
import shine.db.entities.BlockchainStateEntry; import shine.db.entities.BlockchainStateEntry;
import utils.files.FileStoreUtil;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
/** /**
* BlockchainDbWriter единая точка записи блока + состояния в БД. * BlockchainWriter единая точка записи:
* 1) создаём новый файл <name>.tmp_bch = oldFileBytes + newBlockBytes
* 2) атомарно фиксируем в БД:
* - blocks (строка блока)
* - blockchain_state (включая новый fileSizeBytes)
* 3) атомарно заменяем файл:
* - удаляем/замещаем старый <name>.bch
* - переименовываем <name>.tmp_bch -> <name>.bch
* *
* Важно: * Важно:
* - Здесь обеспечивается атомарность записи: либо вставился блок и обновилось состояние, либо не вставилось ничего. * - Шаг (2) строго атомарный (SQL tx).
* - Соединение открывается/закрывается внутри (удобно для хэндлера). * - Шаг (3) атомарный на уровне ФС, если поддерживается ATOMIC_MOVE.
* - Если сервер упадёт между (2) и (3), останется tmp твой recovery при старте починит.
*/ */
public final class BlockchainWriter { public final class BlockchainWriter {
private final SqliteDbController db; private final SqliteDbController db;
private final BlocksDAO blocksDAO; private final BlocksDAO blocksDAO;
private final BlockchainStateDAO stateDAO; private final BlockchainStateDAO stateDAO;
private final FileStoreUtil fs;
public BlockchainWriter(BlocksDAO blocksDAO, BlockchainStateDAO stateDAO) { public BlockchainWriter(BlocksDAO blocksDAO, BlockchainStateDAO stateDAO) {
this.db = SqliteDbController.getInstance(); this.db = SqliteDbController.getInstance();
this.blocksDAO = blocksDAO; this.blocksDAO = blocksDAO;
this.stateDAO = stateDAO; this.stateDAO = stateDAO;
this.fs = FileStoreUtil.getInstance();
} }
/**
* Главный метод:
* - создаёт tmp-файл (старое+новое),
* - атомарно коммитит БД (block+state),
* - атомарно заменяет основной файл.
*/
public void appendBlockAndState( public void appendBlockAndState(
String login, String login,
String blockchainName, String blockchainName,
@ -38,43 +55,126 @@ public final class BlockchainWriter {
String newHashHex String newHashHex
) throws SQLException { ) throws SQLException {
// =====================================================================
// ШАГ 1. Готовим bytes нового блока (включая signature+hash)
// =====================================================================
final byte[] newBlockFullBytes = block.toBytes(); // включает хвост signature+hash
// =====================================================================
// ШАГ 2. Считаем новый fileSizeBytes
// - если genesis (state == null): старый размер = 0
// - иначе берём st.fileSizeBytes
// =====================================================================
final long oldFileSize = (stOrNull == null) ? 0L : stOrNull.getFileSizeBytes();
final long newFileSize = safeAdd(oldFileSize, newBlockFullBytes.length);
// =====================================================================
// ШАГ 3. Создаём новый tmp-файл:
// tmp = (old file bytes) + (new block bytes)
//
// Важно:
// - читаем старый файл ТОЛЬКО если state не null и size > 0
// - если genesis: старого файла нет => tmp = newBlock
// =====================================================================
final byte[] tmpBytes;
if (stOrNull == null || oldFileSize == 0) {
// genesis: tmp = только новый блок
tmpBytes = newBlockFullBytes;
} else {
// не genesis: tmp = старый файл + новый блок
byte[] oldBytes;
try {
oldBytes = fs.readBlockchain(blockchainName);
} catch (Exception e) {
// Здесь лучше падать: state говорит, что файл есть, а прочитать нельзя.
throw new SQLException("Cannot read old blockchain file for: " + blockchainName, e);
}
// (на будущее) можно проверять согласованность: oldBytes.length == oldFileSize
// но ты всё равно будешь делать recovery при старте оставим как подсказку.
tmpBytes = concat(oldBytes, newBlockFullBytes);
}
// Пишем tmp на диск ДО транзакции БД:
// - если сервер упадёт позже tmp останется, но БД может не успеть обновиться (это ок для recovery)
try {
fs.writeBlockchainTmp(blockchainName, tmpBytes);
} catch (Exception e) {
throw new SQLException("Cannot write tmp blockchain file for: " + blockchainName, e);
}
// =====================================================================
// ШАГ 4. АТОМАРНО фиксируем БД:
// - UPSERT blocks
// - UPSERT blockchain_state (включая fileSizeBytes = newFileSize)
// =====================================================================
try (Connection c = db.getConnection()) { try (Connection c = db.getConnection()) {
boolean oldAutoCommit = c.getAutoCommit(); boolean oldAutoCommit = c.getAutoCommit();
c.setAutoCommit(false); c.setAutoCommit(false);
boolean committed = false;
try { try {
// 1) блок // 4.1) вставляем/апдейтим запись блока
insertBlockRow(c, login, blockchainName, prevGlobalHashHex, block); insertBlockRow(c, login, blockchainName, prevGlobalHashHex, block);
// 2) state // 4.2) апдейтим состояние (включая fileSizeBytes)
appendState(c, blockchainName, block.recordNumber, stOrNull, newHashHex); appendState(c, blockchainName, block.recordNumber, stOrNull, newHashHex, newFileSize);
// 3) commit // 4.3) commit
c.commit(); c.commit();
committed = true;
} catch (Exception e) { } catch (Exception e) {
try { c.rollback(); } catch (SQLException ignore) {} try { c.rollback(); } catch (SQLException ignore) {}
if (e instanceof SQLException se) throw se; if (e instanceof SQLException se) throw se;
throw new SQLException("appendBlockAndState failed", e); throw new SQLException("appendBlockAndState failed (db tx)", e);
} finally { } finally {
try { c.setAutoCommit(oldAutoCommit); } catch (SQLException ignore) {} try { c.setAutoCommit(oldAutoCommit); } catch (SQLException ignore) {}
} }
// =================================================================
// ШАГ 5. После успешного коммита БД атомарно заменяем файл:
// <name>.tmp_bch -> <name>.bch
//
// Если тут упадём:
// - БД уже обновлена
// - tmp остаётся
// - recovery при старте восстановит консистентность
// =================================================================
if (committed) {
try {
fs.atomicReplaceBlockchainFile(blockchainName);
} catch (Exception moveError) {
// Здесь ВАЖНО: мы уже не можем откатить БД.
// Оставляем tmp и даём наверх ошибку клиент увидит internal_error,
// а ты при старте починишь файловую часть.
throw new SQLException(
"DB committed but file replace failed; tmp kept for recovery. blockchainName=" + blockchainName,
moveError
);
}
}
} }
} }
/** /**
* Обновление состояния blockchain_state (создаём если отсутствует). * Обновление состояния blockchain_state (создаём если отсутствует).
* Пока линии не используются: lineIndex=0 и lineHash = globalHash. * Пока линии не используются: lineIndex=0 и lineHash = globalHash.
*
* + обновляем fileSizeBytes
*/ */
private void appendState( private void appendState(
Connection c, Connection c,
String blockchainName, String blockchainName,
int globalNumber, int globalNumber,
BlockchainStateEntry stOrNull, BlockchainStateEntry stOrNull,
String newHashHex String newHashHex,
long newFileSizeBytes
) throws SQLException { ) throws SQLException {
BlockchainStateEntry st = stOrNull; BlockchainStateEntry st = stOrNull;
@ -87,14 +187,17 @@ public final class BlockchainWriter {
st.setLastGlobalNumber(globalNumber); st.setLastGlobalNumber(globalNumber);
st.setLastGlobalHash(newHashHex); st.setLastGlobalHash(newHashHex);
// Линии пока не используются: lineIndex=0 и lineHash=globalHash // Линии пока не используются
st.setLastLineNumber(0, globalNumber); st.setLastLineNumber(0, globalNumber);
st.setLastLineHash(0, newHashHex); st.setLastLineHash(0, newHashHex);
// ВАЖНО: сохраняем ожидаемый размер файла
st.setFileSizeBytes(newFileSizeBytes);
// Метка времени обновления // Метка времени обновления
st.setUpdatedAtMs(System.currentTimeMillis()); st.setUpdatedAtMs(System.currentTimeMillis());
// UPSERT состояния // UPSERT
stateDAO.upsert(c, st); stateDAO.upsert(c, st);
} }
@ -111,33 +214,48 @@ public final class BlockchainWriter {
BlockEntry e = new BlockEntry(); BlockEntry e = new BlockEntry();
// Кому принадлежит блок (логин владельца цепочки)
e.setLogin(login); e.setLogin(login);
e.setBchName(blockchainName); e.setBchName(blockchainName);
// Глобальная нумерация
e.setBlockGlobalNumber(block.recordNumber); e.setBlockGlobalNumber(block.recordNumber);
e.setBlockGlobalPreHashe(prevGlobalHashHex); e.setBlockGlobalPreHashe(prevGlobalHashHex);
// Линии пока не используются: lineIndex=0, lineNumber=globalNumber // линии пока не используем
e.setBlockLineIndex(0); e.setBlockLineIndex(0);
e.setBlockLineNumber(block.recordNumber); e.setBlockLineNumber(block.recordNumber);
e.setBlockLinePreHashe(prevGlobalHashHex); e.setBlockLinePreHashe(prevGlobalHashHex);
// msgType у тебя пока 0 (при желании позже можно ставить по Body/type) // тип сообщения по body.type()
// Теперь сохраняем тип блока
e.setMsgType(block.body.type()); e.setMsgType(block.body.type());
// Сырые байты полного блока // полный блок (RAW + signature + hash)
e.setBlockByte(block.toBytes()); e.setBlockByte(block.toBytes());
// Поля "кому" (для сообщений/трансферов) пока пустые
e.setToLogin(null); e.setToLogin(null);
e.setToBchName(null); e.setToBchName(null);
e.setToBlockGlobalNumber(null); e.setToBlockGlobalNumber(null);
e.setToBlockHashe(null); e.setToBlockHashe(null);
// UPSERT блока
blocksDAO.upsert(c, e); blocksDAO.upsert(c, e);
} }
/* ===================================================================== */
/* =============================== Utils ================================ */
/* ===================================================================== */
private static byte[] concat(byte[] a, byte[] b) {
byte[] out = new byte[a.length + b.length];
System.arraycopy(a, 0, out, 0, a.length);
System.arraycopy(b, 0, out, a.length, b.length);
return out;
}
private static long safeAdd(long x, long y) {
// защита от переполнения long (маловероятно, но пусть будет)
long r = x + y;
if (((x ^ r) & (y ^ r)) < 0) {
throw new IllegalArgumentException("fileSizeBytes overflow: " + x + " + " + y);
}
return r;
}
} }

View File

@ -18,9 +18,9 @@ public final class InboundMessageProcessor {
private static final Logger log = LoggerFactory.getLogger(InboundMessageProcessor.class); private static final Logger log = LoggerFactory.getLogger(InboundMessageProcessor.class);
private static final Map<Integer, MessageHandler> HANDLERS = Map.of( private static final Map<Integer, MessageHandler> HANDLERS = Map.of(
WireCodes.Op.PING, new PingHandler(), WireCodes.Op.PING, new PingHandler()
// WireCodes.Op.ADD_BLOCK, new AddBlockHandler(), // WireCodes.Op.ADD_BLOCK, new AddBlockHandler(),
WireCodes.Op.GET_BLOCKCHAIN,new GetBlockchainHandler() // WireCodes.Op.GET_BLOCKCHAIN,new GetBlockchainHandler()
// WireCodes.Op.SEARCH_USERS, new SearchUsersHandler(), // WireCodes.Op.SEARCH_USERS, new SearchUsersHandler(),
// WireCodes.Op.GET_LAST_BLOCK_INFO,new GetLastBlockInfoHandler() // WireCodes.Op.GET_LAST_BLOCK_INFO,new GetLastBlockInfoHandler()

View File

@ -1,53 +1,53 @@
package server.logic.ws_protocol.binary.handlers; //package server.logic.ws_protocol.binary.handlers;
//
import org.slf4j.Logger; //import org.slf4j.Logger;
import org.slf4j.LoggerFactory; //import org.slf4j.LoggerFactory;
import server.logic.ws_protocol.WireCodes; //import server.logic.ws_protocol.WireCodes;
import utils.files.FileStoreUtil; //import utils.files.FileStoreUtil;
//
import java.nio.ByteBuffer; //import java.nio.ByteBuffer;
import java.nio.ByteOrder; //import java.nio.ByteOrder;
//
/** ///**
* Возврат полного содержимого блокчейна (GET_BLOCKCHAIN). // * Возврат полного содержимого блокчейна (GET_BLOCKCHAIN).
*/ // */
public class GetBlockchainHandler implements MessageHandler { //public class GetBlockchainHandler implements MessageHandler {
private static final Logger log = LoggerFactory.getLogger(GetBlockchainHandler.class); // private static final Logger log = LoggerFactory.getLogger(GetBlockchainHandler.class);
//
@Override // @Override
public byte[] handle(byte[] msg) { // public byte[] handle(byte[] msg) {
try { // try {
if (msg.length < 12) // if (msg.length < 12)
return intTo4Bytes(WireCodes.Status.BAD_REQUEST); // return intTo4Bytes(WireCodes.Status.BAD_REQUEST);
//
long id = ByteBuffer.wrap(msg, 4, 8) // long id = ByteBuffer.wrap(msg, 4, 8)
.order(ByteOrder.BIG_ENDIAN) // .order(ByteOrder.BIG_ENDIAN)
.getLong(); // .getLong();
//
FileStoreUtil fs = FileStoreUtil.getInstance(); // FileStoreUtil fs = FileStoreUtil.getInstance();
byte[] data = fs.readAllDataFromBlockchain(id); // byte[] data = fs.readAllDataFromBlockchain(id);
//
return packOk(data); // return packOk(data);
//
} catch (IllegalStateException e) { // } catch (IllegalStateException e) {
log.warn("GET_BLOCKCHAIN: файл не найден ({})", e.getMessage()); // log.warn("GET_BLOCKCHAIN: файл не найден ({})", e.getMessage());
return intTo4Bytes(WireCodes.Status.CHAIN_NOT_FOUND); // return intTo4Bytes(WireCodes.Status.CHAIN_NOT_FOUND);
} catch (Exception e) { // } catch (Exception e) {
log.error("GET_BLOCKCHAIN: ошибка", e); // log.error("GET_BLOCKCHAIN: ошибка", e);
return intTo4Bytes(WireCodes.Status.INTERNAL_ERROR); // return intTo4Bytes(WireCodes.Status.INTERNAL_ERROR);
} // }
} // }
//
private static byte[] packOk(byte[] data) { // private static byte[] packOk(byte[] data) {
if (data == null) data = new byte[0]; // if (data == null) data = new byte[0];
ByteBuffer out = ByteBuffer.allocate(8 + data.length).order(ByteOrder.BIG_ENDIAN); // ByteBuffer out = ByteBuffer.allocate(8 + data.length).order(ByteOrder.BIG_ENDIAN);
out.putInt(WireCodes.Status.OK); // out.putInt(WireCodes.Status.OK);
out.putInt(data.length); // out.putInt(data.length);
out.put(data); // out.put(data);
return out.array(); // return out.array();
} // }
//
private static byte[] intTo4Bytes(int code) { // private static byte[] intTo4Bytes(int code) {
return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(code).array(); // return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(code).array();
} // }
} //}