Добавил логгер в настройки.2
This commit is contained in:
AidarKC 2025-12-25 16:00:57 +03:00
parent c8ee9925a1
commit f8cc12560e
5 changed files with 223 additions and 36 deletions

View File

@ -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

View File

@ -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') // модуль с настройками

View File

@ -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={}): <empty>", 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);
}
}
}

View File

@ -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,
// а ты при старте починишь файловую часть.

View File

@ -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"; }
}
}