Переделать remote AddBlock на сборку блока на homeserver

This commit is contained in:
AidarKC
2026-06-28 14:45:29 +04:00
parent 3068c3e2b8
commit ed83b1f906
4 changed files with 319 additions and 69 deletions
+111 -49
View File
@@ -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 || {};
}