Переделать remote AddBlock на сборку блока на homeserver
This commit is contained in:
parent
3068c3e2b8
commit
ed83b1f906
@ -300,11 +300,11 @@
|
|||||||
|
|
||||||
То есть телефон без локального `blockchain.key` может:
|
То есть телефон без локального `blockchain.key` может:
|
||||||
|
|
||||||
- подготовить unsigned preimage блока;
|
- подготовить только сырой payload операции без текущей вершины цепочки;
|
||||||
- подписать сам `SendSignal` своим `session key`;
|
- подписать сам `SendSignal` своим `session key`;
|
||||||
- дополнительно подписать его `client key`, чтобы homeserver/ESP32 точно видел, что запрос пришёл от доверенного клиента этого же логина;
|
- дополнительно подписать его `client key`, чтобы homeserver/ESP32 точно видел, что запрос пришёл от доверенного клиента этого же логина;
|
||||||
- отправить запрос в выбранную `homeserver`-сессию;
|
- отправить запрос в выбранную `homeserver`-сессию;
|
||||||
- получить от неё ответ после настоящего `AddBlock`.
|
- получить от неё ответ после настоящего `AddBlock`, который homeserver соберёт и подпишет уже сама.
|
||||||
|
|
||||||
### Режимы доставки
|
### Режимы доставки
|
||||||
|
|
||||||
@ -348,7 +348,7 @@
|
|||||||
"targetSessionId": "sess-hs-001",
|
"targetSessionId": "sess-hs-001",
|
||||||
"signalType": "remote_addblock_request",
|
"signalType": "remote_addblock_request",
|
||||||
"signalRequestId": "remote-addblock-001",
|
"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,
|
"timeMs": 1774700000123,
|
||||||
"sessionSignatureB64": "BASE64_64",
|
"sessionSignatureB64": "BASE64_64",
|
||||||
"clientSignatureB64": "BASE64_64"
|
"clientSignatureB64": "BASE64_64"
|
||||||
@ -385,7 +385,7 @@
|
|||||||
"targetSessionId": "sess-hs-001",
|
"targetSessionId": "sess-hs-001",
|
||||||
"signalType": "remote_addblock_request",
|
"signalType": "remote_addblock_request",
|
||||||
"signalRequestId": "remote-addblock-001",
|
"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,
|
"timeMs": 1774700000123,
|
||||||
"sessionSignatureB64": "BASE64_64",
|
"sessionSignatureB64": "BASE64_64",
|
||||||
"clientSignatureB64": "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`
|
### Специфические коды ошибок `SendSignal`
|
||||||
|
|
||||||
- `422 / NOT_AUTHENTICATED` — требуется авторизация.
|
- `422 / NOT_AUTHENTICATED` — требуется авторизация.
|
||||||
|
|||||||
@ -10,7 +10,9 @@
|
|||||||
- клиент без локального `blockchain.key` выбирает `homeserver`-сессию (`sessionType = 100`);
|
- клиент без локального `blockchain.key` выбирает `homeserver`-сессию (`sessionType = 100`);
|
||||||
- клиент отправляет в неё `remote_addblock_request` через `SendSignal`;
|
- клиент отправляет в неё `remote_addblock_request` через `SendSignal`;
|
||||||
- запрос подписывается `session key` и `client key`;
|
- запрос подписывается `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`.
|
- результат возвращается назад сигналом `remote_addblock_result`.
|
||||||
|
|
||||||
## Что проверить вручную
|
## Что проверить вручную
|
||||||
|
|||||||
@ -510,11 +510,13 @@ static void saveShineSessionPrefs();
|
|||||||
static String normalizeLoginValue(const String &value);
|
static String normalizeLoginValue(const String &value);
|
||||||
static bool base58ToFixed32(const String &value, uint8_t out[32]);
|
static bool base58ToFixed32(const String &value, uint8_t out[32]);
|
||||||
static bool base64DecodeStd(const String &value, std::vector<uint8_t> &out);
|
static bool base64DecodeStd(const String &value, std::vector<uint8_t> &out);
|
||||||
|
static bool hex64ToBytes(const String &value, uint8_t out[32]);
|
||||||
static String bytesToBase64String(const uint8_t *data, size_t len);
|
static String bytesToBase64String(const uint8_t *data, size_t len);
|
||||||
static String jsonEscape(const String &value);
|
static String jsonEscape(const String &value);
|
||||||
static bool jsonStringField(const String &json, const String &field, String &valueOut);
|
static bool jsonStringField(const String &json, const String &field, String &valueOut);
|
||||||
static bool jsonBoolField(const String &json, const String &field, bool &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 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 bool parseUrlHostPortPath(const String &url, String &hostOut, uint16_t &portOut, String &pathOut, bool &secureOut);
|
||||||
static String formatPairingShortCode(const String &value);
|
static String formatPairingShortCode(const String &value);
|
||||||
static bool pairingMenuVisible();
|
static bool pairingMenuVisible();
|
||||||
@ -938,6 +940,30 @@ static String normalizeLoginValue(const String &value) {
|
|||||||
return out;
|
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) {
|
static bool isValidShineServerLoginValue(const String &value) {
|
||||||
if (value.isEmpty() || value.length() > 20) {
|
if (value.isEmpty() || value.length() > 20) {
|
||||||
return false;
|
return false;
|
||||||
@ -1625,6 +1651,34 @@ static bool jsonInt64Field(const String &json, const String &field, uint64_t &va
|
|||||||
return true;
|
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) {
|
static String formatSolValue(uint64_t lamports) {
|
||||||
uint64_t whole = lamports / 1000000000ULL;
|
uint64_t whole = lamports / 1000000000ULL;
|
||||||
uint64_t frac = (lamports % 1000000000ULL) / 1000000ULL;
|
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);
|
return shineWsRequest(gShineWs, "SendSignal", req, response, SHINE_RPC_TIMEOUT_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void appendUint16BE(std::vector<uint8_t> &out, uint16_t value) {
|
||||||
|
out.push_back((uint8_t)((value >> 8) & 0xFF));
|
||||||
|
out.push_back((uint8_t)(value & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void appendInt32BE(std::vector<uint8_t> &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<uint8_t> &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,
|
static void queueWalletSignRequest(const PendingWalletRpcRequest &item,
|
||||||
const String &requestId,
|
const String &requestId,
|
||||||
const String &publicKeyBase58,
|
const String &publicKeyBase58,
|
||||||
@ -4393,11 +4516,8 @@ static void processPendingRemoteAddBlockRequests() {
|
|||||||
gPendingRemoteAddBlockRequests.erase(gPendingRemoteAddBlockRequests.begin());
|
gPendingRemoteAddBlockRequests.erase(gPendingRemoteAddBlockRequests.begin());
|
||||||
|
|
||||||
String responseData;
|
String responseData;
|
||||||
String requestLogin;
|
|
||||||
String blockchainName;
|
String blockchainName;
|
||||||
String prevBlockHash;
|
String blockBodyB64;
|
||||||
String blockPreimageB64;
|
|
||||||
uint64_t blockNumberU64 = 0;
|
|
||||||
|
|
||||||
if (item.fromLogin != gLoginValue) {
|
if (item.fromLogin != gLoginValue) {
|
||||||
responseData = String("{\"ok\":false,\"error\":\"forbidden_login\",\"errorMessage\":\"Signal login mismatch\",\"requestId\":\"")
|
responseData = String("{\"ok\":false,\"error\":\"forbidden_login\",\"errorMessage\":\"Signal login mismatch\",\"requestId\":\"")
|
||||||
@ -4412,26 +4532,68 @@ static void processPendingRemoteAddBlockRequests() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonStringField(item.data, "login", requestLogin);
|
|
||||||
jsonStringField(item.data, "blockchainName", blockchainName);
|
jsonStringField(item.data, "blockchainName", blockchainName);
|
||||||
jsonStringField(item.data, "prevBlockHash", prevBlockHash);
|
jsonStringField(item.data, "blockBodyB64", blockBodyB64);
|
||||||
jsonStringField(item.data, "blockPreimageB64", blockPreimageB64);
|
if (blockchainName.isEmpty() || blockBodyB64.isEmpty()) {
|
||||||
jsonInt64Field(item.data, "blockNumber", blockNumberU64);
|
|
||||||
if (requestLogin != gLoginValue || blockchainName.isEmpty() || blockPreimageB64.isEmpty() || prevBlockHash.isEmpty()) {
|
|
||||||
responseData = String("{\"ok\":false,\"error\":\"bad_request\",\"errorMessage\":\"Missing required AddBlock fields\",\"requestId\":\"")
|
responseData = String("{\"ok\":false,\"error\":\"bad_request\",\"errorMessage\":\"Missing required AddBlock fields\",\"requestId\":\"")
|
||||||
+ jsonEscape(item.signalRequestId) + "\"}";
|
+ jsonEscape(item.signalRequestId) + "\"}";
|
||||||
sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData);
|
sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> preimage;
|
std::vector<uint8_t> remoteBody;
|
||||||
if (!base64DecodeStd(blockPreimageB64, preimage) || preimage.empty()) {
|
if (!base64DecodeStd(blockBodyB64, remoteBody) || remoteBody.size() < 6) {
|
||||||
responseData = String("{\"ok\":false,\"error\":\"bad_preimage_base64\",\"errorMessage\":\"Invalid AddBlock preimage base64\",\"requestId\":\"")
|
responseData = String("{\"ok\":false,\"error\":\"bad_block_body_base64\",\"errorMessage\":\"Invalid remote block body base64\",\"requestId\":\"")
|
||||||
+ jsonEscape(item.signalRequestId) + "\"}";
|
+ jsonEscape(item.signalRequestId) + "\"}";
|
||||||
sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData);
|
sendSignalResponse(item.fromLogin, item.fromSessionId, "remote_addblock_result", item.signalRequestId, responseData);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t msgType = readUint16BE(remoteBody.data());
|
||||||
|
uint16_t msgSubType = readUint16BE(remoteBody.data() + 2);
|
||||||
|
uint16_t msgVersion = readUint16BE(remoteBody.data() + 4);
|
||||||
|
std::vector<uint8_t> 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<uint8_t> 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 blockchainSeed[32] = {};
|
||||||
uint8_t blockchainPub[32] = {};
|
uint8_t blockchainPub[32] = {};
|
||||||
uint8_t blockchainSec[64] = {};
|
uint8_t blockchainSec[64] = {};
|
||||||
@ -4456,9 +4618,9 @@ static void processPendingRemoteAddBlockRequests() {
|
|||||||
fullBlock.push_back(0x01);
|
fullBlock.push_back(0x01);
|
||||||
fullBlock.push_back(0x00);
|
fullBlock.push_back(0x00);
|
||||||
fullBlock.insert(fullBlock.end(), signature, signature + 64);
|
fullBlock.insert(fullBlock.end(), signature, signature + 64);
|
||||||
String addBlockReq = String("{\"blockchainName\":\"") + jsonEscape(blockchainName)
|
String addBlockReq = String("{\"blockchainName\":\"") + jsonEscape(resolvedBlockchainName)
|
||||||
+ "\",\"blockNumber\":" + String((unsigned long long)blockNumberU64)
|
+ "\",\"blockNumber\":" + String((long long)nextBlockNumber)
|
||||||
+ ",\"prevBlockHash\":\"" + jsonEscape(prevBlockHash)
|
+ ",\"prevBlockHash\":\"" + jsonEscape(cleanPrevHash)
|
||||||
+ "\",\"blockBytesB64\":\"" + jsonEscape(bytesToBase64String(fullBlock.data(), fullBlock.size())) + "\"}";
|
+ "\",\"blockBytesB64\":\"" + jsonEscape(bytesToBase64String(fullBlock.data(), fullBlock.size())) + "\"}";
|
||||||
String addBlockResp;
|
String addBlockResp;
|
||||||
bool addBlockOk = shineWsRequest(gShineWs, "AddBlock", addBlockReq, addBlockResp, SHINE_RPC_TIMEOUT_MS);
|
bool addBlockOk = shineWsRequest(gShineWs, "AddBlock", addBlockReq, addBlockResp, SHINE_RPC_TIMEOUT_MS);
|
||||||
|
|||||||
@ -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 {
|
export class AuthService {
|
||||||
constructor(serverUrl) {
|
constructor(serverUrl) {
|
||||||
this.serverUrl = normalizeServerUrl(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 }) {
|
async runAddBlockWithRetry({ login, storagePwd, resolveFreshState, buildPreimage }) {
|
||||||
let freshState = await resolveFreshState();
|
let freshState = await resolveFreshState();
|
||||||
let blockchainName = String(freshState?.blockchainName || '').trim();
|
let blockchainName = String(freshState?.blockchainName || '').trim();
|
||||||
@ -1484,6 +1553,21 @@ export class AuthService {
|
|||||||
if (!cleanLogin) throw new Error('Missing login for AddBlock');
|
if (!cleanLogin) throw new Error('Missing login for AddBlock');
|
||||||
if (!storagePwd) throw new Error('Missing storagePwd for AddBlock signing');
|
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({
|
const { response, blockchainName } = await this.runAddBlockWithRetry({
|
||||||
login: cleanLogin,
|
login: cleanLogin,
|
||||||
storagePwd,
|
storagePwd,
|
||||||
@ -2458,33 +2542,22 @@ export class AuthService {
|
|||||||
if (!cleanLogin || !cleanParam) throw new Error('Не переданы login/param.');
|
if (!cleanLogin || !cleanParam) throw new Error('Не переданы login/param.');
|
||||||
if (!cleanValue) throw new Error('Значение параметра не может быть пустым.');
|
if (!cleanValue) throw new Error('Значение параметра не может быть пустым.');
|
||||||
if (!storagePwd) throw new Error('Не передан storagePwd для подписи AddBlock.');
|
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,
|
login: cleanLogin,
|
||||||
storagePwd,
|
storagePwd,
|
||||||
resolveFreshState: () => this.resolveFreshBlockchainCursor(cleanLogin),
|
msgType: 4,
|
||||||
buildPreimage: async ({ blockNumber, prevBlockHash }) => {
|
msgSubType: 1,
|
||||||
const bodyBytes = makeUserParamBodyBytes({
|
msgVersion: 1,
|
||||||
lineCode: 0,
|
bodyBytes,
|
||||||
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,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
return response.payload || {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async addBlockConnection({ login, toLogin, subType, storagePwd }) {
|
async addBlockConnection({ login, toLogin, subType, storagePwd }) {
|
||||||
@ -2504,34 +2577,23 @@ export class AuthService {
|
|||||||
const targetUser = await this.getUser(cleanToLogin);
|
const targetUser = await this.getUser(cleanToLogin);
|
||||||
if (!targetUser?.exists) throw new Error('Пользователь цели не найден.');
|
if (!targetUser?.exists) throw new Error('Пользователь цели не найден.');
|
||||||
const toBlockchainName = String(targetUser?.blockchainName || `${cleanToLogin}-${BCH_SUFFIX}`).trim();
|
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,
|
login: cleanLogin,
|
||||||
storagePwd,
|
storagePwd,
|
||||||
resolveFreshState: () => this.resolveFreshBlockchainCursor(cleanLogin),
|
msgType: 3,
|
||||||
buildPreimage: async ({ blockNumber, prevBlockHash }) => {
|
msgSubType: cleanSubType,
|
||||||
const bodyBytes = makeConnectionBodyBytes({
|
msgVersion: 1,
|
||||||
lineCode: 0,
|
bodyBytes,
|
||||||
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,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
return response.payload || {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user