From f8cc12560e2e416f43b6b4408879b7f5d37751a1e9de8e8171913abf642911a0 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Thu, 25 Dec 2025 16:00:57 +0300 Subject: [PATCH] =?UTF-8?q?25=2012=2025=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BB=D0=BE=D0=B3=D0=B3=D0=B5=D1=80=20=D0=B2=20?= =?UTF-8?q?=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D0=B8.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 8 +- shine-server-net-protocol/build.gradle | 3 - .../JSON/JsonInboundProcessor.java | 177 +++++++++++++++--- .../handlers/blockchain/BlockchainWriter.java | 23 +++ .../blockchain/Net_AddBlock_Handler.java | 48 +++++ 5 files changed, 223 insertions(+), 36 deletions(-) diff --git a/build.gradle b/build.gradle index 1885003..62920f4 100644 --- a/build.gradle +++ b/build.gradle @@ -23,8 +23,10 @@ dependencies { implementation 'org.bouncycastle:bcprov-jdk18on:1.78.1' // шифрование implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' // json - implementation 'org.slf4j:slf4j-api:2.0.9' +// implementation 'org.slf4j:slf4j-api:2.0.9' implementation 'ch.qos.logback:logback-classic:1.5.6' + // Logback (реализация SLF4J + классический модуль) + runtimeOnly "ch.qos.logback:logback-classic:1.5.6" testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation 'org.junit.jupiter:junit-jupiter' @@ -32,8 +34,8 @@ dependencies { implementation "org.slf4j:slf4j-api:2.0.16" // вызов логгера - runtimeOnly "org.apache.logging.log4j:log4j-core:2.24.3" // Реализация: Log4j2 пишет в файл/консоль - runtimeOnly "org.apache.logging.log4j:log4j-slf4j2-impl:2.24.3" // Реализация: Log4j2 пишет в файл/консоль +// runtimeOnly "org.apache.logging.log4j:log4j-core:2.24.3" // Реализация: Log4j2 пишет в файл/консоль +// runtimeOnly "org.apache.logging.log4j:log4j-slf4j2-impl:2.24.3" // Реализация: Log4j2 пишет в файл/консоль implementation project(':shine-server-config') // модуль настроек из application.properties diff --git a/shine-server-net-protocol/build.gradle b/shine-server-net-protocol/build.gradle index 6407aa4..bb8d2f9 100644 --- a/shine-server-net-protocol/build.gradle +++ b/shine-server-net-protocol/build.gradle @@ -22,9 +22,6 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' // json - implementation 'org.slf4j:slf4j-api:2.0.9' - implementation 'ch.qos.logback:logback-classic:1.5.6' - implementation "org.slf4j:slf4j-api:2.0.16" // вызов логгера implementation project(':shine-server-config') // модуль с настройками diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonInboundProcessor.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonInboundProcessor.java index e507478..90ca96a 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonInboundProcessor.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonInboundProcessor.java @@ -32,6 +32,7 @@ import java.util.Map; * } */ public final class JsonInboundProcessor { + private static final Logger log = LoggerFactory.getLogger(JsonInboundProcessor.class); private static final ObjectMapper JSON_MAPPER = new ObjectMapper() @@ -51,6 +52,10 @@ public final class JsonInboundProcessor { String op = null; String requestId = null; + // Для лога полезно знать, кто прислал (хотя бы login/sessionId, если есть) + String ctxLogin = safe(ctx != null ? ctx.getLogin() : null); + String ctxSessionId = safe(ctx != null ? ctx.getSessionId() : null); + try { if (json == null || json.isBlank()) { Net_Exception_Response err = NetExceptionResponseFactory.error( @@ -60,13 +65,26 @@ public final class JsonInboundProcessor { "EMPTY_JSON", "Пустое JSON-сообщение" ); - return writeResponse(err); + + String out = writeResponse(err); + + // DEBUG: что пришло / что ушло + if (log.isDebugEnabled()) { + log.debug("JSON IN (login={}, sessionId={}): ", ctxLogin, ctxSessionId); + log.debug("JSON OUT (login={}, sessionId={}): {}", ctxLogin, ctxSessionId, shorten(out, 1200)); + } + return out; } - // 1. Парсим общий пакет + // DEBUG: сырой вход (обрезаем, чтобы не убить лог) + if (log.isDebugEnabled()) { + log.debug("JSON IN (login={}, sessionId={}): {}", ctxLogin, ctxSessionId, shorten(json, 1200)); + } + + // 1) Парсим общий пакет JsonNode root = JSON_MAPPER.readTree(json); - // 2. op и requestId из корня + // 2) op и requestId из корня op = getTextOrNull(root, "op"); requestId = getTextOrNull(root, "requestId"); @@ -78,7 +96,13 @@ public final class JsonInboundProcessor { "NO_OP", "Поле 'op' отсутствует или пустое" ); - return writeResponse(err); + + String out = writeResponse(err); + if (log.isDebugEnabled()) { + log.debug("JSON OUT (login={}, sessionId={}, op={}, requestId={}): {}", + ctxLogin, ctxSessionId, safe(op), safe(requestId), shorten(out, 1200)); + } + return out; } JsonMessageHandler handler = JSON_HANDLERS.get(op); @@ -92,10 +116,16 @@ public final class JsonInboundProcessor { "UNKNOWN_OP", "Неизвестная операция: " + op ); - return writeResponse(err); + + String out = writeResponse(err); + if (log.isDebugEnabled()) { + log.debug("JSON OUT (login={}, sessionId={}, op={}, requestId={}): {}", + ctxLogin, ctxSessionId, safe(op), safe(requestId), shorten(out, 1200)); + } + return out; } - // 3. Берём payload + // 3) Берём payload JsonNode payloadNode = root.get("payload"); if (payloadNode == null || payloadNode.isNull()) { Net_Exception_Response err = NetExceptionResponseFactory.error( @@ -105,7 +135,13 @@ public final class JsonInboundProcessor { "NO_PAYLOAD", "Поле 'payload' отсутствует" ); - return writeResponse(err); + + String out = writeResponse(err); + if (log.isDebugEnabled()) { + log.debug("JSON OUT (login={}, sessionId={}, op={}, requestId={}): {}", + ctxLogin, ctxSessionId, safe(op), safe(requestId), shorten(out, 1200)); + } + return out; } if (!payloadNode.isObject()) { Net_Exception_Response err = NetExceptionResponseFactory.error( @@ -115,7 +151,13 @@ public final class JsonInboundProcessor { "BAD_PAYLOAD", "Поле 'payload' должно быть объектом" ); - return writeResponse(err); + + String out = writeResponse(err); + if (log.isDebugEnabled()) { + log.debug("JSON OUT (login={}, sessionId={}, op={}, requestId={}): {}", + ctxLogin, ctxSessionId, safe(op), safe(requestId), shorten(out, 1200)); + } + return out; } // 3.1 Собираем "плоский" объект для маппинга в NetRequest: @@ -123,26 +165,52 @@ public final class JsonInboundProcessor { ObjectNode merged = JSON_MAPPER.createObjectNode(); // Добавляем op и requestId, чтобы они попали в NetRequest - if (op != null) { - merged.put("op", op); - } - if (requestId != null) { - merged.put("requestId", requestId); - } + merged.put("op", op); + if (requestId != null) merged.put("requestId", requestId); // Добавляем все поля из payload внутрь merged.setAll((ObjectNode) payloadNode); - // 4. Маппим в конкретный класс NetRequest - Net_Request request = JSON_MAPPER.treeToValue(merged, reqClass); + // 4) Маппим в конкретный класс NetRequest + Net_Request request; + try { + request = JSON_MAPPER.treeToValue(merged, reqClass); + } catch (Exception mapErr) { + // Важно: вот это часто “теряется”, если не логировать отдельно + log.error("❌ JSON map error (op={}, requestId={}, login={}, sessionId={}): merged={}", + op, safe(requestId), ctxLogin, ctxSessionId, shorten(merged.toString(), 1200), mapErr); + Net_Exception_Response err = NetExceptionResponseFactory.error( + op, + requestId, + WireCodes.Status.BAD_REQUEST, + "BAD_REQUEST_FORMAT", + "Некорректный формат запроса: не удалось распарсить поля payload" + ); + + String out = writeResponse(err); + if (log.isDebugEnabled()) { + log.debug("JSON OUT (login={}, sessionId={}, op={}, requestId={}): {}", + ctxLogin, ctxSessionId, safe(op), safe(requestId), shorten(out, 1200)); + } + return out; + } + + // DEBUG: нормализованный запрос (уже распарсен) + if (log.isDebugEnabled()) { + log.debug("REQ OBJ (login={}, sessionId={}, op={}, requestId={}): {}", + ctxLogin, ctxSessionId, safe(op), safe(requestId), shorten(safeToString(request), 1200)); + } + + // 5) Вызываем хэндлер Net_Response response; - - // 5. Вызываем хэндлер try { response = handler.handle(request, ctx); } catch (Exception handlerError) { - log.error("💥 Ошибка внутри хэндлера '{}'", op, handlerError); + // ✅ Вот тут как раз и должны “появляться ошибки в логере” + log.error("💥 Handler error (op={}, requestId={}, login={}, sessionId={})", + op, safe(requestId), ctxLogin, ctxSessionId, handlerError); + Net_Exception_Response err = NetExceptionResponseFactory.error( op, requestId, @@ -150,18 +218,37 @@ public final class JsonInboundProcessor { "INTERNAL_HANDLER_ERROR", "Неожиданная ошибка при обработке операции: " + op ); - return writeResponse(err); + + String out = writeResponse(err); + if (log.isDebugEnabled()) { + log.debug("JSON OUT (login={}, sessionId={}, op={}, requestId={}): {}", + ctxLogin, ctxSessionId, safe(op), safe(requestId), shorten(out, 1200)); + } + return out; } // На всякий случай: если хэндлер не выставил op/requestId if (response.getOp() == null) response.setOp(op); if (response.getRequestId() == null) response.setRequestId(requestId); - // 6. Универсальная сборка ответа - return writeResponse(response); + // 6) Универсальная сборка ответа + String out = writeResponse(response); + + // DEBUG: ответ ушёл + if (log.isDebugEnabled()) { + log.debug("RESP OBJ (login={}, sessionId={}, op={}, requestId={}, status={}): {}", + ctxLogin, ctxSessionId, safe(op), safe(requestId), response.getStatus(), shorten(safeToString(response), 1200)); + log.debug("JSON OUT (login={}, sessionId={}, op={}, requestId={}, status={}): {}", + ctxLogin, ctxSessionId, safe(op), safe(requestId), response.getStatus(), shorten(out, 1200)); + } + + return out; } catch (Exception e) { - log.error("Ошибка при обработке JSON-сообщения", e); + // ✅ Любая неожиданная ошибка парсинга/обработки — в лог + log.error("❌ JSON processing error (op={}, requestId={}, login={}, sessionId={})", + safe(op), safe(requestId), safe(ctxLogin), safe(ctxSessionId), e); + Net_Exception_Response err = NetExceptionResponseFactory.error( op != null ? op : "Unknown", requestId, @@ -169,7 +256,15 @@ public final class JsonInboundProcessor { "INTERNAL_ERROR", "Внутренняя ошибка сервера" ); - return writeResponse(err); + + String out = writeResponse(err); + + if (log.isDebugEnabled()) { + log.debug("JSON OUT (login={}, sessionId={}, op={}, requestId={}): {}", + ctxLogin, ctxSessionId, safe(op), safe(requestId), shorten(out, 1200)); + } + + return out; } } @@ -215,17 +310,39 @@ public final class JsonInboundProcessor { root.set("payload", full); return JSON_MAPPER.writeValueAsString(root); + } catch (Exception e) { // Совсем аварийный случай — сериализация ответа сломалась. - return "{\"op\":\"" + safe(response.getOp()) + - "\",\"requestId\":\"" + safe(response.getRequestId()) + - "\",\"status\":" + response.getStatus() + - ",\"payload\":{\"code\":\"SERIALIZATION_ERROR\",\"message\":\"" + - "Ошибка сериализации ответа\"}}"; + log.error("❌ Response serialization error (op={}, requestId={})", + safe(response != null ? response.getOp() : null), + safe(response != null ? response.getRequestId() : null), + e); + + return "{\"op\":\"" + safe(response != null ? response.getOp() : null) + + "\",\"requestId\":\"" + safe(response != null ? response.getRequestId() : null) + + "\",\"status\":" + (response != null ? response.getStatus() : 500) + + ",\"payload\":{\"code\":\"SERIALIZATION_ERROR\",\"message\":\"Ошибка сериализации ответа\"}}"; } } private static String safe(String s) { return s != null ? s : ""; } -} + + private static String shorten(String s, int max) { + if (s == null) return ""; + if (s.length() <= max) return s; + return s.substring(0, Math.max(0, max)) + "...(+" + (s.length() - max) + " chars)"; + } + + private static String safeToString(Object o) { + if (o == null) return "null"; + try { + // Чтобы не плодить огромные логи и не утыкаться в циклические ссылки — + // логируем как JSON, если возможно. + return JSON_MAPPER.writeValueAsString(o); + } catch (Exception ignore) { + return String.valueOf(o); + } + } +} \ No newline at end of file diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/BlockchainWriter.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/BlockchainWriter.java index f7ba3ca..db4a853 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/BlockchainWriter.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/BlockchainWriter.java @@ -1,6 +1,8 @@ package server.logic.ws_protocol.JSON.handlers.blockchain; import blockchain.BchBlockEntry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import shine.db.SqliteDbController; import shine.db.dao.BlockchainStateDAO; import shine.db.dao.BlocksDAO; @@ -28,6 +30,8 @@ import java.sql.SQLException; */ public final class BlockchainWriter { + private static final Logger log = LoggerFactory.getLogger(BlockchainWriter.class); + private final SqliteDbController db; private final BlocksDAO blocksDAO; private final BlockchainStateDAO stateDAO; @@ -86,12 +90,20 @@ public final class BlockchainWriter { try { oldBytes = fs.readBlockchain(blockchainName); } catch (Exception e) { + // ✅ Добавили подробный лог: это очень важная точка + log.error("Ошибка чтения старого файла блокчейна перед записью tmp (login={}, blockchainName={}, oldFileSize={}, blockNumber={})", + login, blockchainName, oldFileSize, block.recordNumber, e); + // Здесь лучше падать: state говорит, что файл есть, а прочитать нельзя. throw new SQLException("Cannot read old blockchain file for: " + blockchainName, e); } // (на будущее) можно проверять согласованность: oldBytes.length == oldFileSize // но ты всё равно будешь делать recovery при старте — оставим как подсказку. + if (oldBytes.length != (int) oldFileSize) { + log.warn("Несовпадение размера файла блокчейна: state говорит oldFileSize={}, а реально прочитали oldBytes.length={} (login={}, blockchainName={}, blockNumber={})", + oldFileSize, oldBytes.length, login, blockchainName, block.recordNumber); + } tmpBytes = concat(oldBytes, newBlockFullBytes); } @@ -101,6 +113,9 @@ public final class BlockchainWriter { try { fs.writeBlockchainTmp(blockchainName, tmpBytes); } catch (Exception e) { + // ✅ Добавили подробный лог: это тоже критично + log.error("Ошибка записи tmp файла блокчейна (login={}, blockchainName={}, tmpBytesLen={}, oldFileSize={}, newFileSize={}, blockNumber={})", + login, blockchainName, tmpBytes.length, oldFileSize, newFileSize, block.recordNumber, e); throw new SQLException("Cannot write tmp blockchain file for: " + blockchainName, e); } @@ -130,6 +145,10 @@ public final class BlockchainWriter { } catch (Exception e) { try { c.rollback(); } catch (SQLException ignore) {} + // ✅ ВАЖНО: логируем причину отката + контекст + log.error("Ошибка транзакции БД при добавлении блока (rollback выполнен) (login={}, blockchainName={}, blockNumber={}, prevHash={}, newHash={}, oldFileSize={}, newFileSize={})", + login, blockchainName, block.recordNumber, prevGlobalHashHex, newHashHex, oldFileSize, newFileSize, e); + if (e instanceof SQLException se) throw se; throw new SQLException("appendBlockAndState failed (db tx)", e); @@ -150,6 +169,10 @@ public final class BlockchainWriter { try { fs.atomicReplaceBlockchainFile(blockchainName); } catch (Exception moveError) { + // ✅ Очень важная ситуация: БД уже committed, а файл не заменился + log.error("БД закоммичена, но атомарная замена файла блокчейна не удалась. tmp оставлен для recovery. (login={}, blockchainName={}, blockNumber={}, newHash={}, tmpBytesLen={})", + login, blockchainName, block.recordNumber, newHashHex, tmpBytes.length, moveError); + // Здесь ВАЖНО: мы уже не можем откатить БД. // Оставляем tmp и даём наверх ошибку — клиент увидит internal_error, // а ты при старте починишь файловую часть. diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_AddBlock_Handler.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_AddBlock_Handler.java index 920134c..35de9e6 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_AddBlock_Handler.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/blockchain/Net_AddBlock_Handler.java @@ -2,6 +2,8 @@ package server.logic.ws_protocol.JSON.handlers.blockchain; import blockchain.BchBlockEntry; import blockchain.BchCryptoVerifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import server.logic.ws_protocol.JSON.ConnectionContext; import server.logic.ws_protocol.JSON.entyties.Net_Request; import server.logic.ws_protocol.JSON.entyties.Net_Response; @@ -33,6 +35,8 @@ import java.util.concurrent.locks.ReentrantLock; */ public final class Net_AddBlock_Handler implements JsonMessageHandler { + private static final Logger log = LoggerFactory.getLogger(Net_AddBlock_Handler.class); + // DAO (перегрузки сами создают/закрывают Connection внутри) private final BlocksDAO blocksDAO = BlocksDAO.getInstance(); private final BlockchainStateDAO stateDAO = BlockchainStateDAO.getInstance(); @@ -98,12 +102,15 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { ) { // 1) Быстрая валидация входных параметров if (blockchainName == null || blockchainName.isBlank()) { + log.warn("AddBlock: пустой blockchainName (globalNumber={})", globalNumber); return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "empty_blockchain_name", 0, ""); } // 2) Из имени блокчейна вытаскиваем login (как ты и хотел — через util) String login = BlockchainNameUtil.loginFromBlockchainName(blockchainName); if (login == null || login.isBlank()) { + log.warn("AddBlock: плохой blockchainName='{}' => login не получился (globalNumber={})", + blockchainName, globalNumber); return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_blockchain_name", 0, ""); } @@ -112,6 +119,8 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { try { blockBytes = decodeBase64(blockBytesB64); } catch (Exception e) { + log.warn("AddBlock: некорректный base64 блока (login={}, blockchainName={}, globalNumber={})", + login, blockchainName, globalNumber, e); return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_block_base64", 0, ""); } @@ -120,6 +129,8 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { try { block = new BchBlockEntry(blockBytes); } catch (Exception e) { + log.warn("AddBlock: не удалось распарсить BchBlockEntry (login={}, blockchainName={}, globalNumber={}, bytesLen={})", + login, blockchainName, globalNumber, blockBytes.length, e); return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_block_format", 0, ""); } @@ -127,11 +138,15 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { try { block.body.check(); } catch (Exception e) { + log.warn("AddBlock: body.check() не прошёл (login={}, blockchainName={}, globalNumber={}, bodyType={}, bodyVersion={})", + login, blockchainName, globalNumber, safeBodyType(block), safeBodyVersion(block), e); return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_block_body", 0, ""); } // 6) Защита от рассинхрона: recordNumber внутри блока должен совпадать с заявленным globalNumber if (block.recordNumber != globalNumber) { + log.warn("AddBlock: global_number_mismatch (login={}, blockchainName={}, заявлен={}, внутриБлока={})", + login, blockchainName, globalNumber, block.recordNumber); return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "global_number_mismatch", 0, ""); } @@ -140,15 +155,22 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { try { u = solanaUsersDAO.getByLogin(login); // перегрузка: сама открывает/закрывает соединение } catch (Exception e) { + // ✅ ВОТ ТУТ ТВОЯ ОШИБКА РАНЬШЕ ТЕРЯЛАСЬ: теперь будет stacktrace в логе + log.error("AddBlock: ошибка БД при чтении пользователя (login={}, blockchainName={}, globalNumber={})", + login, blockchainName, globalNumber, e); return new AddBlockResult(WireCodes.Status.INTERNAL_ERROR, "db_error", 0, ""); } if (u == null) { + log.warn("AddBlock: user_not_found (login={}, blockchainName={}, globalNumber={})", + login, blockchainName, globalNumber); return new AddBlockResult(WireCodes.Status.NOT_FOUND, "user_not_found", 0, ""); } byte[] loginKey32 = u.getLoginKeyByte(); if (loginKey32 == null || loginKey32.length != 32) { + log.warn("AddBlock: bad_user_login_key (login={}, blockchainName={}, globalNumber={}, keyLen={})", + login, blockchainName, globalNumber, (loginKey32 == null ? -1 : loginKey32.length)); return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_user_login_key", 0, ""); } @@ -157,6 +179,9 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { try { st = stateDAO.getByBlockchainName(blockchainName); // перегрузка: сама открывает/закрывает соединение } catch (Exception e) { + // ✅ ВОТ ТУТ ТВОЯ ОШИБКА РАНЬШЕ ТЕРЯЛАСЬ: теперь будет stacktrace в логе + log.error("AddBlock: ошибка БД при чтении blockchain_state (login={}, blockchainName={}, globalNumber={})", + login, blockchainName, globalNumber, e); return new AddBlockResult(WireCodes.Status.INTERNAL_ERROR, "db_error", 0, ""); } @@ -165,6 +190,8 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { final String serverLastHash; if (st == null) { if (globalNumber != 0) { + log.warn("AddBlock: blockchain_state_not_found, но globalNumber != 0 (login={}, blockchainName={}, globalNumber={})", + login, blockchainName, globalNumber); return new AddBlockResult(WireCodes.Status.NOT_FOUND, "blockchain_state_not_found", 0, ""); } serverLastNum = -1; @@ -177,6 +204,8 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { // 10) Проверяем, что клиент присылает следующий блок ровно (last+1) int expected = serverLastNum + 1; if (globalNumber != expected) { + log.warn("AddBlock: bad_global_number (login={}, blockchainName={}, пришёл={}, ожидали={}, serverLastNum={}, serverLastHash={})", + login, blockchainName, globalNumber, expected, serverLastNum, serverLastHash); return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_global_number", serverLastNum, serverLastHash); } @@ -187,10 +216,14 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { prevGlobalHash32 = hexTo32(nn(prevGlobalHashHex)); serverPrevGlobal32 = (st == null) ? new byte[32] : hexTo32(nn(st.getLastGlobalHash())); } catch (Exception e) { + log.warn("AddBlock: bad_prev_global_hash_format (login={}, blockchainName={}, globalNumber={}, prevGlobalHashHex='{}')", + login, blockchainName, globalNumber, nn(prevGlobalHashHex), e); return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_prev_global_hash_format", serverLastNum, serverLastHash); } if (!bytesEq(prevGlobalHash32, serverPrevGlobal32)) { + log.warn("AddBlock: bad_prev_global_hash (login={}, blockchainName={}, globalNumber={}, clientPrev='{}', serverPrev='{}')", + login, blockchainName, globalNumber, nn(prevGlobalHashHex), nn(st != null ? st.getLastGlobalHash() : "")); return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_prev_global_hash", serverLastNum, serverLastHash); } @@ -209,6 +242,8 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { ); if (!ok) { + log.warn("AddBlock: bad_signature_or_hash (login={}, blockchainName={}, globalNumber={})", + login, blockchainName, globalNumber); return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_signature_or_hash", serverLastNum, serverLastHash); } @@ -226,10 +261,15 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { newHashHex ); } catch (Exception e) { + // ✅ ВОТ ЭТО САМОЕ ВАЖНОЕ: если упал writer/БД/файлы — теперь будет stacktrace в логах + log.error("AddBlock: внутренняя ошибка при записи блока (login={}, blockchainName={}, globalNumber={}, newHash={})", + login, blockchainName, globalNumber, newHashHex, e); return new AddBlockResult(WireCodes.Status.INTERNAL_ERROR, "internal_error", serverLastNum, serverLastHash); } // 16) Успех + log.info("✅ AddBlock ok: login={}, blockchainName={}, globalNumber={}, newHash={}", + login, blockchainName, globalNumber, newHashHex); return new AddBlockResult(WireCodes.Status.OK, null, globalNumber, newHashHex); } @@ -301,4 +341,12 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { } return new String(out); } + + private static String safeBodyType(BchBlockEntry b) { + try { return String.valueOf(b.body.type()); } catch (Exception e) { return "unknown"; } + } + + private static String safeBodyVersion(BchBlockEntry b) { + try { return String.valueOf(b.body.version()); } catch (Exception e) { return "unknown"; } + } } \ No newline at end of file