SHA256
Очень сильно переделать формат блоков
Внимание: версия ещё не проверена.
This commit is contained in:
@@ -50,6 +50,7 @@ const MSG_TYPE_REACTION = 2;
|
||||
const MSG_TYPE_CONNECTION = 3;
|
||||
const MSG_TYPE_STATUS_ACTION = 5;
|
||||
|
||||
const MSG_SUBTYPE_TECH_HEADER = 0;
|
||||
const MSG_SUBTYPE_TECH_CREATE_CHANNEL = 1;
|
||||
const MSG_SUBTYPE_TEXT_POST = 10;
|
||||
const MSG_SUBTYPE_TEXT_EDIT_POST = 11;
|
||||
@@ -534,6 +535,24 @@ function extractContactsFromDialogs(dialogs) {
|
||||
return out;
|
||||
}
|
||||
|
||||
function makeHeaderBodyBytes({ login, initialBlockchainKey32 }) {
|
||||
const cleanLogin = String(login || '').trim();
|
||||
const loginBytes = utf8Bytes(cleanLogin);
|
||||
const keyBytes = initialBlockchainKey32 instanceof Uint8Array
|
||||
? initialBlockchainKey32
|
||||
: new Uint8Array(initialBlockchainKey32 || []);
|
||||
if (loginBytes.length < 1 || loginBytes.length > 255) throw new Error('HEADER login must be 1..255 UTF-8 bytes');
|
||||
if (keyBytes.length !== 32 || keyBytes.every((value) => value === 0)) {
|
||||
throw new Error('HEADER initialBlockchainKey32 must be a non-zero 32-byte key');
|
||||
}
|
||||
return concatBytes(
|
||||
utf8Bytes('SHiNE'),
|
||||
int8Byte(loginBytes.length),
|
||||
loginBytes,
|
||||
keyBytes,
|
||||
);
|
||||
}
|
||||
|
||||
function makeUserParamBodyBytes({ lineCode, prevLineNumber, prevLineHashHex, thisLineNumber, key, value }) {
|
||||
const keyBytes = utf8Bytes(String(key || ''));
|
||||
const valueBytes = utf8Bytes(String(value || ''));
|
||||
@@ -553,262 +572,124 @@ function makeUserParamBodyBytes({ lineCode, prevLineNumber, prevLineHashHex, thi
|
||||
);
|
||||
}
|
||||
|
||||
function makeReactionLikeBodyBytes({ toLogin, toBlockNumber, toBlockHashHex }) {
|
||||
function makeReactionLikeBodyBytes({ toLogin, toForkNumber, toBlockNumber, toBlockHashHex }) {
|
||||
const cleanLogin = String(toLogin || '').trim();
|
||||
if (!cleanLogin) throw new Error('toLogin is required for like');
|
||||
|
||||
const blockNumber = Number(toBlockNumber);
|
||||
if (!Number.isFinite(blockNumber) || blockNumber < 0) {
|
||||
throw new Error('Invalid toBlockNumber for like');
|
||||
}
|
||||
|
||||
const loginBytes = utf8Bytes(cleanLogin);
|
||||
if (loginBytes.length < 1 || loginBytes.length > 255) {
|
||||
throw new Error('toLogin must be 1..255 bytes');
|
||||
}
|
||||
|
||||
return concatBytes(
|
||||
int8Byte(loginBytes.length),
|
||||
loginBytes,
|
||||
int32Bytes(blockNumber),
|
||||
hexToBytes(normalizeHex32(toBlockHashHex))
|
||||
);
|
||||
const forkNumber = normalizeTargetForkNumber(toForkNumber, 'like');
|
||||
const blockNumber = normalizeTargetBlockNumber(toBlockNumber, 'like');
|
||||
const loginBytes = targetLoginBytes(cleanLogin);
|
||||
const hashBytes = nonZeroHash32Bytes(toBlockHashHex, 'like');
|
||||
return concatBytes(int8Byte(loginBytes.length), loginBytes, int16Bytes(forkNumber), int32Bytes(blockNumber), hashBytes);
|
||||
}
|
||||
|
||||
function makeTextReplyBodyBytes({ toLogin, toBlockNumber, toBlockHashHex, text }) {
|
||||
const cleanLogin = String(toLogin || '').trim();
|
||||
if (!cleanLogin) throw new Error('toLogin is required for reply');
|
||||
|
||||
const blockNumber = Number(toBlockNumber);
|
||||
if (!Number.isFinite(blockNumber) || blockNumber < 0) {
|
||||
throw new Error('Invalid toBlockNumber for reply');
|
||||
}
|
||||
|
||||
const message = String(text || '').trim();
|
||||
if (!message) throw new Error('Reply text is required');
|
||||
|
||||
const loginBytes = utf8Bytes(cleanLogin);
|
||||
if (loginBytes.length < 1 || loginBytes.length > 255) {
|
||||
throw new Error('toLogin must be 1..255 bytes');
|
||||
}
|
||||
|
||||
const textBytes = utf8Bytes(message);
|
||||
if (textBytes.length < 1 || textBytes.length > 65535) {
|
||||
throw new Error('Reply text must be 1..65535 UTF-8 bytes');
|
||||
}
|
||||
|
||||
return concatBytes(
|
||||
int8Byte(loginBytes.length),
|
||||
loginBytes,
|
||||
int32Bytes(blockNumber),
|
||||
hexToBytes(normalizeHex32(toBlockHashHex)),
|
||||
int16Bytes(textBytes.length),
|
||||
textBytes
|
||||
);
|
||||
}
|
||||
|
||||
function makeTextRatingBodyBytes({ toLogin, toBlockNumber, toBlockHashHex, text }) {
|
||||
const cleanLogin = String(toLogin || '').trim();
|
||||
if (!cleanLogin) throw new Error('toLogin is required for rating');
|
||||
|
||||
const blockNumber = Number(toBlockNumber);
|
||||
if (!Number.isFinite(blockNumber) || blockNumber < 0) {
|
||||
throw new Error('Invalid toBlockNumber for rating');
|
||||
}
|
||||
|
||||
const message = String(text || '').trim();
|
||||
if (!message) throw new Error('Rating text is required');
|
||||
|
||||
const loginBytes = utf8Bytes(cleanLogin);
|
||||
if (loginBytes.length < 1 || loginBytes.length > 255) {
|
||||
throw new Error('toLogin must be 1..255 bytes');
|
||||
}
|
||||
|
||||
const textBytes = utf8Bytes(message);
|
||||
if (textBytes.length < 1 || textBytes.length > 65535) {
|
||||
throw new Error('Rating text must be 1..65535 UTF-8 bytes');
|
||||
}
|
||||
|
||||
return concatBytes(
|
||||
int8Byte(loginBytes.length),
|
||||
loginBytes,
|
||||
int32Bytes(blockNumber),
|
||||
hexToBytes(normalizeHex32(toBlockHashHex)),
|
||||
int16Bytes(textBytes.length),
|
||||
textBytes
|
||||
);
|
||||
}
|
||||
|
||||
function makeStatusActionBodyBytes({ toLogin, toBlockNumber, toBlockHashHex, text = '' }) {
|
||||
const cleanLogin = String(toLogin || '').trim();
|
||||
if (!cleanLogin) throw new Error('toLogin is required for status action');
|
||||
|
||||
const blockNumber = Number(toBlockNumber);
|
||||
if (!Number.isFinite(blockNumber) || blockNumber < 0) {
|
||||
throw new Error('Invalid toBlockNumber for status action');
|
||||
}
|
||||
|
||||
const loginBytes = utf8Bytes(cleanLogin);
|
||||
if (loginBytes.length < 1 || loginBytes.length > 255) {
|
||||
throw new Error('toLogin must be 1..255 bytes');
|
||||
}
|
||||
|
||||
function makeTextReplyBodyBytes({ toLogin, toForkNumber, toBlockNumber, toBlockHashHex, text }) {
|
||||
const loginBytes = targetLoginBytes(toLogin);
|
||||
const forkNumber = normalizeTargetForkNumber(toForkNumber, 'reply');
|
||||
const blockNumber = normalizeTargetBlockNumber(toBlockNumber, 'reply');
|
||||
const hashBytes = nonZeroHash32Bytes(toBlockHashHex, 'reply');
|
||||
const textBytes = utf8Bytes(String(text || '').trim());
|
||||
if (textBytes.length > 65535) {
|
||||
throw new Error('Status action text must be 0..65535 UTF-8 bytes');
|
||||
}
|
||||
if (textBytes.length < 1 || textBytes.length > 65535) throw new Error('Reply text must be 1..65535 UTF-8 bytes');
|
||||
return concatBytes(int8Byte(loginBytes.length), loginBytes, int16Bytes(forkNumber), int32Bytes(blockNumber), hashBytes, int16Bytes(textBytes.length), textBytes);
|
||||
}
|
||||
|
||||
return concatBytes(
|
||||
int8Byte(loginBytes.length),
|
||||
loginBytes,
|
||||
int32Bytes(blockNumber),
|
||||
hexToBytes(normalizeHex32(toBlockHashHex)),
|
||||
int16Bytes(textBytes.length),
|
||||
textBytes
|
||||
);
|
||||
function makeTextRatingBodyBytes({ toLogin, toForkNumber, toBlockNumber, toBlockHashHex, text }) {
|
||||
const loginBytes = targetLoginBytes(toLogin);
|
||||
const forkNumber = normalizeTargetForkNumber(toForkNumber, 'rating');
|
||||
const blockNumber = normalizeTargetBlockNumber(toBlockNumber, 'rating');
|
||||
const hashBytes = nonZeroHash32Bytes(toBlockHashHex, 'rating');
|
||||
const textBytes = utf8Bytes(String(text || '').trim());
|
||||
if (textBytes.length < 1 || textBytes.length > 65535) throw new Error('Rating text must be 1..65535 UTF-8 bytes');
|
||||
return concatBytes(int8Byte(loginBytes.length), loginBytes, int16Bytes(forkNumber), int32Bytes(blockNumber), hashBytes, int16Bytes(textBytes.length), textBytes);
|
||||
}
|
||||
|
||||
function makeStatusActionBodyBytes({ toLogin, toForkNumber, toBlockNumber, toBlockHashHex, text = '' }) {
|
||||
const loginBytes = targetLoginBytes(toLogin);
|
||||
const forkNumber = normalizeTargetForkNumber(toForkNumber, 'status action');
|
||||
const blockNumber = normalizeTargetBlockNumber(toBlockNumber, 'status action');
|
||||
const hashBytes = nonZeroHash32Bytes(toBlockHashHex, 'status action');
|
||||
const textBytes = utf8Bytes(String(text || '').trim());
|
||||
if (textBytes.length > 65535) throw new Error('Status action text must be 0..65535 UTF-8 bytes');
|
||||
return concatBytes(int8Byte(loginBytes.length), loginBytes, int16Bytes(forkNumber), int32Bytes(blockNumber), hashBytes, int16Bytes(textBytes.length), textBytes);
|
||||
}
|
||||
|
||||
function makeTextRepostBodyBytes({
|
||||
lineCode,
|
||||
prevLineNumber,
|
||||
prevLineHashHex,
|
||||
thisLineNumber,
|
||||
toLogin,
|
||||
toBlockNumber,
|
||||
toBlockHashHex,
|
||||
text,
|
||||
lineCode, prevLineNumber, prevLineHashHex, thisLineNumber,
|
||||
toLogin, toForkNumber, toBlockNumber, toBlockHashHex, text,
|
||||
}) {
|
||||
const message = String(text || '').trim();
|
||||
if (!message) throw new Error('Комментарий к репосту обязателен');
|
||||
|
||||
const cleanTargetLogin = String(toLogin || '').trim();
|
||||
if (!cleanTargetLogin) throw new Error('toLogin is required for repost');
|
||||
const loginBytes = utf8Bytes(cleanTargetLogin);
|
||||
if (loginBytes.length < 1 || loginBytes.length > 255) {
|
||||
throw new Error('toLogin must be 1..255 bytes');
|
||||
}
|
||||
|
||||
const blockNumber = Number(toBlockNumber);
|
||||
if (!Number.isFinite(blockNumber) || blockNumber < 0) {
|
||||
throw new Error('Invalid toBlockNumber for repost');
|
||||
}
|
||||
|
||||
const loginBytes = targetLoginBytes(toLogin);
|
||||
const forkNumber = normalizeTargetForkNumber(toForkNumber, 'repost');
|
||||
const blockNumber = normalizeTargetBlockNumber(toBlockNumber, 'repost');
|
||||
const hashBytes = nonZeroHash32Bytes(toBlockHashHex, 'repost');
|
||||
const textBytes = utf8Bytes(message);
|
||||
if (textBytes.length < 1 || textBytes.length > 65535) {
|
||||
throw new Error('Repost comment must be 1..65535 UTF-8 bytes');
|
||||
}
|
||||
|
||||
if (textBytes.length > 65535) throw new Error('Repost comment too long');
|
||||
return concatBytes(
|
||||
int32Bytes(lineCode),
|
||||
int32Bytes(prevLineNumber),
|
||||
hexToBytes(normalizeHex32(prevLineHashHex)),
|
||||
int32Bytes(thisLineNumber),
|
||||
int8Byte(loginBytes.length),
|
||||
loginBytes,
|
||||
int32Bytes(blockNumber),
|
||||
hexToBytes(normalizeHex32(toBlockHashHex)),
|
||||
int16Bytes(textBytes.length),
|
||||
textBytes,
|
||||
int32Bytes(lineCode), int32Bytes(prevLineNumber), hexToBytes(normalizeHex32(prevLineHashHex)), int32Bytes(thisLineNumber),
|
||||
int8Byte(loginBytes.length), loginBytes, int16Bytes(forkNumber), int32Bytes(blockNumber), hashBytes,
|
||||
int16Bytes(textBytes.length), textBytes,
|
||||
);
|
||||
}
|
||||
|
||||
function makeTextEditPostBodyBytes({
|
||||
lineCode,
|
||||
prevLineNumber,
|
||||
prevLineHashHex,
|
||||
thisLineNumber,
|
||||
toLogin,
|
||||
toBlockNumber,
|
||||
toBlockHashHex,
|
||||
text,
|
||||
lineCode, prevLineNumber, prevLineHashHex, thisLineNumber,
|
||||
toBlockNumber, toBlockHashHex, text,
|
||||
}) {
|
||||
const message = String(text || '').trim();
|
||||
const cleanTargetLogin = String(toLogin || '').trim();
|
||||
if (!cleanTargetLogin) throw new Error('toLogin is required for edit post');
|
||||
const loginBytes = utf8Bytes(cleanTargetLogin);
|
||||
if (loginBytes.length < 1 || loginBytes.length > 255) throw new Error('toLogin must be 1..255 bytes');
|
||||
const targetBlockNumber = Number(toBlockNumber);
|
||||
if (!Number.isFinite(targetBlockNumber) || targetBlockNumber < 0) {
|
||||
throw new Error('Invalid target block number for edit post');
|
||||
}
|
||||
const textBytes = utf8Bytes(message);
|
||||
if (textBytes.length > 65535) {
|
||||
throw new Error('Message text must be 0..65535 UTF-8 bytes');
|
||||
}
|
||||
|
||||
const targetBlockNumber = normalizeTargetBlockNumber(toBlockNumber, 'edit post');
|
||||
const hashBytes = nonZeroHash32Bytes(toBlockHashHex, 'edit post');
|
||||
const textBytes = utf8Bytes(String(text || '').trim());
|
||||
if (textBytes.length > 65535) throw new Error('Message text must be 0..65535 UTF-8 bytes');
|
||||
return concatBytes(
|
||||
int32Bytes(lineCode),
|
||||
int32Bytes(prevLineNumber),
|
||||
hexToBytes(normalizeHex32(prevLineHashHex)),
|
||||
int32Bytes(thisLineNumber),
|
||||
int8Byte(loginBytes.length),
|
||||
loginBytes,
|
||||
int32Bytes(targetBlockNumber),
|
||||
hexToBytes(normalizeHex32(toBlockHashHex)),
|
||||
int16Bytes(textBytes.length),
|
||||
textBytes,
|
||||
int32Bytes(lineCode), int32Bytes(prevLineNumber), hexToBytes(normalizeHex32(prevLineHashHex)), int32Bytes(thisLineNumber),
|
||||
int32Bytes(targetBlockNumber), hashBytes, int16Bytes(textBytes.length), textBytes,
|
||||
);
|
||||
}
|
||||
|
||||
function makeTextEditReplyBodyBytes({ toLogin, toBlockNumber, toBlockHashHex, text }) {
|
||||
const message = String(text || '').trim();
|
||||
const cleanTargetLogin = String(toLogin || '').trim();
|
||||
if (!cleanTargetLogin) throw new Error('toLogin is required for edit reply');
|
||||
const loginBytes = utf8Bytes(cleanTargetLogin);
|
||||
if (loginBytes.length < 1 || loginBytes.length > 255) throw new Error('toLogin must be 1..255 bytes');
|
||||
const targetBlockNumber = Number(toBlockNumber);
|
||||
if (!Number.isFinite(targetBlockNumber) || targetBlockNumber < 0) {
|
||||
throw new Error('Invalid target block number for edit reply');
|
||||
}
|
||||
const textBytes = utf8Bytes(message);
|
||||
if (textBytes.length > 65535) {
|
||||
throw new Error('Message text must be 0..65535 UTF-8 bytes');
|
||||
}
|
||||
|
||||
return concatBytes(
|
||||
int8Byte(loginBytes.length),
|
||||
loginBytes,
|
||||
int32Bytes(targetBlockNumber),
|
||||
hexToBytes(normalizeHex32(toBlockHashHex)),
|
||||
int16Bytes(textBytes.length),
|
||||
textBytes,
|
||||
);
|
||||
function makeTextEditReplyBodyBytes({ toBlockNumber, toBlockHashHex, text }) {
|
||||
const targetBlockNumber = normalizeTargetBlockNumber(toBlockNumber, 'edit reply');
|
||||
const hashBytes = nonZeroHash32Bytes(toBlockHashHex, 'edit reply');
|
||||
const textBytes = utf8Bytes(String(text || '').trim());
|
||||
if (textBytes.length > 65535) throw new Error('Message text must be 0..65535 UTF-8 bytes');
|
||||
return concatBytes(int32Bytes(targetBlockNumber), hashBytes, int16Bytes(textBytes.length), textBytes);
|
||||
}
|
||||
|
||||
function makeConnectionBodyBytes({
|
||||
lineCode = 0,
|
||||
prevLineNumber = -1,
|
||||
prevLineHashHex = ZERO64,
|
||||
thisLineNumber = -1,
|
||||
toLogin,
|
||||
toBlockNumber,
|
||||
toBlockHashHex,
|
||||
lineCode = 0, prevLineNumber = -1, prevLineHashHex = ZERO64, thisLineNumber = -1,
|
||||
toLogin, toForkNumber, toBlockNumber, toBlockHashHex,
|
||||
}) {
|
||||
const cleanTargetLogin = String(toLogin || '').trim();
|
||||
if (!cleanTargetLogin) throw new Error('toLogin is required for connection');
|
||||
|
||||
const blockNumber = Number(toBlockNumber);
|
||||
if (!Number.isFinite(blockNumber) || blockNumber < 0) {
|
||||
throw new Error('Invalid toBlockNumber for connection');
|
||||
}
|
||||
|
||||
const loginBytes = utf8Bytes(cleanTargetLogin);
|
||||
if (loginBytes.length < 1 || loginBytes.length > 255) {
|
||||
throw new Error('toLogin must be 1..255 bytes');
|
||||
}
|
||||
|
||||
const loginBytes = targetLoginBytes(toLogin);
|
||||
const forkNumber = normalizeTargetForkNumber(toForkNumber, 'connection');
|
||||
const blockNumber = normalizeTargetBlockNumber(toBlockNumber, 'connection');
|
||||
const hashBytes = nonZeroHash32Bytes(toBlockHashHex, 'connection');
|
||||
return concatBytes(
|
||||
int32Bytes(lineCode),
|
||||
int32Bytes(prevLineNumber),
|
||||
hexToBytes(normalizeHex32(prevLineHashHex)),
|
||||
int32Bytes(thisLineNumber),
|
||||
int8Byte(loginBytes.length),
|
||||
loginBytes,
|
||||
int32Bytes(blockNumber),
|
||||
hexToBytes(normalizeHex32(toBlockHashHex))
|
||||
int32Bytes(lineCode), int32Bytes(prevLineNumber), hexToBytes(normalizeHex32(prevLineHashHex)), int32Bytes(thisLineNumber),
|
||||
int8Byte(loginBytes.length), loginBytes, int16Bytes(forkNumber), int32Bytes(blockNumber), hashBytes,
|
||||
);
|
||||
}
|
||||
|
||||
function targetLoginBytes(value) {
|
||||
const bytes = utf8Bytes(String(value || '').trim());
|
||||
if (bytes.length < 1 || bytes.length > 255) throw new Error('toLogin must be 1..255 UTF-8 bytes');
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function normalizeTargetForkNumber(value, actionName = 'target') {
|
||||
const n = Number(value);
|
||||
if (!Number.isInteger(n) || n < 0 || n > 999) throw new Error(`Invalid toForkNumber for ${actionName}`);
|
||||
return n;
|
||||
}
|
||||
|
||||
function normalizeTargetBlockNumber(value, actionName = 'target') {
|
||||
const n = Number(value);
|
||||
if (!Number.isInteger(n) || n < 0) throw new Error(`Invalid toBlockNumber for ${actionName}`);
|
||||
return n;
|
||||
}
|
||||
|
||||
function nonZeroHash32Bytes(value, actionName = 'target') {
|
||||
const hex = normalizeHex32(value, ZERO64);
|
||||
if (hex === ZERO64) throw new Error(`Zero target hash is forbidden for ${actionName}`);
|
||||
return hexToBytes(hex);
|
||||
}
|
||||
|
||||
function normalizeChannelDescription(value) {
|
||||
const text = String(value == null ? '' : value).trim().replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
||||
const bytes = utf8Bytes(text);
|
||||
@@ -953,10 +834,18 @@ function makeTextLineBodyBytesAllowEmpty({ lineCode, prevLineNumber, prevLineHas
|
||||
|
||||
function loginFromBlockchainNameValue(value) {
|
||||
const name = String(value || '').trim();
|
||||
const match = name.match(/^(.*)-\d{3}$/u);
|
||||
const match = name.match(/^(.*)-(\d{3})$/u);
|
||||
return match?.[1] ? match[1] : '';
|
||||
}
|
||||
|
||||
function forkNumberFromBlockchainNameValue(value) {
|
||||
const name = String(value || '').trim();
|
||||
const match = name.match(/^(.*)-(\d{3})$/u);
|
||||
if (!match) return 0;
|
||||
const forkNumber = Number(match[2]);
|
||||
return Number.isInteger(forkNumber) && forkNumber >= 1 && forkNumber <= 999 ? forkNumber : 0;
|
||||
}
|
||||
|
||||
function normalizeMessageRefTarget(target, actionName = 'action') {
|
||||
const cleanBch = String(target?.blockchainName || target?.authorBlockchainName || '').trim();
|
||||
const cleanLogin = String(target?.login || target?.authorLogin || target?.ownerLogin || loginFromBlockchainNameValue(cleanBch)).trim();
|
||||
@@ -973,9 +862,15 @@ function normalizeMessageRefTarget(target, actionName = 'action') {
|
||||
throw new Error(`Invalid message target hash for ${actionName}`);
|
||||
}
|
||||
|
||||
const forkNumber = Number(target?.forkNumber ?? target?.toForkNumber ?? forkNumberFromBlockchainNameValue(cleanBch));
|
||||
if (!Number.isInteger(forkNumber) || forkNumber < 1 || forkNumber > 999) {
|
||||
throw new Error(`Missing/invalid message target fork for ${actionName}`);
|
||||
}
|
||||
|
||||
return {
|
||||
login: cleanLogin,
|
||||
blockchainName: cleanBch,
|
||||
forkNumber,
|
||||
blockNumber: cleanBlockNumber,
|
||||
blockHash: cleanBlockHash,
|
||||
};
|
||||
@@ -1839,11 +1734,16 @@ export class AuthService {
|
||||
};
|
||||
}
|
||||
|
||||
async addBlockSigned({ login, storagePwd, msgType, msgSubType, msgVersion = 1, bodyBytes, channelSlug = '' }) {
|
||||
async addBlockSigned({ login, storagePwd, msgType, msgSubType, msgVersion = 1, bodyBytes, channelSlug = '', _skipHeaderEnsure = false }) {
|
||||
const cleanLogin = (login || '').trim();
|
||||
if (!cleanLogin) throw new Error('Missing login for AddBlock');
|
||||
if (!storagePwd) throw new Error('Missing storagePwd for AddBlock signing');
|
||||
|
||||
const isHeader = Number(msgType) === MSG_TYPE_TECH && Number(msgSubType) === MSG_SUBTYPE_TECH_HEADER;
|
||||
if (!_skipHeaderEnsure && !isHeader) {
|
||||
await this.ensureBlockchainHeader(cleanLogin, storagePwd);
|
||||
}
|
||||
|
||||
const keyBundle = await loadEncryptedUserSecrets(cleanLogin, storagePwd);
|
||||
const blockchainPrivatePkcs8 = String(keyBundle?.blockchainKey || '').trim();
|
||||
if (!blockchainPrivatePkcs8) {
|
||||
@@ -1888,22 +1788,43 @@ export class AuthService {
|
||||
return payload;
|
||||
}
|
||||
|
||||
async ensureChainInitializedForLineOps(login, storagePwd) {
|
||||
const current = await this.getUser(login);
|
||||
async ensureBlockchainHeader(login, storagePwd) {
|
||||
const cleanLogin = String(login || '').trim();
|
||||
if (!cleanLogin) throw new Error('Missing login for HEADER bootstrap');
|
||||
|
||||
const current = await this.getUser(cleanLogin);
|
||||
const lastNum = Number(current?.serverLastGlobalNumber);
|
||||
if (Number.isFinite(lastNum) && lastNum >= 0) return current;
|
||||
if (!(Number.isFinite(lastNum) && lastNum === -1)) return current;
|
||||
|
||||
// Bootstrap an empty chain with a minimal USER_PARAM block so line-based
|
||||
// channel operations have a valid anchor at block #0.
|
||||
await this.addBlockUserParam({
|
||||
login,
|
||||
const keyBundle = await loadEncryptedUserSecrets(cleanLogin, storagePwd);
|
||||
const privatePkcs8 = String(keyBundle?.blockchainKey || '').trim();
|
||||
let publicKeyB64 = '';
|
||||
if (privatePkcs8) {
|
||||
publicKeyB64 = await publicKeyB64FromPkcs8Ed25519(privatePkcs8);
|
||||
} else {
|
||||
publicKeyB64 = String(current?.blockchainKey || '').trim();
|
||||
}
|
||||
const initialBlockchainKey32 = base64ToBytes(publicKeyB64);
|
||||
if (initialBlockchainKey32.length !== 32 || initialBlockchainKey32.every((value) => value === 0)) {
|
||||
throw new Error('Не удалось определить initial blockchain key для HEADER');
|
||||
}
|
||||
|
||||
await this.addBlockSigned({
|
||||
login: cleanLogin,
|
||||
storagePwd,
|
||||
param: 'shine',
|
||||
value: 'yes',
|
||||
msgType: MSG_TYPE_TECH,
|
||||
msgSubType: MSG_SUBTYPE_TECH_HEADER,
|
||||
msgVersion: 1,
|
||||
bodyBytes: makeHeaderBodyBytes({ login: cleanLogin, initialBlockchainKey32 }),
|
||||
_skipHeaderEnsure: true,
|
||||
});
|
||||
|
||||
return this.getUser(login);
|
||||
return this.getUser(cleanLogin);
|
||||
}
|
||||
|
||||
async ensureChainInitializedForLineOps(login, storagePwd) {
|
||||
return this.ensureBlockchainHeader(login, storagePwd);
|
||||
}
|
||||
|
||||
async addBlockLike({ login, message, storagePwd }) {
|
||||
@@ -1914,6 +1835,7 @@ export class AuthService {
|
||||
return this.runWriteLocked(key, async () => {
|
||||
const bodyBytes = makeReactionLikeBodyBytes({
|
||||
toLogin: target.login,
|
||||
toForkNumber: target.forkNumber,
|
||||
toBlockNumber: target.blockNumber,
|
||||
toBlockHashHex: target.blockHash,
|
||||
});
|
||||
@@ -1937,6 +1859,7 @@ export class AuthService {
|
||||
return this.runWriteLocked(key, async () => {
|
||||
const bodyBytes = makeReactionLikeBodyBytes({
|
||||
toLogin: target.login,
|
||||
toForkNumber: target.forkNumber,
|
||||
toBlockNumber: target.blockNumber,
|
||||
toBlockHashHex: target.blockHash,
|
||||
});
|
||||
@@ -1961,6 +1884,7 @@ export class AuthService {
|
||||
return this.runWriteLocked(key, async () => {
|
||||
const bodyBytes = makeTextReplyBodyBytes({
|
||||
toLogin: target.login,
|
||||
toForkNumber: target.forkNumber,
|
||||
toBlockNumber: target.blockNumber,
|
||||
toBlockHashHex: target.blockHash,
|
||||
text: cleanText,
|
||||
@@ -1986,6 +1910,7 @@ export class AuthService {
|
||||
return this.runWriteLocked(key, async () => {
|
||||
const bodyBytes = makeTextRatingBodyBytes({
|
||||
toLogin: target.login,
|
||||
toForkNumber: target.forkNumber,
|
||||
toBlockNumber: target.blockNumber,
|
||||
toBlockHashHex: target.blockHash,
|
||||
text: cleanText,
|
||||
@@ -2026,6 +1951,7 @@ export class AuthService {
|
||||
return this.runWriteLocked(key, async () => {
|
||||
const bodyBytes = makeStatusActionBodyBytes({
|
||||
toLogin: target.login,
|
||||
toForkNumber: target.forkNumber,
|
||||
toBlockNumber: target.blockNumber,
|
||||
toBlockHashHex: target.blockHash,
|
||||
text: cleanText,
|
||||
@@ -2099,6 +2025,7 @@ export class AuthService {
|
||||
prevLineHashHex,
|
||||
thisLineNumber,
|
||||
toLogin: target.login,
|
||||
toForkNumber: target.forkNumber,
|
||||
toBlockNumber: target.blockNumber,
|
||||
toBlockHashHex: target.blockHash,
|
||||
text: cleanText,
|
||||
@@ -2198,7 +2125,6 @@ export class AuthService {
|
||||
}
|
||||
|
||||
const bodyBytes = makeTextEditReplyBodyBytes({
|
||||
toLogin: target.login,
|
||||
toBlockNumber: target.blockNumber,
|
||||
toBlockHashHex: target.blockHash,
|
||||
text: cleanText,
|
||||
@@ -2273,6 +2199,7 @@ export class AuthService {
|
||||
prevLineHashHex: ZERO64,
|
||||
thisLineNumber: -1,
|
||||
toLogin: cleanTargetLogin,
|
||||
toForkNumber: forkNumberFromBlockchainNameValue(cleanTargetBch),
|
||||
toBlockNumber: cleanTargetBlockNumber,
|
||||
toBlockHashHex: targetHashHex,
|
||||
});
|
||||
@@ -3255,14 +3182,20 @@ export class AuthService {
|
||||
|
||||
const targetUser = await this.getUser(cleanToLogin);
|
||||
if (!targetUser?.exists) throw new Error('Пользователь цели не найден.');
|
||||
const targetBlockchainName = String(targetUser.blockchainName || '').trim();
|
||||
const targetForkNumber = forkNumberFromBlockchainNameValue(targetBlockchainName);
|
||||
if (!targetForkNumber) throw new Error('Не удалось определить fork пользователя цели.');
|
||||
const targetHeaderHash = await this.resolveHeaderHashForBlockchain(targetBlockchainName);
|
||||
if (targetHeaderHash === ZERO64) throw new Error('HEADER пользователя цели не найден.');
|
||||
const bodyBytes = makeConnectionBodyBytes({
|
||||
lineCode: 0,
|
||||
prevLineNumber: -1,
|
||||
prevLineHashHex: ZERO_HASH_HEX,
|
||||
thisLineNumber: -1,
|
||||
toLogin: cleanToLogin,
|
||||
toForkNumber: targetForkNumber,
|
||||
toBlockNumber: 0,
|
||||
toBlockHashHex: ZERO_HASH_HEX,
|
||||
toBlockHashHex: targetHeaderHash,
|
||||
});
|
||||
return this.addBlockSigned({
|
||||
login: cleanLogin,
|
||||
|
||||
Reference in New Issue
Block a user