сделал единый формат протокола в случае ошибок (Наверное сделал удобнее)
This commit is contained in:
AidarKC 2026-02-19 18:06:03 +03:00
parent c7440e2b5c
commit 37c36ffdba
2 changed files with 158 additions and 31 deletions

View File

@ -9,6 +9,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import server.logic.ws_protocol.Base64Ws; import server.logic.ws_protocol.Base64Ws;
import server.logic.ws_protocol.JSON.ConnectionContext; 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_Request;
import server.logic.ws_protocol.JSON.entyties.Net_Response; import server.logic.ws_protocol.JSON.entyties.Net_Response;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler; import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
@ -29,21 +30,10 @@ import java.util.concurrent.locks.ReentrantLock;
/** /**
* Net_AddBlock_Handler единый хэндлер добавления блока (JSON). * Net_AddBlock_Handler единый хэндлер добавления блока (JSON).
* *
* Новый порядок валидации (ТЗ): * Изменение (v3):
* 1) Достаём из blockchain_state: last_block_number, last_block_hash * - ВСЕ ошибки теперь возвращаются в стандартном формате Net_Exception_Response:
* 2) Проверяем: * status != 200, payload: { code, message, serverLastGlobalNumber, serverLastGlobalHash }
* - incoming.blockNumber == last+1 * - Успех как и раньше Net_AddBlock_Response (status=200).
* - 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),
* но внутренняя логика использует НОВЫЙ формат блока.
*/ */
public final class Net_AddBlock_Handler implements JsonMessageHandler { public final class Net_AddBlock_Handler implements JsonMessageHandler {
@ -70,28 +60,79 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
req.getBlockBytesB64() req.getBlockBytesB64()
); );
Net_AddBlock_Response resp = new Net_AddBlock_Response(); // УСПЕХ: как раньше
resp.setOp(req.getOp());
resp.setRequestId(req.getRequestId());
if (r.isOk()) { 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.setStatus(WireCodes.Status.OK);
resp.setReasonCode(null); resp.setReasonCode(null);
} else { resp.setServerLastGlobalNumber(r.serverLastBlockNumber);
resp.setStatus(r.httpStatus); resp.setServerLastGlobalHash(r.serverLastBlockHashHex);
resp.setReasonCode(r.reasonCode);
return resp;
} }
resp.setServerLastGlobalNumber(r.serverLastBlockNumber); // ОШИБКА: стандартный формат (code + message) + доп.поля для ресинка
resp.setServerLastGlobalHash(r.serverLastBlockHashHex); return error(req, r.httpStatus, r.reasonCode, r.serverLastBlockNumber, r.serverLastBlockHashHex);
return resp;
} finally { } finally {
lock.unlock(); 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( private AddBlockResult addBlock(
String blockchainName, String blockchainName,
int globalNumberFromReq, int globalNumberFromReq,
@ -127,9 +168,18 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
} }
final int serverLastNum = st.getLastBlockNumber(); final int serverLastNum = st.getLastBlockNumber();
final byte[] serverLastHash32 = (serverLastNum < 0)
? new byte[32] final byte[] serverLastHash32;
: require32OrThrow(st.getLastBlockHash(), "state.last_block_hash is null/invalid"); 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); final String serverLastHashHex = toHex(serverLastHash32);
@ -215,7 +265,7 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
} catch (Exception e) { } catch (Exception e) {
log.warn("AddBlock: signature_verify_failed (login={}, blockchainName={}, blockNumber={})", log.warn("AddBlock: signature_verify_failed (login={}, blockchainName={}, blockNumber={})",
login, blockchainName, block.blockNumber, e); 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) { if (!sigOk) {
@ -351,6 +401,31 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
return new String(out); 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 { private static final class AddBlockResult {
final int httpStatus; final int httpStatus;
final String reasonCode; final String reasonCode;
@ -366,4 +441,4 @@ public final class Net_AddBlock_Handler implements JsonMessageHandler {
boolean isOk() { return httpStatus == WireCodes.Status.OK; } boolean isOk() { return httpStatus == WireCodes.Status.OK; }
} }
} }

View File

@ -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);
// }
//}