From ed83b1f90630ac0ecdfecb79a775b9b601be74276953a1ed370ce0bffc8604b1 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Sun, 28 Jun 2026 14:45:29 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=20remote=20AddBlock=20=D0=BD=D0=B0=20=D1=81?= =?UTF-8?q?=D0=B1=D0=BE=D1=80=D0=BA=D1=83=20=D0=B1=D0=BB=D0=BE=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BD=D0=B0=20homeserver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dev_Docs/API/05_Technical_Requests_API.md | 32 ++- ...8_1330_remote_addblock_через_homeserver.md | 4 +- .../shine_homeserver_main.ino | 192 ++++++++++++++++-- shine-UI/js/services/auth-service.js | 160 ++++++++++----- 4 files changed, 319 insertions(+), 69 deletions(-) diff --git a/Dev_Docs/API/05_Technical_Requests_API.md b/Dev_Docs/API/05_Technical_Requests_API.md index 9c3d043..79604bc 100644 --- a/Dev_Docs/API/05_Technical_Requests_API.md +++ b/Dev_Docs/API/05_Technical_Requests_API.md @@ -300,11 +300,11 @@ То есть телефон без локального `blockchain.key` может: -- подготовить unsigned preimage блока; +- подготовить только сырой payload операции без текущей вершины цепочки; - подписать сам `SendSignal` своим `session key`; - дополнительно подписать его `client key`, чтобы homeserver/ESP32 точно видел, что запрос пришёл от доверенного клиента этого же логина; - отправить запрос в выбранную `homeserver`-сессию; -- получить от неё ответ после настоящего `AddBlock`. +- получить от неё ответ после настоящего `AddBlock`, который homeserver соберёт и подпишет уже сама. ### Режимы доставки @@ -348,7 +348,7 @@ "targetSessionId": "sess-hs-001", "signalType": "remote_addblock_request", "signalRequestId": "remote-addblock-001", - "data": "{\"operation\":\"remote_addblock_request\",\"login\":\"alice\",\"blockchainName\":\"alice_main\",\"blockNumber\":152,\"prevBlockHash\":\"abc...\",\"blockPreimageB64\":\"...\"}", + "data": "{\"operation\":\"remote_addblock_request\",\"signalRequestId\":\"remote-addblock-001\",\"blockchainName\":\"alice_main\",\"blockBodyB64\":\"...\"}", "timeMs": 1774700000123, "sessionSignatureB64": "BASE64_64", "clientSignatureB64": "BASE64_64" @@ -385,7 +385,7 @@ "targetSessionId": "sess-hs-001", "signalType": "remote_addblock_request", "signalRequestId": "remote-addblock-001", - "data": "{\"operation\":\"remote_addblock_request\",\"login\":\"alice\",\"blockchainName\":\"alice_main\",\"blockNumber\":152,\"prevBlockHash\":\"abc...\",\"blockPreimageB64\":\"...\"}", + "data": "{\"operation\":\"remote_addblock_request\",\"signalRequestId\":\"remote-addblock-001\",\"blockchainName\":\"alice_main\",\"blockBodyB64\":\"...\"}", "timeMs": 1774700000123, "sessionSignatureB64": "BASE64_64", "clientSignatureB64": "BASE64_64", @@ -394,6 +394,30 @@ } ``` +### Специфика `remote AddBlock` + +Для `remote_addblock_request` поле `data` теперь содержит: + +- `blockchainName` +- `blockBodyB64` + +Где `blockBodyB64` — это не финальный блок и не почти готовый preimage, а компактный бинарный контейнер: + +- `msgType` (`u16`) +- `msgSubType` (`u16`) +- `msgVersion` (`u16`) +- `bodyBytes` + +После этого homeserver сама: + +- вызывает `GetUser(login)` и получает `serverLastGlobalNumber/serverLastGlobalHash`; +- вычисляет новый `blockNumber = last + 1`; +- подставляет актуальный `prevBlockHash`; +- ставит текущее время; +- досчитывает полный preimage; +- подписывает его своим `blockchain key`; +- и только потом делает настоящий `AddBlock`. + ### Специфические коды ошибок `SendSignal` - `422 / NOT_AUTHENTICATED` — требуется авторизация. diff --git a/Dev_Docs/Pending_Features/2026-06-28_1330_remote_addblock_через_homeserver.md b/Dev_Docs/Pending_Features/2026-06-28_1330_remote_addblock_через_homeserver.md index 1e08963..c21749b 100644 --- a/Dev_Docs/Pending_Features/2026-06-28_1330_remote_addblock_через_homeserver.md +++ b/Dev_Docs/Pending_Features/2026-06-28_1330_remote_addblock_через_homeserver.md @@ -10,7 +10,9 @@ - клиент без локального `blockchain.key` выбирает `homeserver`-сессию (`sessionType = 100`); - клиент отправляет в неё `remote_addblock_request` через `SendSignal`; - запрос подписывается `session key` и `client key`; -- ESP32/homeserver автоматически подписывает настоящий `AddBlock` своим `blockchain key` и сам отправляет его на сервер; +- UI больше не передаёт `blockNumber` и `prevBlockHash`; +- UI передаёт только `blockchainName + blockBodyB64`; +- ESP32/homeserver сама делает `GetUser(login)`, получает актуальную вершину цепочки, собирает финальный блок, подписывает настоящий `AddBlock` своим `blockchain key` и сам отправляет его на сервер; - результат возвращается назад сигналом `remote_addblock_result`. ## Что проверить вручную diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino index e4def3b..df9c214 100644 --- a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino @@ -510,11 +510,13 @@ static void saveShineSessionPrefs(); static String normalizeLoginValue(const String &value); static bool base58ToFixed32(const String &value, uint8_t out[32]); static bool base64DecodeStd(const String &value, std::vector &out); +static bool hex64ToBytes(const String &value, uint8_t out[32]); static String bytesToBase64String(const uint8_t *data, size_t len); static String jsonEscape(const String &value); static bool jsonStringField(const String &json, const String &field, String &valueOut); static bool jsonBoolField(const String &json, const String &field, bool &valueOut); static bool jsonInt64Field(const String &json, const String &field, uint64_t &valueOut); +static bool jsonSignedInt64Field(const String &json, const String &field, int64_t &valueOut); static bool parseUrlHostPortPath(const String &url, String &hostOut, uint16_t &portOut, String &pathOut, bool &secureOut); static String formatPairingShortCode(const String &value); static bool pairingMenuVisible(); @@ -938,6 +940,30 @@ static String normalizeLoginValue(const String &value) { return out; } +static int hexNibbleValue(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return 10 + (c - 'a'); + if (c >= 'A' && c <= 'F') return 10 + (c - 'A'); + return -1; +} + +static bool hex64ToBytes(const String &value, uint8_t out[32]) { + String clean = value; + clean.trim(); + if (clean.length() != 64) { + return false; + } + for (size_t i = 0; i < 32; ++i) { + int hi = hexNibbleValue(clean.charAt((int)(i * 2))); + int lo = hexNibbleValue(clean.charAt((int)(i * 2 + 1))); + if (hi < 0 || lo < 0) { + return false; + } + out[i] = (uint8_t)((hi << 4) | lo); + } + return true; +} + static bool isValidShineServerLoginValue(const String &value) { if (value.isEmpty() || value.length() > 20) { return false; @@ -1625,6 +1651,34 @@ static bool jsonInt64Field(const String &json, const String &field, uint64_t &va return true; } +static bool jsonSignedInt64Field(const String &json, const String &field, int64_t &valueOut) { + String needle = "\"" + field + "\""; + int keyPos = json.indexOf(needle); + if (keyPos < 0) { + return false; + } + int colon = json.indexOf(':', keyPos + needle.length()); + if (colon < 0) { + return false; + } + int pos = colon + 1; + while (pos < (int)json.length() && (json[pos] == ' ' || json[pos] == '\n' || json[pos] == '\r' || json[pos] == '\t')) { + pos++; + } + int start = pos; + if (pos < (int)json.length() && json[pos] == '-') { + pos++; + } + while (pos < (int)json.length() && isDigit((unsigned char)json[pos])) { + pos++; + } + if (pos == start || (pos == start + 1 && json[start] == '-')) { + return false; + } + valueOut = strtoll(json.substring(start, pos).c_str(), nullptr, 10); + return true; +} + static String formatSolValue(uint64_t lamports) { uint64_t whole = lamports / 1000000000ULL; uint64_t frac = (lamports % 1000000000ULL) / 1000000ULL; @@ -2853,6 +2907,75 @@ static bool sendSignalResponse(const String &toLogin, return shineWsRequest(gShineWs, "SendSignal", req, response, SHINE_RPC_TIMEOUT_MS); } +static void appendUint16BE(std::vector &out, uint16_t value) { + out.push_back((uint8_t)((value >> 8) & 0xFF)); + out.push_back((uint8_t)(value & 0xFF)); +} + +static void appendInt32BE(std::vector &out, int32_t value) { + uint32_t v = (uint32_t)value; + out.push_back((uint8_t)((v >> 24) & 0xFF)); + out.push_back((uint8_t)((v >> 16) & 0xFF)); + out.push_back((uint8_t)((v >> 8) & 0xFF)); + out.push_back((uint8_t)(v & 0xFF)); +} + +static void appendInt64BE(std::vector &out, int64_t value) { + uint64_t v = (uint64_t)value; + out.push_back((uint8_t)((v >> 56) & 0xFF)); + out.push_back((uint8_t)((v >> 48) & 0xFF)); + out.push_back((uint8_t)((v >> 40) & 0xFF)); + out.push_back((uint8_t)((v >> 32) & 0xFF)); + out.push_back((uint8_t)((v >> 24) & 0xFF)); + out.push_back((uint8_t)((v >> 16) & 0xFF)); + out.push_back((uint8_t)((v >> 8) & 0xFF)); + out.push_back((uint8_t)(v & 0xFF)); +} + +static uint16_t readUint16BE(const uint8_t *data) { + return (uint16_t)(((uint16_t)data[0] << 8) | (uint16_t)data[1]); +} + +static bool fetchRemoteAddBlockCursor(const String &login, + String &blockchainNameOut, + int32_t &lastBlockNumberOut, + String &lastBlockHashOut, + String &errorMessageOut, + String &errorCodeOut) { + const char *kRemoteZeroHash64 = "0000000000000000000000000000000000000000000000000000000000000000"; + blockchainNameOut = ""; + lastBlockNumberOut = -1; + lastBlockHashOut = String(kRemoteZeroHash64); + errorMessageOut = ""; + errorCodeOut = ""; + + String getUserReq = String("{\"login\":\"") + jsonEscape(login) + "\"}"; + String getUserResp; + bool ok = shineWsRequest(gShineWs, "GetUser", getUserReq, getUserResp, SHINE_RPC_TIMEOUT_MS); + uint64_t statusCode = 0; + jsonInt64Field(getUserResp, "status", statusCode); + if (!ok || statusCode != 200) { + jsonStringField(getUserResp, "message", errorMessageOut); + jsonStringField(getUserResp, "code", errorCodeOut); + if (errorCodeOut.isEmpty()) errorCodeOut = ok ? "getuser_rejected" : "getuser_request_failed"; + if (errorMessageOut.isEmpty()) errorMessageOut = ok ? "GetUser rejected by server" : "GetUser request failed"; + return false; + } + + int64_t lastBlockNumberI64 = -1; + jsonStringField(getUserResp, "blockchainName", blockchainNameOut); + jsonStringField(getUserResp, "serverLastGlobalHash", lastBlockHashOut); + if (!jsonSignedInt64Field(getUserResp, "serverLastGlobalNumber", lastBlockNumberI64)) { + lastBlockNumberI64 = -1; + } + lastBlockNumberOut = (int32_t)lastBlockNumberI64; + if (lastBlockHashOut.isEmpty()) { + lastBlockHashOut = String(kRemoteZeroHash64); + } + lastBlockHashOut.toLowerCase(); + return true; +} + static void queueWalletSignRequest(const PendingWalletRpcRequest &item, const String &requestId, const String &publicKeyBase58, @@ -4393,11 +4516,8 @@ static void processPendingRemoteAddBlockRequests() { gPendingRemoteAddBlockRequests.erase(gPendingRemoteAddBlockRequests.begin()); String responseData; - String requestLogin; String blockchainName; - String prevBlockHash; - String blockPreimageB64; - uint64_t blockNumberU64 = 0; + String blockBodyB64; if (item.fromLogin != gLoginValue) { responseData = String("{\"ok\":false,\"error\":\"forbidden_login\",\"errorMessage\":\"Signal login mismatch\",\"requestId\":\"") @@ -4412,26 +4532,68 @@ static void processPendingRemoteAddBlockRequests() { continue; } - jsonStringField(item.data, "login", requestLogin); jsonStringField(item.data, "blockchainName", blockchainName); - jsonStringField(item.data, "prevBlockHash", prevBlockHash); - jsonStringField(item.data, "blockPreimageB64", blockPreimageB64); - jsonInt64Field(item.data, "blockNumber", blockNumberU64); - if (requestLogin != gLoginValue || blockchainName.isEmpty() || blockPreimageB64.isEmpty() || prevBlockHash.isEmpty()) { + jsonStringField(item.data, "blockBodyB64", blockBodyB64); + if (blockchainName.isEmpty() || blockBodyB64.isEmpty()) { responseData = String("{\"ok\":false,\"error\":\"bad_request\",\"errorMessage\":\"Missing required AddBlock fields\",\"requestId\":\"") + jsonEscape(item.signalRequestId) + "\"}"; sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData); continue; } - std::vector preimage; - if (!base64DecodeStd(blockPreimageB64, preimage) || preimage.empty()) { - responseData = String("{\"ok\":false,\"error\":\"bad_preimage_base64\",\"errorMessage\":\"Invalid AddBlock preimage base64\",\"requestId\":\"") + std::vector remoteBody; + if (!base64DecodeStd(blockBodyB64, remoteBody) || remoteBody.size() < 6) { + responseData = String("{\"ok\":false,\"error\":\"bad_block_body_base64\",\"errorMessage\":\"Invalid remote block body base64\",\"requestId\":\"") + jsonEscape(item.signalRequestId) + "\"}"; sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData); continue; } + uint16_t msgType = readUint16BE(remoteBody.data()); + uint16_t msgSubType = readUint16BE(remoteBody.data() + 2); + uint16_t msgVersion = readUint16BE(remoteBody.data() + 4); + std::vector bodyBytes(remoteBody.begin() + 6, remoteBody.end()); + + String resolvedBlockchainName; + int32_t lastBlockNumber = -1; + String prevBlockHash; + String cursorError; + String cursorErrorCode; + if (!fetchRemoteAddBlockCursor(gLoginValue, resolvedBlockchainName, lastBlockNumber, prevBlockHash, cursorError, cursorErrorCode)) { + responseData = String("{\"ok\":false,\"error\":\"") + jsonEscape(cursorErrorCode) + + "\",\"errorMessage\":\"" + jsonEscape(cursorError) + + "\",\"requestId\":\"" + jsonEscape(item.signalRequestId) + "\"}"; + sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData); + continue; + } + if (resolvedBlockchainName.isEmpty()) { + resolvedBlockchainName = blockchainName; + } + int32_t nextBlockNumber = lastBlockNumber + 1; + String cleanPrevHash = prevBlockHash.isEmpty() + ? String("0000000000000000000000000000000000000000000000000000000000000000") + : prevBlockHash; + uint8_t prevHash32[32] = {}; + if (!hex64ToBytes(cleanPrevHash, prevHash32)) { + responseData = String("{\"ok\":false,\"error\":\"bad_prev_hash\",\"errorMessage\":\"Invalid previous block hash from GetUser\",\"requestId\":\"") + + jsonEscape(item.signalRequestId) + "\"}"; + sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData); + continue; + } + + std::vector preimage; + preimage.reserve(2 + 32 + 4 + 4 + 8 + 2 + 2 + 2 + bodyBytes.size()); + appendUint16BE(preimage, 0); + preimage.insert(preimage.end(), prevHash32, prevHash32 + 32); + int32_t blockSize = (int32_t)(2 + 32 + 4 + 4 + 8 + 2 + 2 + 2 + bodyBytes.size()); + appendInt32BE(preimage, blockSize); + appendInt32BE(preimage, nextBlockNumber); + appendInt64BE(preimage, (int64_t)(shineNowMs() / 1000ULL)); + appendUint16BE(preimage, msgType); + appendUint16BE(preimage, msgSubType); + appendUint16BE(preimage, msgVersion); + preimage.insert(preimage.end(), bodyBytes.begin(), bodyBytes.end()); + uint8_t blockchainSeed[32] = {}; uint8_t blockchainPub[32] = {}; uint8_t blockchainSec[64] = {}; @@ -4456,9 +4618,9 @@ static void processPendingRemoteAddBlockRequests() { fullBlock.push_back(0x01); fullBlock.push_back(0x00); fullBlock.insert(fullBlock.end(), signature, signature + 64); - String addBlockReq = String("{\"blockchainName\":\"") + jsonEscape(blockchainName) - + "\",\"blockNumber\":" + String((unsigned long long)blockNumberU64) - + ",\"prevBlockHash\":\"" + jsonEscape(prevBlockHash) + String addBlockReq = String("{\"blockchainName\":\"") + jsonEscape(resolvedBlockchainName) + + "\",\"blockNumber\":" + String((long long)nextBlockNumber) + + ",\"prevBlockHash\":\"" + jsonEscape(cleanPrevHash) + "\",\"blockBytesB64\":\"" + jsonEscape(bytesToBase64String(fullBlock.data(), fullBlock.size())) + "\"}"; String addBlockResp; bool addBlockOk = shineWsRequest(gShineWs, "AddBlock", addBlockReq, addBlockResp, SHINE_RPC_TIMEOUT_MS); diff --git a/shine-UI/js/services/auth-service.js b/shine-UI/js/services/auth-service.js index c950e9a..7f451fa 100644 --- a/shine-UI/js/services/auth-service.js +++ b/shine-UI/js/services/auth-service.js @@ -751,6 +751,16 @@ function buildBlockPreimage({ prevBlockHashHex, blockNumber, msgType, msgSubType ); } +function buildRemoteBlockBodyBytes({ msgType, msgSubType, msgVersion = 1, bodyBytes }) { + const body = bodyBytes || new Uint8Array(0); + return concatBytes( + int16Bytes(msgType), + int16Bytes(msgSubType), + int16Bytes(msgVersion), + body, + ); +} + export class AuthService { constructor(serverUrl) { this.serverUrl = normalizeServerUrl(serverUrl); @@ -1437,6 +1447,65 @@ export class AuthService { }; } + async submitRemoteAddBlockBody({ login, storagePwd, blockchainName, blockBodyBytes }) { + const cleanLogin = String(login || '').trim(); + const cleanBlockchainName = String(blockchainName || '').trim(); + if (!cleanLogin || !cleanBlockchainName) throw new Error('submitRemoteAddBlockBody: missing login/blockchainName'); + if (!(blockBodyBytes instanceof Uint8Array) || blockBodyBytes.length < 6) { + throw new Error('submitRemoteAddBlockBody: bad blockBodyBytes'); + } + + const remoteSessionId = String(this.remoteAddBlockSessionId || '').trim(); + if (!remoteSessionId) { + throw new Error('На устройстве нет blockchain key и не выбрана homeserver-сессия для remote AddBlock'); + } + + const signalRequestId = createSignalRequestId('remote-addblock'); + const responseWait = this.waitForSignal({ + signalType: SIGNAL_TYPE_REMOTE_ADDBLOCK_RESULT, + signalRequestId, + timeoutMs: 20000, + }); + + const signalData = { + operation: SIGNAL_TYPE_REMOTE_ADDBLOCK_REQUEST, + signalRequestId, + blockchainName: cleanBlockchainName, + blockBodyB64: bytesToBase64(blockBodyBytes), + }; + + await this.sendSignal({ + toLogin: cleanLogin, + targetMode: SIGNAL_TARGET_SINGLE, + targetSessionId: remoteSessionId, + signalType: SIGNAL_TYPE_REMOTE_ADDBLOCK_REQUEST, + signalRequestId, + data: JSON.stringify(signalData), + storagePwd, + includeClientSignature: true, + }); + + const signalPayload = await responseWait; + let result = {}; + try { + result = JSON.parse(String(signalPayload?.data || '{}')); + } catch { + throw new Error('Некорректный ответ remote AddBlock от homeserver'); + } + if (!result?.ok) { + throw new Error(String(result?.errorMessage || result?.error || 'remote_addblock_failed')); + } + + return { + status: 200, + payload: { + serverLastGlobalNumber: Number(result?.serverLastGlobalNumber ?? -1), + serverLastGlobalHash: String(result?.serverLastGlobalHash || ZERO_HASH_HEX), + remote: true, + }, + }; + } + async runAddBlockWithRetry({ login, storagePwd, resolveFreshState, buildPreimage }) { let freshState = await resolveFreshState(); let blockchainName = String(freshState?.blockchainName || '').trim(); @@ -1484,6 +1553,21 @@ export class AuthService { if (!cleanLogin) throw new Error('Missing login for AddBlock'); if (!storagePwd) throw new Error('Missing storagePwd for AddBlock signing'); + const keyBundle = await loadEncryptedUserSecrets(cleanLogin, storagePwd); + const blockchainPrivatePkcs8 = String(keyBundle?.blockchainKey || '').trim(); + if (!blockchainPrivatePkcs8) { + const user = await this.getUser(cleanLogin); + const blockchainName = String(user?.blockchainName || `${cleanLogin}-${BCH_SUFFIX}`).trim(); + if (!blockchainName) throw new Error('Не удалось определить blockchainName для remote AddBlock'); + const response = await this.submitRemoteAddBlockBody({ + login: cleanLogin, + storagePwd, + blockchainName, + blockBodyBytes: buildRemoteBlockBodyBytes({ msgType, msgSubType, msgVersion, bodyBytes }), + }); + return response.payload || {}; + } + const { response, blockchainName } = await this.runAddBlockWithRetry({ login: cleanLogin, storagePwd, @@ -2458,33 +2542,22 @@ export class AuthService { if (!cleanLogin || !cleanParam) throw new Error('Не переданы login/param.'); if (!cleanValue) throw new Error('Значение параметра не может быть пустым.'); if (!storagePwd) throw new Error('Не передан storagePwd для подписи AddBlock.'); - const { response } = await this.runAddBlockWithRetry({ + const bodyBytes = makeUserParamBodyBytes({ + lineCode: 0, + prevLineNumber: -1, + prevLineHashHex: ZERO_HASH_HEX, + thisLineNumber: -1, + key: cleanParam, + value: cleanValue, + }); + return this.addBlockSigned({ login: cleanLogin, storagePwd, - resolveFreshState: () => this.resolveFreshBlockchainCursor(cleanLogin), - buildPreimage: async ({ blockNumber, prevBlockHash }) => { - const bodyBytes = makeUserParamBodyBytes({ - lineCode: 0, - prevLineNumber: -1, - prevLineHashHex: ZERO_HASH_HEX, - thisLineNumber: -1, - key: cleanParam, - value: cleanValue, - }); - return concatBytes( - int16Bytes(0), - hexToBytes(prevBlockHash), - int32Bytes(2 + 32 + 4 + 4 + 8 + 2 + 2 + 2 + bodyBytes.length), - int32Bytes(blockNumber), - int64Bytes(Math.floor(Date.now() / 1000)), - int16Bytes(4), - int16Bytes(1), - int16Bytes(1), - bodyBytes, - ); - }, + msgType: 4, + msgSubType: 1, + msgVersion: 1, + bodyBytes, }); - return response.payload || {}; } async addBlockConnection({ login, toLogin, subType, storagePwd }) { @@ -2504,34 +2577,23 @@ export class AuthService { const targetUser = await this.getUser(cleanToLogin); if (!targetUser?.exists) throw new Error('Пользователь цели не найден.'); const toBlockchainName = String(targetUser?.blockchainName || `${cleanToLogin}-${BCH_SUFFIX}`).trim(); - const { response } = await this.runAddBlockWithRetry({ + const bodyBytes = makeConnectionBodyBytes({ + lineCode: 0, + prevLineNumber: -1, + prevLineHashHex: ZERO_HASH_HEX, + thisLineNumber: -1, + toBlockchainName, + toBlockNumber: 0, + toBlockHashHex: ZERO_HASH_HEX, + }); + return this.addBlockSigned({ login: cleanLogin, storagePwd, - resolveFreshState: () => this.resolveFreshBlockchainCursor(cleanLogin), - buildPreimage: async ({ blockNumber, prevBlockHash }) => { - const bodyBytes = makeConnectionBodyBytes({ - lineCode: 0, - prevLineNumber: -1, - prevLineHashHex: ZERO_HASH_HEX, - thisLineNumber: -1, - toBlockchainName, - toBlockNumber: 0, - toBlockHashHex: ZERO_HASH_HEX, - }); - return concatBytes( - int16Bytes(0), - hexToBytes(prevBlockHash), - int32Bytes(2 + 32 + 4 + 4 + 8 + 2 + 2 + 2 + bodyBytes.length), - int32Bytes(blockNumber), - int64Bytes(Math.floor(Date.now() / 1000)), - int16Bytes(3), - int16Bytes(cleanSubType), - int16Bytes(1), - bodyBytes, - ); - }, + msgType: 3, + msgSubType: cleanSubType, + msgVersion: 1, + bodyBytes, }); - return response.payload || {}; }