diff --git a/Dev_Docs/Blockchain/01_Channel_Types_and_CreateChannel_v3.md b/Dev_Docs/Blockchain/01_Channel_Types_and_CreateChannel.md similarity index 89% rename from Dev_Docs/Blockchain/01_Channel_Types_and_CreateChannel_v3.md rename to Dev_Docs/Blockchain/01_Channel_Types_and_CreateChannel.md index d8017e2..eb523cc 100644 --- a/Dev_Docs/Blockchain/01_Channel_Types_and_CreateChannel_v3.md +++ b/Dev_Docs/Blockchain/01_Channel_Types_and_CreateChannel.md @@ -1,7 +1,7 @@ -# Типы каналов и CreateChannel v3 +# Типы каналов и CreateChannel -## 1. Формат `CreateChannelBody v3` -Формат `TECH_CREATE_CHANNEL` поддерживает `version=3` и включает: +## 1. Формат `CreateChannelBody` +Формат `TECH_CREATE_CHANNEL` поддерживает единственный текущий `version=1` и включает: 1. line-поля канала (`lineCode`, `prevLineNumber`, `prevLineHash32`, `thisLineNumber`); 2. `channelName`; diff --git a/Dev_Docs/Blockchain/CHANGELOG.md b/Dev_Docs/Blockchain/CHANGELOG.md index b44117f..7a28d05 100644 --- a/Dev_Docs/Blockchain/CHANGELOG.md +++ b/Dev_Docs/Blockchain/CHANGELOG.md @@ -2,7 +2,7 @@ ## 2026-05-13 00:02:32 +0300 - Базовый коммит-ориентир: `f63f40f1eb2f`. -- Добавлен `CreateChannelBody v3` с полями `channelType (2 байта)` и `channelTypeVersion (2 байта)`. +- Добавлен текущий формат `CreateChannelBody` с полями `channelType (2 байта)` и `channelTypeVersion (2 байта)`. - Зафиксированы типы каналов: `0=stories`, `1=public`, `100=personal`, `200=group`. - Серверная уникальность имени канала изменена на `owner + type + name(slug)`. - Root-канал `0` переименован в `stories` на уровне API-чтения. diff --git a/Dev_Docs/Blockchain/README.md b/Dev_Docs/Blockchain/README.md index bf49d24..e4c925c 100644 --- a/Dev_Docs/Blockchain/README.md +++ b/Dev_Docs/Blockchain/README.md @@ -4,8 +4,8 @@ Этот набор файлов — актуальная документация по текущему формату блокчейна SHiNE для каналов, их типов и командных сообщений. ## Оглавление -1. [01_Channel_Types_and_CreateChannel_v3.md](./01_Channel_Types_and_CreateChannel_v3.md) - Формат `CreateChannelBody v3`, типы каналов, уникальность имён и правила `stories`. +1. [01_Channel_Types_and_CreateChannel.md](./01_Channel_Types_and_CreateChannel.md) + Текущий формат `CreateChannelBody`, типы каналов, уникальность имён и правила `stories`. 2. [02_Channel_Commands.md](./02_Channel_Commands.md) Командные сообщения в каналах: `/.desc`, `/.add`, `/.remove`. 3. [CHANGELOG.md](./CHANGELOG.md) diff --git a/shine-UI/js/services/auth-service.js b/shine-UI/js/services/auth-service.js index 02ab181..beb4a2b 100644 --- a/shine-UI/js/services/auth-service.js +++ b/shine-UI/js/services/auth-service.js @@ -42,7 +42,7 @@ const MSG_SUBTYPE_REACTION_LIKE = 1; const MSG_SUBTYPE_REACTION_UNLIKE = 2; const MSG_SUBTYPE_CONNECTION_FOLLOW = 30; const MSG_SUBTYPE_CONNECTION_UNFOLLOW = 31; -const CREATE_CHANNEL_BODY_VERSION = 3; +const CREATE_CHANNEL_BODY_VERSION = 1; const CHANNEL_TYPE_STORIES = 0; const CHANNEL_TYPE_PUBLIC = 1; const CHANNEL_TYPE_PERSONAL = 100; @@ -409,7 +409,7 @@ function normalizeChannelDescription(value) { return text; } -function makeCreateChannelBodyV3Bytes({ +function makeCreateChannelBodyBytes({ lineCode, prevLineNumber, prevLineHashHex, @@ -1080,7 +1080,7 @@ export class AuthService { msgType: MSG_TYPE_TECH, msgSubType: MSG_SUBTYPE_TECH_CREATE_CHANNEL, msgVersion: CREATE_CHANNEL_BODY_VERSION, - bodyBytes: makeCreateChannelBodyV3Bytes({ + bodyBytes: makeCreateChannelBodyBytes({ lineCode: 0, prevLineNumber, prevLineHashHex, diff --git a/shine-server-blockchain/src/main/java/blockchain/BodyRecordParser.java b/shine-server-blockchain/src/main/java/blockchain/BodyRecordParser.java index 4e82cfc..c487b21 100644 --- a/shine-server-blockchain/src/main/java/blockchain/BodyRecordParser.java +++ b/shine-server-blockchain/src/main/java/blockchain/BodyRecordParser.java @@ -16,15 +16,13 @@ public final class BodyRecordParser { int v = version & 0xFFFF; int st = subType & 0xFFFF; - // TECH supports Header v1 and CreateChannel v1/v2. + // TECH supports Header v1 and CreateChannel current format (ver=1). if (t == (CreateChannelBody.TYPE & 0xFFFF)) { if (st == (HeaderBody.SUBTYPE_COMPAT & 0xFFFF) && v == (HeaderBody.VER & 0xFFFF)) { return new HeaderBody(subType, version, bodyBytes).check(); } if (st == (CreateChannelBody.SUBTYPE & 0xFFFF) - && (v == (CreateChannelBody.VER & 0xFFFF) - || v == (CreateChannelBody.VER2 & 0xFFFF) - || v == (CreateChannelBody.VER3 & 0xFFFF))) { + && (v == (CreateChannelBody.VER & 0xFFFF))) { return new CreateChannelBody(subType, version, bodyBytes).check(); } throw new IllegalArgumentException( diff --git a/shine-server-blockchain/src/main/java/blockchain/body/CreateChannelBody.java b/shine-server-blockchain/src/main/java/blockchain/body/CreateChannelBody.java index be8c2fc..3bae241 100644 --- a/shine-server-blockchain/src/main/java/blockchain/body/CreateChannelBody.java +++ b/shine-server-blockchain/src/main/java/blockchain/body/CreateChannelBody.java @@ -11,25 +11,7 @@ import java.util.Objects; /** * TECH body for create channel. * - * v1 body bytes: - * [4] lineCode - * [4] prevLineNumber - * [32] prevLineHash32 - * [4] thisLineNumber - * [1] channelNameLen - * [N] channelName UTF-8 - * - * v2 body bytes: - * [4] lineCode - * [4] prevLineNumber - * [32] prevLineHash32 - * [4] thisLineNumber - * [1] channelNameLen - * [N] channelName UTF-8 - * [2] channelDescriptionLen - * [M] channelDescription UTF-8 (0..200 bytes) - * - * v3 body bytes: + * body bytes: * [4] lineCode * [4] prevLineNumber * [32] prevLineHash32 @@ -45,13 +27,7 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine { public static final short TYPE = 0; public static final short VER = 1; - public static final short VER2 = 2; - public static final short VER3 = 3; - public static final int KEY = ((TYPE & 0xFFFF) << 16) | (VER & 0xFFFF); - public static final int KEY_V2 = ((TYPE & 0xFFFF) << 16) | (VER2 & 0xFFFF); - public static final int KEY_V3 = ((TYPE & 0xFFFF) << 16) | (VER3 & 0xFFFF); - public static final short SUBTYPE = MsgSubType.TECH_CREATE_CHANNEL; public static final short CHANNEL_TYPE_STORIES = 0; @@ -79,35 +55,30 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine { public CreateChannelBody(short subType, short version, byte[] bodyBytes) { Objects.requireNonNull(bodyBytes, "bodyBytes == null"); - this.subType = subType; this.version = version; int ver = this.version & 0xFFFF; - if (ver != (VER & 0xFFFF) && ver != (VER2 & 0xFFFF) && ver != (VER3 & 0xFFFF)) { - throw new IllegalArgumentException("CreateChannelBody version must be 1, 2 or 3, got=" + ver); + if (ver != (VER & 0xFFFF)) { + throw new IllegalArgumentException("CreateChannelBody version must be 1, got=" + ver); } if ((this.subType & 0xFFFF) != (SUBTYPE & 0xFFFF)) { throw new IllegalArgumentException("CreateChannelBody subType must be TECH_CREATE_CHANNEL(1), got=" + (this.subType & 0xFFFF)); } - - if (bodyBytes.length < 4 + (4 + 32 + 4) + 1 + 1) { + if (bodyBytes.length < 4 + (4 + 32 + 4) + 1 + 1 + 2 + 4) { throw new IllegalArgumentException("CreateChannelBody too short"); } ByteBuffer bb = ByteBuffer.wrap(bodyBytes).order(ByteOrder.BIG_ENDIAN); - this.lineCode = bb.getInt(); this.prevLineNumber = bb.getInt(); - this.prevLineHash32 = new byte[32]; bb.get(this.prevLineHash32); - this.thisLineNumber = bb.getInt(); int nameLen = Byte.toUnsignedInt(bb.get()); if (nameLen <= 0) throw new IllegalArgumentException("channelNameLen is 0"); - if (bb.remaining() < nameLen) { + if (bb.remaining() < nameLen + 2 + 4) { throw new IllegalArgumentException("CreateChannelBody tail mismatch: remaining=" + bb.remaining() + " nameLen=" + nameLen); } @@ -115,68 +86,27 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine { bb.get(nameBytes); this.channelName = new String(nameBytes, StandardCharsets.UTF_8); - if (ver == (VER2 & 0xFFFF) || ver == (VER3 & 0xFFFF)) { - if (bb.remaining() < 2) { - throw new IllegalArgumentException("CreateChannelBody v2/v3 missing channelDescriptionLen"); - } - - int descriptionLen = Short.toUnsignedInt(bb.getShort()); - if (descriptionLen > MAX_DESCRIPTION_UTF8_LEN) { - throw new IllegalArgumentException("channelDescription utf8 len must be <=200"); - } - if (bb.remaining() != descriptionLen) { - throw new IllegalArgumentException("CreateChannelBody v2 tail mismatch: remaining=" + bb.remaining() + " descriptionLen=" + descriptionLen); - } - - if (descriptionLen == 0) { - this.channelDescription = ""; - } else { - byte[] descriptionBytes = new byte[descriptionLen]; - bb.get(descriptionBytes); - this.channelDescription = normalizeDescription(new String(descriptionBytes, StandardCharsets.UTF_8)); - } - if (ver == (VER3 & 0xFFFF)) { - if (bb.remaining() < 4) { - throw new IllegalArgumentException("CreateChannelBody v3 missing channelTypeCode/channelTypeVersion"); - } - this.channelTypeCode = bb.getShort(); - this.channelTypeVersion = bb.getShort(); - } else { - this.channelTypeCode = CHANNEL_TYPE_PUBLIC; - this.channelTypeVersion = CHANNEL_TYPE_VERSION_DEFAULT; - } - - if (bb.remaining() != 0) { - throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining()); - } - return; + int descriptionLen = Short.toUnsignedInt(bb.getShort()); + if (descriptionLen > MAX_DESCRIPTION_UTF8_LEN) { + throw new IllegalArgumentException("channelDescription utf8 len must be <=200"); } - - this.channelDescription = ""; - this.channelTypeCode = CHANNEL_TYPE_PUBLIC; - this.channelTypeVersion = CHANNEL_TYPE_VERSION_DEFAULT; + if (bb.remaining() != descriptionLen + 4) { + throw new IllegalArgumentException("CreateChannelBody tail mismatch: remaining=" + bb.remaining() + " descriptionLen=" + descriptionLen); + } + if (descriptionLen == 0) { + this.channelDescription = ""; + } else { + byte[] descriptionBytes = new byte[descriptionLen]; + bb.get(descriptionBytes); + this.channelDescription = normalizeDescription(new String(descriptionBytes, StandardCharsets.UTF_8)); + } + this.channelTypeCode = bb.getShort(); + this.channelTypeVersion = bb.getShort(); if (bb.remaining() != 0) { throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining()); } } - public CreateChannelBody(int lineCode, - int prevLineNumber, - byte[] prevLineHash32, - int thisLineNumber, - String channelName) { - this(lineCode, prevLineNumber, prevLineHash32, thisLineNumber, channelName, "", VER); - } - - public CreateChannelBody(int lineCode, - int prevLineNumber, - byte[] prevLineHash32, - int thisLineNumber, - String channelName, - String channelDescription) { - this(lineCode, prevLineNumber, prevLineHash32, thisLineNumber, channelName, channelDescription, VER2); - } - public CreateChannelBody(int lineCode, int prevLineNumber, byte[] prevLineHash32, @@ -185,41 +115,15 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine { String channelDescription, short channelTypeCode, short channelTypeVersion) { - this(lineCode, prevLineNumber, prevLineHash32, thisLineNumber, channelName, channelDescription, - channelTypeCode, channelTypeVersion, VER3); - } - - private CreateChannelBody(int lineCode, - int prevLineNumber, - byte[] prevLineHash32, - int thisLineNumber, - String channelName, - String channelDescription, - short version) { - this(lineCode, prevLineNumber, prevLineHash32, thisLineNumber, channelName, channelDescription, - CHANNEL_TYPE_PUBLIC, CHANNEL_TYPE_VERSION_DEFAULT, version); - } - - private CreateChannelBody(int lineCode, - int prevLineNumber, - byte[] prevLineHash32, - int thisLineNumber, - String channelName, - String channelDescription, - short channelTypeCode, - short channelTypeVersion, - short version) { Objects.requireNonNull(channelName, "channelName == null"); if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0"); this.subType = SUBTYPE; - this.version = version; - + this.version = VER; this.lineCode = lineCode; this.prevLineNumber = prevLineNumber; this.prevLineHash32 = (prevLineHash32 == null ? ZERO32 : Arrays.copyOf(prevLineHash32, 32)); this.thisLineNumber = thisLineNumber; - this.channelName = channelName; this.channelDescription = channelDescription == null ? "" : channelDescription; this.channelTypeCode = channelTypeCode; @@ -229,20 +133,14 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine { @Override public CreateChannelBody check() { if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0"); - if ((subType & 0xFFFF) != (SUBTYPE & 0xFFFF)) { throw new IllegalArgumentException("CreateChannelBody subType must be TECH_CREATE_CHANNEL(1)"); } String normalizedName = normalizeDisplayName(channelName); - if (normalizedName.isEmpty()) { - throw new IllegalArgumentException("channelName is blank"); - } - + if (normalizedName.isEmpty()) throw new IllegalArgumentException("channelName is blank"); int cpLen = normalizedName.codePointCount(0, normalizedName.length()); - if (cpLen > MAX_NAME_LENGTH) { - throw new IllegalArgumentException("channelName length must be <=32"); - } + if (cpLen > MAX_NAME_LENGTH) throw new IllegalArgumentException("channelName length must be <=32"); String normalizedDescription = normalizeDescription(channelDescription); byte[] descUtf8 = normalizedDescription.getBytes(StandardCharsets.UTF_8); @@ -252,23 +150,12 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine { int typeCode = Short.toUnsignedInt(channelTypeCode); int typeVer = Short.toUnsignedInt(channelTypeVersion); - if (typeCode < 0 || typeCode > 0xFFFF) { - throw new IllegalArgumentException("channelTypeCode invalid"); - } - if (typeVer < 0 || typeVer > 0xFFFF) { - throw new IllegalArgumentException("channelTypeVersion invalid"); - } - - if (prevLineNumber < 0) { - throw new IllegalArgumentException("prevLineNumber must be >=0 for CreateChannelBody"); - } - if (prevLineHash32 == null || prevLineHash32.length != 32) { - throw new IllegalArgumentException("prevLineHash32 invalid"); - } - if (thisLineNumber <= 0) { - throw new IllegalArgumentException("thisLineNumber must be >=1 for CreateChannelBody"); - } + if (typeCode < 0 || typeCode > 0xFFFF) throw new IllegalArgumentException("channelTypeCode invalid"); + if (typeVer < 0 || typeVer > 0xFFFF) throw new IllegalArgumentException("channelTypeVersion invalid"); + if (prevLineNumber < 0) throw new IllegalArgumentException("prevLineNumber must be >=0 for CreateChannelBody"); + if (prevLineHash32 == null || prevLineHash32.length != 32) throw new IllegalArgumentException("prevLineHash32 invalid"); + if (thisLineNumber <= 0) throw new IllegalArgumentException("thisLineNumber must be >=1 for CreateChannelBody"); return this; } @@ -288,53 +175,33 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine { if (nameUtf8.length == 0 || nameUtf8.length > 255) { throw new IllegalArgumentException("channelName utf8 len must be 1..255"); } - - boolean isV2 = (version & 0xFFFF) == (VER2 & 0xFFFF); - boolean isV3 = (version & 0xFFFF) == (VER3 & 0xFFFF); byte[] descriptionUtf8 = normalizeDescription(channelDescription).getBytes(StandardCharsets.UTF_8); if (descriptionUtf8.length > MAX_DESCRIPTION_UTF8_LEN) { throw new IllegalArgumentException("channelDescription utf8 len must be <=200"); } - int cap = 4 + (4 + 32 + 4) + 1 + nameUtf8.length - + ((isV2 || isV3) ? 2 + descriptionUtf8.length : 0) - + (isV3 ? 4 : 0); + int cap = 4 + (4 + 32 + 4) + 1 + nameUtf8.length + 2 + descriptionUtf8.length + 4; ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN); - bb.putInt(lineCode); bb.putInt(prevLineNumber); bb.put(prevLineHash32 == null ? ZERO32 : Arrays.copyOf(prevLineHash32, 32)); bb.putInt(thisLineNumber); - bb.put((byte) nameUtf8.length); bb.put(nameUtf8); - - if (isV2 || isV3) { - bb.putShort((short) (descriptionUtf8.length & 0xFFFF)); - if (descriptionUtf8.length > 0) { - bb.put(descriptionUtf8); - } - } - - if (isV3) { - bb.putShort(channelTypeCode); - bb.putShort(channelTypeVersion); - } - + bb.putShort((short) (descriptionUtf8.length & 0xFFFF)); + if (descriptionUtf8.length > 0) bb.put(descriptionUtf8); + bb.putShort(channelTypeCode); + bb.putShort(channelTypeVersion); return bb.array(); } @Override public int lineCode() { return lineCode; } - @Override public int prevLineBlockGlobalNumber() { return prevLineNumber; } - @Override - public byte[] prevLineBlockHash32() { - return prevLineHash32 == null ? null : Arrays.copyOf(prevLineHash32, 32); - } - + public byte[] prevLineBlockHash32() { return prevLineHash32 == null ? null : Arrays.copyOf(prevLineHash32, 32); } @Override public int lineSeq() { return thisLineNumber; } } + diff --git a/src/test/java/test/it/cases/IT_03_AddBlock_NoAuth.java b/src/test/java/test/it/cases/IT_03_AddBlock_NoAuth.java index b7d4945..d40f4b6 100644 --- a/src/test/java/test/it/cases/IT_03_AddBlock_NoAuth.java +++ b/src/test/java/test/it/cases/IT_03_AddBlock_NoAuth.java @@ -105,7 +105,10 @@ public class IT_03_AddBlock_NoAuth { sender1.send(new CreateChannelBody( 0, // lineCode TECH ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber, - "News" + "News", + "", + CreateChannelBody.CHANNEL_TYPE_PUBLIC, + CreateChannelBody.CHANNEL_TYPE_VERSION_DEFAULT ), t); newsRootBlock = st1.lastBlockNumber(); // root канала = blockNumber этого CREATE_CHANNEL @@ -286,4 +289,4 @@ public class IT_03_AddBlock_NoAuth { toBlockHash32 ), timeout); } -} \ No newline at end of file +}