diff --git a/Dev_Docs/API/05_Technical_Requests_API.md b/Dev_Docs/API/05_Technical_Requests_API.md index 950aea4..0314891 100644 --- a/Dev_Docs/API/05_Technical_Requests_API.md +++ b/Dev_Docs/API/05_Technical_Requests_API.md @@ -2,10 +2,11 @@ Этот файл описывает технические WebSocket-запросы, которые нужны для служебной работы клиента с сервером. Часть операций доступна без авторизации, часть требует успешной авторизованной сессии. -Сейчас здесь шесть методов: +Сейчас здесь семь методов: - `Ping` — keep-alive запрос для поддержания живого WebSocket-соединения; - `GetServerInfo` — запрос базовой публичной информации о сервере для выбора узла в децентрализованной сети; +- `ListBlockchainHeads` — краткая сводка по всем локальным блокчейнам сервера для межсерверной синхронизации; - `GetCallIceConfig` — выдача STUN/TURN конфигурации для звонков; - `ClientErrorLog` — отправка клиентской ошибки в серверный лог; - `ClientDebugLog` — отправка клиентского debug-события в серверный буфер; @@ -15,6 +16,7 @@ - `Ping` нужен для регулярной проверки, что соединение всё ещё живо; - `GetServerInfo` нужен до авторизации и до работы с данными, чтобы клиент понял, что сервер доступен, и показал пользователю краткую карточку этого узла. +- `ListBlockchainHeads` нужен для сервер-сервер сверки: партнёр получает список heads по всем цепочкам, сравнивает его со своим состоянием и затем добирает недостающие блоки по диапазону. Ниже сначала описаны назначение методов, затем точные форматы запросов и ответов. @@ -134,7 +136,68 @@ --- -## 3. `GetCallIceConfig` +## 3. `ListBlockchainHeads` + +### Назначение + +Запрос краткой сводки по всем локальным блокчейнам сервера. + +Нужен для межсерверной синхронизации. Партнёр может: + +- получить список всех блокчейнов; +- сравнить `lastBlockNumber` и `lastBlockHash` со своими значениями; +- понять, какие цепочки нужно догонять; +- затем отдельно запросить недостающие блоки по диапазону. + +Этот запрос доступен без авторизации. + +### Запрос + +```json +{ + "op": "ListBlockchainHeads", + "requestId": "heads-001", + "payload": {} +} +``` + +### Успешный ответ + +```json +{ + "op": "ListBlockchainHeads", + "requestId": "heads-001", + "status": 200, + "ok": true, + "payload": { + "blockchains": [ + { + "blockchainName": "alice_main", + "lastBlockNumber": 124, + "lastBlockHash": "aabbccdd00112233445566778899aabbccddeeff00112233445566778899aabb", + "fileSizeBytes": 58720 + } + ] + } +} +``` + +### Поля ответа + +- `blockchains` — массив текущих heads всех цепочек сервера. +- `blockchainName` — имя блокчейна. +- `lastBlockNumber` — последний номер блока в этой цепочке. +- `lastBlockHash` — последний хэш блока в HEX-формате `64` символа. +- `fileSizeBytes` — текущий размер файла блокчейна в байтах. + +### Специфические коды ошибок `ListBlockchainHeads` + +- У `ListBlockchainHeads` нет специальных прикладных ошибок при штатной работе. +- Если произойдёт непредвиденная проблема, сервер вернёт общую ошибку из раздела `00`, обычно `500 / INTERNAL_ERROR`. + +--- + +## 4. `GetCallIceConfig` Доступно только после успешной авторизации. @@ -184,7 +247,7 @@ --- -## 4. `ClientErrorLog` +## 5. `ClientErrorLog` ### Запрос @@ -231,7 +294,7 @@ --- -## 5. `ClientDebugLog` +## 6. `ClientDebugLog` ### Запрос @@ -269,7 +332,7 @@ --- -## 6. `CallDeliveryReport` +## 7. `CallDeliveryReport` ### Запрос diff --git a/Dev_Docs/API/09_Operations_Index.md b/Dev_Docs/API/09_Operations_Index.md index 0b4e044..8c17ff3 100644 --- a/Dev_Docs/API/09_Operations_Index.md +++ b/Dev_Docs/API/09_Operations_Index.md @@ -34,6 +34,7 @@ | `AddBlock` | `04_Add_Block_to_Blockchain_API.md` | добавление блока в блокчейн | | `Ping` | `05_Technical_Requests_API.md` | keep-alive | | `GetServerInfo` | `05_Technical_Requests_API.md` | публичная информация о сервере | +| `ListBlockchainHeads` | `05_Technical_Requests_API.md` | список heads всех локальных блокчейнов | | `GetCallIceConfig` | `05_Technical_Requests_API.md` | STUN/TURN конфигурация звонков | | `ClientErrorLog` | `05_Technical_Requests_API.md` | логирование клиентской ошибки | | `ClientDebugLog` | `05_Technical_Requests_API.md` | клиентский debug-лог | diff --git a/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/BlockchainStateDAO.java b/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/BlockchainStateDAO.java index a19e879..334cd2d 100644 --- a/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/BlockchainStateDAO.java +++ b/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/BlockchainStateDAO.java @@ -4,6 +4,8 @@ import shine.db.SqliteDbController; import shine.db.entities.BlockchainStateEntry; import java.sql.*; +import java.util.ArrayList; +import java.util.List; public final class BlockchainStateDAO { @@ -53,6 +55,39 @@ public final class BlockchainStateDAO { } } + /** Получить все blockchain_state записи. */ + public List listAll() throws SQLException { + try (Connection c = db.getConnection()) { + return listAll(c); + } + } + + /** Получить все blockchain_state записи с внешним соединением. Соединение НЕ закрывает. */ + public List listAll(Connection c) throws SQLException { + String sql = """ + SELECT + blockchain_name, + login, + blockchain_key, + size_limit, + file_size_bytes, + last_block_number, + last_block_hash, + updated_at_ms + FROM blockchain_state + ORDER BY blockchain_name COLLATE NOCASE + """; + + List result = new ArrayList<>(); + try (PreparedStatement ps = c.prepareStatement(sql); + ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + result.add(mapRow(rs)); + } + } + return result; + } + /** UPSERT без внешнего соединения. Сам открывает/закрывает. */ public void upsert(BlockchainStateEntry e) throws SQLException { try (Connection c = db.getConnection()) { @@ -150,4 +185,4 @@ public final class BlockchainStateDAO { } private static String nn(String s) { return s == null ? "" : s; } -} \ No newline at end of file +} diff --git a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java index 39e8056..28133fe 100644 --- a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java +++ b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java @@ -106,6 +106,7 @@ import server.logic.ws_protocol.JSON.handlers.system.Net_GetServerInfo_Handler; import server.logic.ws_protocol.JSON.handlers.system.Net_GetCallIceConfig_Handler; import server.logic.ws_protocol.JSON.handlers.system.Net_ClientErrorLog_Handler; import server.logic.ws_protocol.JSON.handlers.system.Net_ClientDebugLog_Handler; +import server.logic.ws_protocol.JSON.handlers.system.Net_ListBlockchainHeads_Handler; import server.logic.ws_protocol.JSON.handlers.system.Net_CallDeliveryReport_Handler; import server.logic.ws_protocol.JSON.handlers.system.Net_Ping_Handler; import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_CallDeliveryReport_Request; @@ -113,6 +114,7 @@ import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_ClientErrorLog import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_ClientDebugLog_Request; import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_GetCallIceConfig_Request; import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_GetServerInfo_Request; +import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_ListBlockchainHeads_Request; import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_Ping_Request; import java.util.Map; @@ -193,6 +195,7 @@ public final class JsonHandlerRegistry { // --- system --- Map.entry("Ping", new Net_Ping_Handler()), Map.entry("GetServerInfo", new Net_GetServerInfo_Handler()), + Map.entry("ListBlockchainHeads", new Net_ListBlockchainHeads_Handler()), Map.entry("GetCallIceConfig", new Net_GetCallIceConfig_Handler()), Map.entry("ClientErrorLog", new Net_ClientErrorLog_Handler()), Map.entry("ClientDebugLog", new Net_ClientDebugLog_Handler()), @@ -268,6 +271,7 @@ public final class JsonHandlerRegistry { // --- system --- Map.entry("Ping", Net_Ping_Request.class), Map.entry("GetServerInfo", Net_GetServerInfo_Request.class), + Map.entry("ListBlockchainHeads", Net_ListBlockchainHeads_Request.class), Map.entry("GetCallIceConfig", Net_GetCallIceConfig_Request.class), Map.entry("ClientErrorLog", Net_ClientErrorLog_Request.class), Map.entry("ClientDebugLog", Net_ClientDebugLog_Request.class), diff --git a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/Net_ListBlockchainHeads_Handler.java b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/Net_ListBlockchainHeads_Handler.java new file mode 100644 index 0000000..8a84e7f --- /dev/null +++ b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/Net_ListBlockchainHeads_Handler.java @@ -0,0 +1,76 @@ +package server.logic.ws_protocol.JSON.handlers.system; + +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; +import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler; +import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_ListBlockchainHeads_Request; +import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_ListBlockchainHeads_Response; +import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory; +import server.logic.ws_protocol.WireCodes; +import shine.db.dao.BlockchainStateDAO; +import shine.db.entities.BlockchainStateEntry; + +import java.util.ArrayList; +import java.util.List; + +/** + * ListBlockchainHeads — получить краткую сводку по всем blockchain_state. + * Используется для межсерверной сверки heads перед догоняющей синхронизацией. + */ +public class Net_ListBlockchainHeads_Handler implements JsonMessageHandler { + + private static final Logger log = LoggerFactory.getLogger(Net_ListBlockchainHeads_Handler.class); + + @Override + public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) { + Net_ListBlockchainHeads_Request req = (Net_ListBlockchainHeads_Request) baseRequest; + + try { + List states = BlockchainStateDAO.getInstance().listAll(); + + Net_ListBlockchainHeads_Response resp = new Net_ListBlockchainHeads_Response(); + resp.setOp(req.getOp()); + resp.setRequestId(req.getRequestId()); + resp.setStatus(WireCodes.Status.OK); + + List items = new ArrayList<>(states.size()); + for (BlockchainStateEntry state : states) { + Net_ListBlockchainHeads_Response.Item item = new Net_ListBlockchainHeads_Response.Item(); + item.setBlockchainName(state.getBlockchainName()); + item.setLastBlockNumber(state.getLastBlockNumber()); + item.setLastBlockHash(toHex32(state.getLastBlockHash())); + item.setFileSizeBytes(state.getFileSizeBytes()); + items.add(item); + } + resp.setBlockchains(items); + + return resp; + } catch (Exception e) { + log.error("❌ Internal error ListBlockchainHeads", e); + return NetExceptionResponseFactory.error( + req, + WireCodes.Status.INTERNAL_ERROR, + "INTERNAL_ERROR", + NetExceptionResponseFactory.detailedMessage("Внутренняя ошибка сервера при ListBlockchainHeads", e) + ); + } + } + + private static String toHex32(byte[] bytes32) { + byte[] b = bytes32; + if (b == null || b.length != 32) { + b = new byte[32]; + } + final char[] hex = "0123456789abcdef".toCharArray(); + char[] out = new char[64]; + for (int i = 0; i < 32; i++) { + int v = b[i] & 0xFF; + out[i * 2] = hex[v >>> 4]; + out[i * 2 + 1] = hex[v & 0x0F]; + } + return new String(out); + } +} diff --git a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_ListBlockchainHeads_Request.java b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_ListBlockchainHeads_Request.java new file mode 100644 index 0000000..9335957 --- /dev/null +++ b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_ListBlockchainHeads_Request.java @@ -0,0 +1,9 @@ +package server.logic.ws_protocol.JSON.handlers.system.entyties; + +import server.logic.ws_protocol.JSON.entyties.Net_Request; + +/** + * Пустой запрос для получения списка heads всех blockchain_state. + */ +public class Net_ListBlockchainHeads_Request extends Net_Request { +} diff --git a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_ListBlockchainHeads_Response.java b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_ListBlockchainHeads_Response.java new file mode 100644 index 0000000..debeb90 --- /dev/null +++ b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_ListBlockchainHeads_Response.java @@ -0,0 +1,33 @@ +package server.logic.ws_protocol.JSON.handlers.system.entyties; + +import server.logic.ws_protocol.JSON.entyties.Net_Response; + +import java.util.ArrayList; +import java.util.List; + +public class Net_ListBlockchainHeads_Response extends Net_Response { + + public static final class Item { + private String blockchainName; + private int lastBlockNumber; + private String lastBlockHash; + private long fileSizeBytes; + + public String getBlockchainName() { return blockchainName; } + public void setBlockchainName(String blockchainName) { this.blockchainName = blockchainName; } + + public int getLastBlockNumber() { return lastBlockNumber; } + public void setLastBlockNumber(int lastBlockNumber) { this.lastBlockNumber = lastBlockNumber; } + + public String getLastBlockHash() { return lastBlockHash; } + public void setLastBlockHash(String lastBlockHash) { this.lastBlockHash = lastBlockHash; } + + public long getFileSizeBytes() { return fileSizeBytes; } + public void setFileSizeBytes(long fileSizeBytes) { this.fileSizeBytes = fileSizeBytes; } + } + + private List blockchains = new ArrayList<>(); + + public List getBlockchains() { return blockchains; } + public void setBlockchains(List blockchains) { this.blockchains = blockchains; } +} diff --git a/VERSION.properties b/VERSION.properties index 93755ef..f8866a3 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.269 -server.version=1.2.249 +client.version=1.2.270 +server.version=1.2.250