From 37c36ffdba6dd70927749b38c5366131c014146abc68664291c0d8979b290780 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Thu, 19 Feb 2026 18:06:03 +0300 Subject: [PATCH] 19 02 25 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit сделал единый формат протокола в случае ошибок (Наверное сделал удобнее) --- .../blockchain/Net_AddBlock_Handler.java | 137 ++++++++++++++---- .../JSON/utils/AuthSignatures.java | 52 +++++++ 2 files changed, 158 insertions(+), 31 deletions(-) create mode 100644 shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/utils/AuthSignatures.java 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 6145d1d..ffc1d5c 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 @@ -9,6 +9,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import server.logic.ws_protocol.Base64Ws; import server.logic.ws_protocol.JSON.ConnectionContext; +import server.logic.ws_protocol.JSON.entyties.Net_Exception_Response; import server.logic.ws_protocol.JSON.entyties.Net_Request; import server.logic.ws_protocol.JSON.entyties.Net_Response; import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler; @@ -29,21 +30,10 @@ import java.util.concurrent.locks.ReentrantLock; /** * Net_AddBlock_Handler — единый хэндлер добавления блока (JSON). * - * Новый порядок валидации (ТЗ): - * 1) Достаём из blockchain_state: last_block_number, last_block_hash - * 2) Проверяем: - * - incoming.blockNumber == last+1 - * - incoming.prevHash32 == last_hash (для genesis last_hash = 32 нулей) - * 3) Проверяем подпись Ed25519.verify(hash32(preimage), signature64, pubKey) - * 4) Если тип имеет линию: - * - если prevLineNumber != null: - * достаём hash блока prevLineNumber из blocks - * сравниваем с prevLineHash32 из body - * 5) Сохраняем блок в blocks + обновляем blockchain_state - * - * Важно: - * - Сетевой протокол AddBlock пока оставляем старые поля (globalNumber/prevGlobalHash), - * но внутренняя логика использует НОВЫЙ формат блока. + * Изменение (v3): + * - ВСЕ ошибки теперь возвращаются в стандартном формате Net_Exception_Response: + * status != 200, payload: { code, message, serverLastGlobalNumber, serverLastGlobalHash } + * - Успех — как и раньше Net_AddBlock_Response (status=200). */ public final class Net_AddBlock_Handler implements JsonMessageHandler { @@ -70,28 +60,79 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { req.getBlockBytesB64() ); - Net_AddBlock_Response resp = new Net_AddBlock_Response(); - resp.setOp(req.getOp()); - resp.setRequestId(req.getRequestId()); - + // ✅ УСПЕХ: как раньше if (r.isOk()) { + Net_AddBlock_Response resp = new Net_AddBlock_Response(); + resp.setOp(req.getOp()); + resp.setRequestId(req.getRequestId()); resp.setStatus(WireCodes.Status.OK); + resp.setReasonCode(null); - } else { - resp.setStatus(r.httpStatus); - resp.setReasonCode(r.reasonCode); + resp.setServerLastGlobalNumber(r.serverLastBlockNumber); + resp.setServerLastGlobalHash(r.serverLastBlockHashHex); + + return resp; } - resp.setServerLastGlobalNumber(r.serverLastBlockNumber); - resp.setServerLastGlobalHash(r.serverLastBlockHashHex); - - return resp; + // ✅ ОШИБКА: стандартный формат (code + message) + доп.поля для ресинка + return error(req, r.httpStatus, r.reasonCode, r.serverLastBlockNumber, r.serverLastBlockHashHex); } finally { lock.unlock(); } } + private Net_Response error(Net_AddBlock_Request req, + int status, + String reasonCode, + int serverLastNum, + String serverLastHashHex) { + + AddBlockExceptionResponse resp = new AddBlockExceptionResponse(); + resp.setOp(req.getOp()); + resp.setRequestId(req.getRequestId()); + resp.setStatus(status); + + // code — машинный + resp.setCode(reasonCode != null ? reasonCode : "add_block_error"); + // message — человеческий (можешь улучшать тексты как угодно) + resp.setMessage(humanMessage(reasonCode)); + + // полезно клиенту для ресинка + resp.setServerLastGlobalNumber(serverLastNum); + resp.setServerLastGlobalHash(serverLastHashHex); + + return resp; + } + + private static String humanMessage(String code) { + if (code == null) return "Ошибка добавления блока"; + + return switch (code) { + case "empty_blockchain_name" -> "Пустое имя блокчейна"; + case "bad_blockchain_name" -> "Некорректное имя блокчейна"; + case "db_error" -> "Ошибка базы данных"; + case "blockchain_state_not_found" -> "Состояние блокчейна не найдено"; + case "state_last_hash_invalid" -> "Повреждено состояние блокчейна: неверный last_block_hash"; + case "bad_block_base64" -> "Некорректный base64 блока"; + case "limit_exceeded" -> "Превышен лимит размера блокчейна"; + case "limit_check_failed" -> "Ошибка проверки лимита размера"; + case "bad_block_format" -> "Некорректный формат блока"; + case "bad_block_body" -> "Некорректное тело блока"; + case "bad_block_number" -> "Некорректный номер блока"; + case "req_global_mismatch" -> "Номер блока в запросе не совпадает с номером в блоке"; + case "bad_prev_hash" -> "Некорректный prevHash (цепочка не совпадает)"; + case "bad_blockchain_key_len" -> "Некорректный ключ блокчейна в состоянии (ожидалось 32 байта)"; + case "signature_verify_failed" -> "Ошибка проверки подписи блока"; + case "bad_signature" -> "Некорректная подпись блока"; + case "prev_line_block_not_found" -> "Не найден блок prevLineNumber для проверки линии"; + case "bad_prev_line_hash" -> "Некорректный prevLineHash"; + case "db_error_prev_line_check" -> "Ошибка БД при проверке prevLine"; + case "internal_error" -> "Внутренняя ошибка сервера при записи блока"; + default -> "Ошибка: " + code; + }; + } + private AddBlockResult addBlock( String blockchainName, int globalNumberFromReq, @@ -127,9 +168,18 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { } final int serverLastNum = st.getLastBlockNumber(); - final byte[] serverLastHash32 = (serverLastNum < 0) - ? new byte[32] - : require32OrThrow(st.getLastBlockHash(), "state.last_block_hash is null/invalid"); + + final byte[] serverLastHash32; + try { + serverLastHash32 = (serverLastNum < 0) + ? new byte[32] + : require32OrThrow(st.getLastBlockHash(), "state.last_block_hash is null/invalid"); + } catch (Exception e) { + // ✅ Раньше тут мог вылететь неожиданный 500 через внешний try/catch. + log.error("AddBlock: state_last_hash_invalid (login={}, blockchainName={}, serverLastNum={})", + login, blockchainName, serverLastNum, e); + return new AddBlockResult(WireCodes.Status.INTERNAL_ERROR, "state_last_hash_invalid", serverLastNum, ""); + } final String serverLastHashHex = toHex(serverLastHash32); @@ -215,7 +265,7 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { } catch (Exception e) { log.warn("AddBlock: signature_verify_failed (login={}, blockchainName={}, blockNumber={})", login, blockchainName, block.blockNumber, e); - return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_signature", serverLastNum, serverLastHashHex); + return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "signature_verify_failed", serverLastNum, serverLastHashHex); } if (!sigOk) { @@ -351,6 +401,31 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { return new String(out); } + /** + * Спец-ответ ошибки AddBlock: стандартный code/message + поля для ресинка. + * В wire-формате это окажется внутри payload. + */ + public static final class AddBlockExceptionResponse extends Net_Exception_Response { + private Integer serverLastGlobalNumber; + private String serverLastGlobalHash; + + public Integer getServerLastGlobalNumber() { + return serverLastGlobalNumber; + } + + public void setServerLastGlobalNumber(Integer serverLastGlobalNumber) { + this.serverLastGlobalNumber = serverLastGlobalNumber; + } + + public String getServerLastGlobalHash() { + return serverLastGlobalHash; + } + + public void setServerLastGlobalHash(String serverLastGlobalHash) { + this.serverLastGlobalHash = serverLastGlobalHash; + } + } + private static final class AddBlockResult { final int httpStatus; final String reasonCode; @@ -366,4 +441,4 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler { boolean isOk() { return httpStatus == WireCodes.Status.OK; } } -} \ No newline at end of file +} diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/utils/AuthSignatures.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/utils/AuthSignatures.java new file mode 100644 index 0000000..be3c997 --- /dev/null +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/utils/AuthSignatures.java @@ -0,0 +1,52 @@ +////package server.logic.ws_protocol.JSON.utils; +// +//import shine.db.entities.SolanaUserEntry; +//import utils.crypto.Ed25519Util; +// +//import java.nio.charset.StandardCharsets; +//import java.util.Base64; +// +//public final class AuthSignatures { +// +// private AuthSignatures() {} +// +// /** preimage для CreateAuthSession(v2): "AUTH_CREATE_SESSION:login:timeMs:authNonce" */ +// public static byte[] preimageCreateAuthSession(String login, long timeMs, String authNonce) { +// String preimageStr = "AUTH_CREATE_SESSION:" + login + ":" + timeMs + ":" + authNonce; +// return preimageStr.getBytes(StandardCharsets.UTF_8); +// } +// +// /** Декод base64 / base64url (если надо — подстрой под твой decodeBase64Any) */ +// public static byte[] decodeBase64Any(String s) throws IllegalArgumentException { +// if (s == null) throw new IllegalArgumentException("base64 is null"); +// String x = s.trim(); +// if (x.isEmpty()) throw new IllegalArgumentException("base64 is empty"); +// +// try { +// return Base64.getDecoder().decode(x); +// } catch (IllegalArgumentException e1) { +// // пробуем base64url без паддинга +// return Base64.getUrlDecoder().decode(x); +// } +// } +// +// /** +// * Проверка подписи CreateAuthSession(v2) по deviceKey пользователя. +// * Подпись проверяется над preimageCreateAuthSession(...). +// */ +// public static boolean verifyCreateAuthSessionSignature( +// SolanaUserEntry user, +// String login, +// String authNonce, +// long timeMs, +// String signatureB64 +// ) throws IllegalArgumentException { +// +// // user.getDeviceKey() — base64 публичного ключа (32 байта) +// byte[] publicKey32 = decodeBase64Any(user.getDeviceKey()); +// byte[] signature64 = decodeBase64Any(signatureB64); +// +// byte[] preimage = preimageCreateAuthSession(login, timeMs, authNonce); +// return Ed25519Util.verify(preimage, signature64, publicKey32); +// } +//} \ No newline at end of file