Добавить ListBlockchainHeads для межсерверной сверки
This commit is contained in:
parent
f0e1ab3af8
commit
1f8b20a7d1
@ -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`
|
||||
|
||||
### Запрос
|
||||
|
||||
|
||||
@ -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-лог |
|
||||
|
||||
@ -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<BlockchainStateEntry> listAll() throws SQLException {
|
||||
try (Connection c = db.getConnection()) {
|
||||
return listAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
/** Получить все blockchain_state записи с внешним соединением. Соединение НЕ закрывает. */
|
||||
public List<BlockchainStateEntry> 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<BlockchainStateEntry> 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()) {
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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<BlockchainStateEntry> 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<Net_ListBlockchainHeads_Response.Item> 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);
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
}
|
||||
@ -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<Item> blockchains = new ArrayList<>();
|
||||
|
||||
public List<Item> getBlockchains() { return blockchains; }
|
||||
public void setBlockchains(List<Item> blockchains) { this.blockchains = blockchains; }
|
||||
}
|
||||
@ -1,2 +1,2 @@
|
||||
client.version=1.2.269
|
||||
server.version=1.2.249
|
||||
client.version=1.2.270
|
||||
server.version=1.2.250
|
||||
|
||||
Loading…
Reference in New Issue
Block a user