15 01 25
Потч работает добавление линий - ситуация сложная тест падает
This commit is contained in:
parent
376d42cd79
commit
69cd33479b
@ -15,7 +15,7 @@ import java.util.Objects;
|
|||||||
* RAW (BigEndian) = preimage:
|
* RAW (BigEndian) = preimage:
|
||||||
* [32] prevHash32 (SHA-256) hash предыдущего блока (цепочка)
|
* [32] prevHash32 (SHA-256) hash предыдущего блока (цепочка)
|
||||||
* [4] blockSize (int) = размер preimage (в байтах), БЕЗ signature64
|
* [4] blockSize (int) = размер preimage (в байтах), БЕЗ signature64
|
||||||
* [4] blockNumber (int) глобальный номер блока
|
* [4] blockNumber (int) глобальный номер блока (>=0)
|
||||||
* [8] timestamp (long) unix seconds
|
* [8] timestamp (long) unix seconds
|
||||||
*
|
*
|
||||||
* [2] type (short) тип сообщения
|
* [2] type (short) тип сообщения
|
||||||
@ -62,7 +62,7 @@ public final class BchBlockEntry {
|
|||||||
// --- HEADER (RAW) ---
|
// --- HEADER (RAW) ---
|
||||||
public final byte[] prevHash32; // 32
|
public final byte[] prevHash32; // 32
|
||||||
public final int blockSize; // preimage size
|
public final int blockSize; // preimage size
|
||||||
public final int blockNumber;
|
public final int blockNumber; // >=0
|
||||||
public final long timestamp;
|
public final long timestamp;
|
||||||
public final short type;
|
public final short type;
|
||||||
public final short subType;
|
public final short subType;
|
||||||
@ -113,6 +113,10 @@ public final class BchBlockEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.blockNumber = bb.getInt();
|
this.blockNumber = bb.getInt();
|
||||||
|
if (this.blockNumber < 0) {
|
||||||
|
throw new IllegalArgumentException("blockNumber < 0: " + this.blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
this.timestamp = bb.getLong();
|
this.timestamp = bb.getLong();
|
||||||
|
|
||||||
// запрет “в будущее” больше чем на 1 минуту
|
// запрет “в будущее” больше чем на 1 минуту
|
||||||
@ -171,6 +175,10 @@ public final class BchBlockEntry {
|
|||||||
if (prevHash32.length != 32) throw new IllegalArgumentException("prevHash32 != 32");
|
if (prevHash32.length != 32) throw new IllegalArgumentException("prevHash32 != 32");
|
||||||
if (signature64.length != SIGNATURE_LEN) throw new IllegalArgumentException("signature64 != 64");
|
if (signature64.length != SIGNATURE_LEN) throw new IllegalArgumentException("signature64 != 64");
|
||||||
|
|
||||||
|
if (blockNumber < 0) {
|
||||||
|
throw new IllegalArgumentException("blockNumber < 0: " + blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
// запрет “в будущее” больше чем на 1 минуту
|
// запрет “в будущее” больше чем на 1 минуту
|
||||||
long now = Instant.now().getEpochSecond();
|
long now = Instant.now().getEpochSecond();
|
||||||
if (timestamp > now + MAX_FUTURE_SECONDS) {
|
if (timestamp > now + MAX_FUTURE_SECONDS) {
|
||||||
|
|||||||
@ -3,13 +3,10 @@ package blockchain.body;
|
|||||||
/**
|
/**
|
||||||
* BodyHasLine — для типов, которые имеют линейные поля в body.
|
* BodyHasLine — для типов, которые имеют линейные поля в body.
|
||||||
*
|
*
|
||||||
* В проекте hasLine встречается, например, у:
|
* Новый префикс для line-сообщений (BigEndian) в НАЧАЛЕ bodyBytes:
|
||||||
* - TECH: CREATE_CHANNEL (type=0, subType=1) — идёт по тех-линии
|
* [4] lineCode код линии:
|
||||||
* - TEXT: POST / EDIT_POST (type=1, subType=10/11) — линия канала
|
* - 0 для нулевой линии
|
||||||
* - CONNECTION (type=3)
|
* - для каналов: blockNumber "заглавия линии" (CREATE_CHANNEL или HEADER/0)
|
||||||
* - USER_PARAM (type=4)
|
|
||||||
*
|
|
||||||
* Формат линейных полей (BigEndian) в НАЧАЛЕ bodyBytes:
|
|
||||||
* [4] prevLineNumber
|
* [4] prevLineNumber
|
||||||
* [32] prevLineHash32
|
* [32] prevLineHash32
|
||||||
* [4] thisLineNumber
|
* [4] thisLineNumber
|
||||||
@ -20,6 +17,8 @@ package blockchain.body;
|
|||||||
*/
|
*/
|
||||||
public interface BodyHasLine {
|
public interface BodyHasLine {
|
||||||
|
|
||||||
|
int lineCode();
|
||||||
|
|
||||||
int prevLineNumber();
|
int prevLineNumber();
|
||||||
|
|
||||||
byte[] prevLineHash32();
|
byte[] prevLineHash32();
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import java.util.Objects;
|
|||||||
* FOLLOW=30, UNFOLLOW=31
|
* FOLLOW=30, UNFOLLOW=31
|
||||||
*
|
*
|
||||||
* bodyBytes (BigEndian), новый формат (toLogin НЕ ХРАНИМ):
|
* bodyBytes (BigEndian), новый формат (toLogin НЕ ХРАНИМ):
|
||||||
|
* [4] lineCode
|
||||||
* [4] prevLineNumber
|
* [4] prevLineNumber
|
||||||
* [32] prevLineHash32
|
* [32] prevLineHash32
|
||||||
* [4] thisLineNumber
|
* [4] thisLineNumber
|
||||||
@ -41,6 +42,7 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
|
|||||||
public final short version; // из header
|
public final short version; // из header
|
||||||
|
|
||||||
// line
|
// line
|
||||||
|
public final int lineCode;
|
||||||
public final int prevLineNumber;
|
public final int prevLineNumber;
|
||||||
public final byte[] prevLineHash32;
|
public final byte[] prevLineHash32;
|
||||||
public final int thisLineNumber;
|
public final int thisLineNumber;
|
||||||
@ -64,13 +66,15 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
|
|||||||
}
|
}
|
||||||
|
|
||||||
// минимум:
|
// минимум:
|
||||||
// line(4+32+4) + toBchLen[1]+toBch[1] + global[4] + hash[32]
|
// lineCode(4) + line(4+32+4) + toBchLen[1]+toBch[1] + global[4] + hash[32]
|
||||||
if (bodyBytes.length < (4 + 32 + 4) + 1 + 1 + 4 + 32) {
|
if (bodyBytes.length < 4 + (4 + 32 + 4) + 1 + 1 + 4 + 32) {
|
||||||
throw new IllegalArgumentException("ConnectionBody too short");
|
throw new IllegalArgumentException("ConnectionBody too short");
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteBuffer bb = ByteBuffer.wrap(bodyBytes).order(ByteOrder.BIG_ENDIAN);
|
ByteBuffer bb = ByteBuffer.wrap(bodyBytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
this.lineCode = bb.getInt();
|
||||||
|
|
||||||
this.prevLineNumber = bb.getInt();
|
this.prevLineNumber = bb.getInt();
|
||||||
|
|
||||||
this.prevLineHash32 = new byte[32];
|
this.prevLineHash32 = new byte[32];
|
||||||
@ -94,7 +98,8 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
|
|||||||
if (bb.remaining() != 0) throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining());
|
if (bb.remaining() != 0) throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConnectionBody(int prevLineNumber,
|
public ConnectionBody(int lineCode,
|
||||||
|
int prevLineNumber,
|
||||||
byte[] prevLineHash32,
|
byte[] prevLineHash32,
|
||||||
int thisLineNumber,
|
int thisLineNumber,
|
||||||
short subType,
|
short subType,
|
||||||
@ -105,6 +110,7 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
|
|||||||
Objects.requireNonNull(toBlockchainName, "toBlockchainName == null");
|
Objects.requireNonNull(toBlockchainName, "toBlockchainName == null");
|
||||||
Objects.requireNonNull(toBlockHash32, "toBlockHash32 == null");
|
Objects.requireNonNull(toBlockHash32, "toBlockHash32 == null");
|
||||||
|
|
||||||
|
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0");
|
||||||
if (!isValidSubType(subType)) throw new IllegalArgumentException("Bad connection subType: " + (subType & 0xFFFF));
|
if (!isValidSubType(subType)) throw new IllegalArgumentException("Bad connection subType: " + (subType & 0xFFFF));
|
||||||
|
|
||||||
if (toBlockchainName.isBlank()) throw new IllegalArgumentException("toBlockchainName is blank");
|
if (toBlockchainName.isBlank()) throw new IllegalArgumentException("toBlockchainName is blank");
|
||||||
@ -116,6 +122,8 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
|
|||||||
if (toBlockGlobalNumber < 0) throw new IllegalArgumentException("toBlockGlobalNumber < 0");
|
if (toBlockGlobalNumber < 0) throw new IllegalArgumentException("toBlockGlobalNumber < 0");
|
||||||
if (toBlockHash32.length != 32) throw new IllegalArgumentException("toBlockHash32 != 32");
|
if (toBlockHash32.length != 32) throw new IllegalArgumentException("toBlockHash32 != 32");
|
||||||
|
|
||||||
|
this.lineCode = lineCode;
|
||||||
|
|
||||||
this.prevLineNumber = prevLineNumber;
|
this.prevLineNumber = prevLineNumber;
|
||||||
this.prevLineHash32 = (prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
|
this.prevLineHash32 = (prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
|
||||||
this.thisLineNumber = thisLineNumber;
|
this.thisLineNumber = thisLineNumber;
|
||||||
@ -140,9 +148,10 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ConnectionBody check() {
|
public ConnectionBody check() {
|
||||||
|
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0");
|
||||||
if (!isValidSubType(subType)) throw new IllegalArgumentException("Bad connection subType: " + (subType & 0xFFFF));
|
if (!isValidSubType(subType)) throw new IllegalArgumentException("Bad connection subType: " + (subType & 0xFFFF));
|
||||||
|
|
||||||
// line rule
|
// line rule (как было)
|
||||||
if (prevLineNumber == -1) {
|
if (prevLineNumber == -1) {
|
||||||
if (!isAllZero32(prevLineHash32)) throw new IllegalArgumentException("prevLineHash32 must be zero when prevLineNumber=-1");
|
if (!isAllZero32(prevLineHash32)) throw new IllegalArgumentException("prevLineHash32 must be zero when prevLineNumber=-1");
|
||||||
if (thisLineNumber != -1) throw new IllegalArgumentException("thisLineNumber must be -1 when prevLineNumber=-1");
|
if (thisLineNumber != -1) throw new IllegalArgumentException("thisLineNumber must be -1 when prevLineNumber=-1");
|
||||||
@ -172,12 +181,14 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
|
|||||||
if (toBlockHash32 == null || toBlockHash32.length != 32)
|
if (toBlockHash32 == null || toBlockHash32.length != 32)
|
||||||
throw new IllegalArgumentException("toBlockHash32 != 32");
|
throw new IllegalArgumentException("toBlockHash32 != 32");
|
||||||
|
|
||||||
int cap = (4 + 32 + 4)
|
int cap = 4 + (4 + 32 + 4)
|
||||||
+ 1 + bchBytes.length
|
+ 1 + bchBytes.length
|
||||||
+ 4 + 32;
|
+ 4 + 32;
|
||||||
|
|
||||||
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
|
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
bb.putInt(lineCode);
|
||||||
|
|
||||||
bb.putInt(prevLineNumber);
|
bb.putInt(prevLineNumber);
|
||||||
bb.put(prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
|
bb.put(prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
|
||||||
bb.putInt(thisLineNumber);
|
bb.putInt(thisLineNumber);
|
||||||
@ -198,12 +209,12 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ====================== BodyHasLine ====================== */
|
/* ====================== BodyHasLine ====================== */
|
||||||
|
@Override public int lineCode() { return lineCode; }
|
||||||
@Override public int prevLineNumber() { return prevLineNumber; }
|
@Override public int prevLineNumber() { return prevLineNumber; }
|
||||||
@Override public byte[] prevLineHash32() { return prevLineHash32 == null ? null : Arrays.copyOf(prevLineHash32, 32); }
|
@Override public byte[] prevLineHash32() { return prevLineHash32 == null ? null : Arrays.copyOf(prevLineHash32, 32); }
|
||||||
@Override public int thisLineNumber() { return thisLineNumber; }
|
@Override public int thisLineNumber() { return thisLineNumber; }
|
||||||
|
|
||||||
/* ====================== BodyHasTarget ===================== */
|
/* ====================== BodyHasTarget ===================== */
|
||||||
// toLogin() теперь default в интерфейсе и вычисляется из toBchName()
|
|
||||||
@Override public String toBchName() { return toBlockchainName; }
|
@Override public String toBchName() { return toBlockchainName; }
|
||||||
@Override public Integer toBlockGlobalNumber() { return toBlockGlobalNumber; }
|
@Override public Integer toBlockGlobalNumber() { return toBlockGlobalNumber; }
|
||||||
@Override public byte[] toBlockHashBytes() { return toBlockHash32; }
|
@Override public byte[] toBlockHashBytes() { return toBlockHash32; }
|
||||||
|
|||||||
@ -18,7 +18,8 @@ import java.util.Objects;
|
|||||||
* - prevLineNumber/hash указывают на предыдущее TECH-сообщение (HEADER или прошлый CREATE_CHANNEL)
|
* - prevLineNumber/hash указывают на предыдущее TECH-сообщение (HEADER или прошлый CREATE_CHANNEL)
|
||||||
* - thisLineNumber: 1,2,3... (тех-нумерация)
|
* - thisLineNumber: 1,2,3... (тех-нумерация)
|
||||||
*
|
*
|
||||||
* bodyBytes (BigEndian):
|
* bodyBytes (BigEndian), новый формат line-prefix:
|
||||||
|
* [4] lineCode (для TECH линии обычно 0)
|
||||||
* [4] prevLineNumber
|
* [4] prevLineNumber
|
||||||
* [32] prevLineHash32
|
* [32] prevLineHash32
|
||||||
* [4] thisLineNumber
|
* [4] thisLineNumber
|
||||||
@ -43,6 +44,7 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
|
|||||||
public final short version; // из header
|
public final short version; // из header
|
||||||
|
|
||||||
// line
|
// line
|
||||||
|
public final int lineCode;
|
||||||
public final int prevLineNumber;
|
public final int prevLineNumber;
|
||||||
public final byte[] prevLineHash32; // 32
|
public final byte[] prevLineHash32; // 32
|
||||||
public final int thisLineNumber;
|
public final int thisLineNumber;
|
||||||
@ -63,12 +65,15 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
|
|||||||
throw new IllegalArgumentException("CreateChannelBody subType must be TECH_CREATE_CHANNEL(1), got=" + (this.subType & 0xFFFF));
|
throw new IllegalArgumentException("CreateChannelBody subType must be TECH_CREATE_CHANNEL(1), got=" + (this.subType & 0xFFFF));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bodyBytes.length < (4 + 32 + 4) + 1 + 1) {
|
// минимум: lineCode(4) + line(4+32+4) + nameLen(1) + name(1)
|
||||||
|
if (bodyBytes.length < 4 + (4 + 32 + 4) + 1 + 1) {
|
||||||
throw new IllegalArgumentException("CreateChannelBody too short");
|
throw new IllegalArgumentException("CreateChannelBody too short");
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteBuffer bb = ByteBuffer.wrap(bodyBytes).order(ByteOrder.BIG_ENDIAN);
|
ByteBuffer bb = ByteBuffer.wrap(bodyBytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
this.lineCode = bb.getInt();
|
||||||
|
|
||||||
this.prevLineNumber = bb.getInt();
|
this.prevLineNumber = bb.getInt();
|
||||||
|
|
||||||
this.prevLineHash32 = new byte[32];
|
this.prevLineHash32 = new byte[32];
|
||||||
@ -90,12 +95,18 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
|
|||||||
if (bb.remaining() != 0) throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining());
|
if (bb.remaining() != 0) throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining());
|
||||||
}
|
}
|
||||||
|
|
||||||
public CreateChannelBody(int prevLineNumber, byte[] prevLineHash32, int thisLineNumber, String channelName) {
|
public CreateChannelBody(int lineCode,
|
||||||
|
int prevLineNumber,
|
||||||
|
byte[] prevLineHash32,
|
||||||
|
int thisLineNumber,
|
||||||
|
String channelName) {
|
||||||
Objects.requireNonNull(channelName, "channelName == null");
|
Objects.requireNonNull(channelName, "channelName == null");
|
||||||
|
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0");
|
||||||
|
|
||||||
this.subType = SUBTYPE;
|
this.subType = SUBTYPE;
|
||||||
this.version = VER;
|
this.version = VER;
|
||||||
|
|
||||||
|
this.lineCode = lineCode;
|
||||||
this.prevLineNumber = prevLineNumber;
|
this.prevLineNumber = prevLineNumber;
|
||||||
this.prevLineHash32 = (prevLineHash32 == null ? ZERO32 : Arrays.copyOf(prevLineHash32, 32));
|
this.prevLineHash32 = (prevLineHash32 == null ? ZERO32 : Arrays.copyOf(prevLineHash32, 32));
|
||||||
this.thisLineNumber = thisLineNumber;
|
this.thisLineNumber = thisLineNumber;
|
||||||
@ -105,6 +116,8 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CreateChannelBody check() {
|
public CreateChannelBody check() {
|
||||||
|
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0");
|
||||||
|
|
||||||
if ((subType & 0xFFFF) != (SUBTYPE & 0xFFFF))
|
if ((subType & 0xFFFF) != (SUBTYPE & 0xFFFF))
|
||||||
throw new IllegalArgumentException("CreateChannelBody subType must be TECH_CREATE_CHANNEL(1)");
|
throw new IllegalArgumentException("CreateChannelBody subType must be TECH_CREATE_CHANNEL(1)");
|
||||||
|
|
||||||
@ -134,9 +147,11 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
|
|||||||
if (nameUtf8.length == 0 || nameUtf8.length > 255)
|
if (nameUtf8.length == 0 || nameUtf8.length > 255)
|
||||||
throw new IllegalArgumentException("channelName utf8 len must be 1..255");
|
throw new IllegalArgumentException("channelName utf8 len must be 1..255");
|
||||||
|
|
||||||
int cap = (4 + 32 + 4) + 1 + nameUtf8.length;
|
int cap = 4 + (4 + 32 + 4) + 1 + nameUtf8.length;
|
||||||
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
|
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
bb.putInt(lineCode);
|
||||||
|
|
||||||
bb.putInt(prevLineNumber);
|
bb.putInt(prevLineNumber);
|
||||||
bb.put(prevLineHash32 == null ? ZERO32 : Arrays.copyOf(prevLineHash32, 32));
|
bb.put(prevLineHash32 == null ? ZERO32 : Arrays.copyOf(prevLineHash32, 32));
|
||||||
bb.putInt(thisLineNumber);
|
bb.putInt(thisLineNumber);
|
||||||
@ -148,6 +163,7 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ====================== BodyHasLine ====================== */
|
/* ====================== BodyHasLine ====================== */
|
||||||
|
@Override public int lineCode() { return lineCode; }
|
||||||
@Override public int prevLineNumber() { return prevLineNumber; }
|
@Override public int prevLineNumber() { return prevLineNumber; }
|
||||||
@Override public byte[] prevLineHash32() { return prevLineHash32 == null ? null : Arrays.copyOf(prevLineHash32, 32); }
|
@Override public byte[] prevLineHash32() { return prevLineHash32 == null ? null : Arrays.copyOf(prevLineHash32, 32); }
|
||||||
@Override public int thisLineNumber() { return thisLineNumber; }
|
@Override public int thisLineNumber() { return thisLineNumber; }
|
||||||
|
|||||||
@ -22,32 +22,29 @@ import java.util.Objects;
|
|||||||
* =========================================================================
|
* =========================================================================
|
||||||
* КОНЦЕПЦИЯ ЛИНИЙ ДЛЯ ТЕКСТОВЫХ СООБЩЕНИЙ:
|
* КОНЦЕПЦИЯ ЛИНИЙ ДЛЯ ТЕКСТОВЫХ СООБЩЕНИЙ:
|
||||||
*
|
*
|
||||||
* POST и EDIT_POST принадлежат ЛИНИИ КАНАЛА и имеют hasLine:
|
* POST и EDIT_POST принадлежат ЛИНИИ КАНАЛА и имеют hasLine.
|
||||||
* [4] prevLineNumber
|
* В новом формате добавлен lineCode:
|
||||||
* [32] prevLineHash32
|
* lineCode = 0 для канала "0"
|
||||||
* [4] thisLineNumber
|
* lineCode = blockNumber "заглавия линии/канала" (например CREATE_CHANNEL)
|
||||||
*
|
*
|
||||||
* Канал в POST/EDIT_POST НЕ хранится (channelName не лежит в bodyBytes).
|
* REPLY и EDIT_REPLY НЕ имеют линии (нет hasLine в байтах).
|
||||||
* Канал определяется логически через lineRootBlockNumber:
|
|
||||||
* - канал "0": lineRootBlockNumber = blockNumber заголовка (HEADER)
|
|
||||||
* - канал "X": lineRootBlockNumber = blockNumber тех-сообщения CREATE_CHANNEL("X")
|
|
||||||
*
|
|
||||||
* REPLY и EDIT_REPLY НЕ имеют линии (нет hasLine).
|
|
||||||
*
|
*
|
||||||
* =========================================================================
|
* =========================================================================
|
||||||
* ФОРМАТЫ bodyBytes (BigEndian):
|
* ФОРМАТЫ bodyBytes (BigEndian):
|
||||||
*
|
*
|
||||||
* 1) POST (subType=10):
|
* 1) POST (subType=10):
|
||||||
|
* [4] lineCode
|
||||||
* [4] prevLineNumber
|
* [4] prevLineNumber
|
||||||
* [32] prevLineHash32
|
* [32] prevLineHash32
|
||||||
* [4] thisLineNumber // 0,1,2...
|
* [4] thisLineNumber
|
||||||
* [2] textLenBytes (uint16)
|
* [2] textLenBytes (uint16)
|
||||||
* [N] text UTF-8
|
* [N] text UTF-8
|
||||||
*
|
*
|
||||||
* 2) EDIT_POST (subType=11):
|
* 2) EDIT_POST (subType=11):
|
||||||
|
* [4] lineCode
|
||||||
* [4] prevLineNumber
|
* [4] prevLineNumber
|
||||||
* [32] prevLineHash32
|
* [32] prevLineHash32
|
||||||
* [4] thisLineNumber // равен thisLineNumber предыдущего сообщения линии
|
* [4] thisLineNumber
|
||||||
*
|
*
|
||||||
* hasTarget (на ОРИГИНАЛЬНЫЙ POST, toBchName НЕ хранить):
|
* hasTarget (на ОРИГИНАЛЬНЫЙ POST, toBchName НЕ хранить):
|
||||||
* [4] toBlockGlobalNumber
|
* [4] toBlockGlobalNumber
|
||||||
@ -57,7 +54,7 @@ import java.util.Objects;
|
|||||||
* [N] text UTF-8
|
* [N] text UTF-8
|
||||||
*
|
*
|
||||||
* 3) REPLY (subType=20) — НЕ в линии:
|
* 3) REPLY (subType=20) — НЕ в линии:
|
||||||
* hasTarget (может быть на чужой блокчейн; существование НЕ проверяем):
|
* hasTarget:
|
||||||
* [1] toBlockchainNameLen (uint8)
|
* [1] toBlockchainNameLen (uint8)
|
||||||
* [N] toBlockchainName UTF-8
|
* [N] toBlockchainName UTF-8
|
||||||
* [4] toBlockGlobalNumber
|
* [4] toBlockGlobalNumber
|
||||||
@ -73,15 +70,6 @@ import java.util.Objects;
|
|||||||
*
|
*
|
||||||
* [2] textLenBytes (uint16)
|
* [2] textLenBytes (uint16)
|
||||||
* [N] text UTF-8
|
* [N] text UTF-8
|
||||||
*
|
|
||||||
* =========================================================================
|
|
||||||
* ВАЖНО:
|
|
||||||
* - Body.check() НЕ имеет доступа к БД, поэтому:
|
|
||||||
* - не проверяет существование prevLineNumber/hash
|
|
||||||
* - не проверяет согласование thisLineNumber относительно prev
|
|
||||||
* - не проверяет существование target для REPLY
|
|
||||||
*
|
|
||||||
* Эти проверки выполняются на сервере/в БД при вставке.
|
|
||||||
*/
|
*/
|
||||||
public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
||||||
|
|
||||||
@ -95,6 +83,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
|
|
||||||
// ===== line fields (только для POST/EDIT_POST) =====
|
// ===== line fields (только для POST/EDIT_POST) =====
|
||||||
// Для REPLY/EDIT_REPLY эти поля НЕ сериализуются; значения держим как "пустые".
|
// Для REPLY/EDIT_REPLY эти поля НЕ сериализуются; значения держим как "пустые".
|
||||||
|
public final int lineCode; // только для line-message; иначе -1
|
||||||
public final int prevLineNumber;
|
public final int prevLineNumber;
|
||||||
public final byte[] prevLineHash32; // 32 or null
|
public final byte[] prevLineHash32; // 32 or null
|
||||||
public final int thisLineNumber;
|
public final int thisLineNumber;
|
||||||
@ -107,7 +96,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
// EDIT_POST / EDIT_REPLY: только globalNumber + hash32 (без toBlockchainName)
|
// EDIT_POST / EDIT_REPLY: только globalNumber + hash32 (без toBlockchainName)
|
||||||
public final String toBlockchainName; // nullable
|
public final String toBlockchainName; // nullable
|
||||||
public final Integer toBlockGlobalNumber; // nullable
|
public final Integer toBlockGlobalNumber; // nullable
|
||||||
public final byte[] toBlockHash32; // nullable(но если target есть -> 32)
|
public final byte[] toBlockHash32; // nullable (но если target есть -> 32)
|
||||||
|
|
||||||
/* ===================================================================== */
|
/* ===================================================================== */
|
||||||
/* ====================== Конструктор из байт ========================== */
|
/* ====================== Конструктор из байт ========================== */
|
||||||
@ -131,9 +120,10 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
int st = this.subType & 0xFFFF;
|
int st = this.subType & 0xFFFF;
|
||||||
|
|
||||||
if (st == (MsgSubType.TEXT_POST & 0xFFFF)) {
|
if (st == (MsgSubType.TEXT_POST & 0xFFFF)) {
|
||||||
// POST: hasLine + text
|
// POST: hasLine(lineCode+line) + text
|
||||||
ensureMin(bb, (4 + 32 + 4) + 2, "POST too short");
|
ensureMin(bb, (4 + 4 + 32 + 4) + 2, "POST too short");
|
||||||
|
|
||||||
|
this.lineCode = bb.getInt();
|
||||||
this.prevLineNumber = bb.getInt();
|
this.prevLineNumber = bb.getInt();
|
||||||
this.prevLineHash32 = new byte[32];
|
this.prevLineHash32 = new byte[32];
|
||||||
bb.get(this.prevLineHash32);
|
bb.get(this.prevLineHash32);
|
||||||
@ -148,9 +138,10 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
ensureNoTail(bb, "POST");
|
ensureNoTail(bb, "POST");
|
||||||
|
|
||||||
} else if (st == (MsgSubType.TEXT_EDIT_POST & 0xFFFF)) {
|
} else if (st == (MsgSubType.TEXT_EDIT_POST & 0xFFFF)) {
|
||||||
// EDIT_POST: hasLine + target(no bch) + text
|
// EDIT_POST: hasLine(lineCode+line) + target(no bch) + text
|
||||||
ensureMin(bb, (4 + 32 + 4) + (4 + 32) + 2, "EDIT_POST too short");
|
ensureMin(bb, (4 + 4 + 32 + 4) + (4 + 32) + 2, "EDIT_POST too short");
|
||||||
|
|
||||||
|
this.lineCode = bb.getInt();
|
||||||
this.prevLineNumber = bb.getInt();
|
this.prevLineNumber = bb.getInt();
|
||||||
this.prevLineHash32 = new byte[32];
|
this.prevLineHash32 = new byte[32];
|
||||||
bb.get(this.prevLineHash32);
|
bb.get(this.prevLineHash32);
|
||||||
@ -169,7 +160,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
ensureNoTail(bb, "EDIT_POST");
|
ensureNoTail(bb, "EDIT_POST");
|
||||||
|
|
||||||
} else if (st == (MsgSubType.TEXT_REPLY & 0xFFFF)) {
|
} else if (st == (MsgSubType.TEXT_REPLY & 0xFFFF)) {
|
||||||
// REPLY: target(with bch) + text
|
// REPLY: target(with bch) + text (без line)
|
||||||
ensureMin(bb, 1 + 1 + 4 + 32 + 2, "REPLY too short");
|
ensureMin(bb, 1 + 1 + 4 + 32 + 2, "REPLY too short");
|
||||||
|
|
||||||
int nameLen = Byte.toUnsignedInt(bb.get());
|
int nameLen = Byte.toUnsignedInt(bb.get());
|
||||||
@ -188,6 +179,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
this.message = readStrictUtf8Len16(bb, "REPLY text");
|
this.message = readStrictUtf8Len16(bb, "REPLY text");
|
||||||
|
|
||||||
// line fields отсутствуют в байтах
|
// line fields отсутствуют в байтах
|
||||||
|
this.lineCode = -1;
|
||||||
this.prevLineNumber = -1;
|
this.prevLineNumber = -1;
|
||||||
this.prevLineHash32 = null;
|
this.prevLineHash32 = null;
|
||||||
this.thisLineNumber = -1;
|
this.thisLineNumber = -1;
|
||||||
@ -195,7 +187,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
ensureNoTail(bb, "REPLY");
|
ensureNoTail(bb, "REPLY");
|
||||||
|
|
||||||
} else if (st == (MsgSubType.TEXT_EDIT_REPLY & 0xFFFF)) {
|
} else if (st == (MsgSubType.TEXT_EDIT_REPLY & 0xFFFF)) {
|
||||||
// EDIT_REPLY: target(no bch) + text
|
// EDIT_REPLY: target(no bch) + text (без line)
|
||||||
ensureMin(bb, (4 + 32) + 2, "EDIT_REPLY too short");
|
ensureMin(bb, (4 + 32) + 2, "EDIT_REPLY too short");
|
||||||
|
|
||||||
int tgtNum = bb.getInt();
|
int tgtNum = bb.getInt();
|
||||||
@ -209,6 +201,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
this.message = readStrictUtf8Len16(bb, "EDIT_REPLY text");
|
this.message = readStrictUtf8Len16(bb, "EDIT_REPLY text");
|
||||||
|
|
||||||
// line fields отсутствуют в байтах
|
// line fields отсутствуют в байтах
|
||||||
|
this.lineCode = -1;
|
||||||
this.prevLineNumber = -1;
|
this.prevLineNumber = -1;
|
||||||
this.prevLineHash32 = null;
|
this.prevLineHash32 = null;
|
||||||
this.thisLineNumber = -1;
|
this.thisLineNumber = -1;
|
||||||
@ -216,7 +209,6 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
ensureNoTail(bb, "EDIT_REPLY");
|
ensureNoTail(bb, "EDIT_REPLY");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// недостижимо из-за isValidSubType, но пусть будет
|
|
||||||
throw new IllegalArgumentException("Unsupported Text subType: " + st);
|
throw new IllegalArgumentException("Unsupported Text subType: " + st);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,25 +217,25 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
/* ====================== Фабрики (удобно) ============================= */
|
/* ====================== Фабрики (удобно) ============================= */
|
||||||
/* ===================================================================== */
|
/* ===================================================================== */
|
||||||
|
|
||||||
public static TextBody newPost(int prevLineNumber, byte[] prevLineHash32, int thisLineNumber, String message) {
|
public static TextBody newPost(int lineCode, int prevLineNumber, byte[] prevLineHash32, int thisLineNumber, String message) {
|
||||||
return new TextBody(MsgSubType.TEXT_POST, prevLineNumber, prevLineHash32, thisLineNumber,
|
return new TextBody(MsgSubType.TEXT_POST, lineCode, prevLineNumber, prevLineHash32, thisLineNumber,
|
||||||
message, null, null, null);
|
message, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TextBody newEditPost(int prevLineNumber, byte[] prevLineHash32, int thisLineNumber,
|
public static TextBody newEditPost(int lineCode, int prevLineNumber, byte[] prevLineHash32, int thisLineNumber,
|
||||||
int targetBlockNumber, byte[] targetHash32,
|
int targetBlockNumber, byte[] targetHash32,
|
||||||
String message) {
|
String message) {
|
||||||
return new TextBody(MsgSubType.TEXT_EDIT_POST, prevLineNumber, prevLineHash32, thisLineNumber,
|
return new TextBody(MsgSubType.TEXT_EDIT_POST, lineCode, prevLineNumber, prevLineHash32, thisLineNumber,
|
||||||
message, null, targetBlockNumber, targetHash32);
|
message, null, targetBlockNumber, targetHash32);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TextBody newReply(String toBlockchainName, int targetBlockNumber, byte[] targetHash32, String message) {
|
public static TextBody newReply(String toBlockchainName, int targetBlockNumber, byte[] targetHash32, String message) {
|
||||||
return new TextBody(MsgSubType.TEXT_REPLY, -1, null, -1,
|
return new TextBody(MsgSubType.TEXT_REPLY, -1, -1, null, -1,
|
||||||
message, toBlockchainName, targetBlockNumber, targetHash32);
|
message, toBlockchainName, targetBlockNumber, targetHash32);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TextBody newEditReply(int targetBlockNumber, byte[] targetHash32, String message) {
|
public static TextBody newEditReply(int targetBlockNumber, byte[] targetHash32, String message) {
|
||||||
return new TextBody(MsgSubType.TEXT_EDIT_REPLY, -1, null, -1,
|
return new TextBody(MsgSubType.TEXT_EDIT_REPLY, -1, -1, null, -1,
|
||||||
message, null, targetBlockNumber, targetHash32);
|
message, null, targetBlockNumber, targetHash32);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,6 +244,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
* Для REPLY/EDIT_REPLY line поля игнорируются при сериализации (их в формате нет).
|
* Для REPLY/EDIT_REPLY line поля игнорируются при сериализации (их в формате нет).
|
||||||
*/
|
*/
|
||||||
public TextBody(short subType,
|
public TextBody(short subType,
|
||||||
|
int lineCode,
|
||||||
int prevLineNumber,
|
int prevLineNumber,
|
||||||
byte[] prevLineHash32,
|
byte[] prevLineHash32,
|
||||||
int thisLineNumber,
|
int thisLineNumber,
|
||||||
@ -272,10 +265,13 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
|
|
||||||
// line применима только к POST/EDIT_POST
|
// line применима только к POST/EDIT_POST
|
||||||
if (st == (MsgSubType.TEXT_POST & 0xFFFF) || st == (MsgSubType.TEXT_EDIT_POST & 0xFFFF)) {
|
if (st == (MsgSubType.TEXT_POST & 0xFFFF) || st == (MsgSubType.TEXT_EDIT_POST & 0xFFFF)) {
|
||||||
|
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0 for line message");
|
||||||
|
this.lineCode = lineCode;
|
||||||
this.prevLineNumber = prevLineNumber;
|
this.prevLineNumber = prevLineNumber;
|
||||||
this.prevLineHash32 = (prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
|
this.prevLineHash32 = (prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
|
||||||
this.thisLineNumber = thisLineNumber;
|
this.thisLineNumber = thisLineNumber;
|
||||||
} else {
|
} else {
|
||||||
|
this.lineCode = -1;
|
||||||
this.prevLineNumber = -1;
|
this.prevLineNumber = -1;
|
||||||
this.prevLineHash32 = null;
|
this.prevLineHash32 = null;
|
||||||
this.thisLineNumber = -1;
|
this.thisLineNumber = -1;
|
||||||
@ -322,7 +318,6 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
this.toBlockHash32 = Arrays.copyOf(toBlockHash32, 32);
|
this.toBlockHash32 = Arrays.copyOf(toBlockHash32, 32);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// недостижимо
|
|
||||||
this.toBlockchainName = null;
|
this.toBlockchainName = null;
|
||||||
this.toBlockGlobalNumber = null;
|
this.toBlockGlobalNumber = null;
|
||||||
this.toBlockHash32 = null;
|
this.toBlockHash32 = null;
|
||||||
@ -349,6 +344,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
|
|
||||||
// локальные проверки line (БД не трогаем)
|
// локальные проверки line (БД не трогаем)
|
||||||
if (st == (MsgSubType.TEXT_POST & 0xFFFF) || st == (MsgSubType.TEXT_EDIT_POST & 0xFFFF)) {
|
if (st == (MsgSubType.TEXT_POST & 0xFFFF) || st == (MsgSubType.TEXT_EDIT_POST & 0xFFFF)) {
|
||||||
|
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0 for line message");
|
||||||
if (prevLineHash32 == null || prevLineHash32.length != 32)
|
if (prevLineHash32 == null || prevLineHash32.length != 32)
|
||||||
throw new IllegalArgumentException("prevLineHash32 invalid");
|
throw new IllegalArgumentException("prevLineHash32 invalid");
|
||||||
} else {
|
} else {
|
||||||
@ -399,10 +395,11 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
int st = subType & 0xFFFF;
|
int st = subType & 0xFFFF;
|
||||||
|
|
||||||
if (st == (MsgSubType.TEXT_POST & 0xFFFF)) {
|
if (st == (MsgSubType.TEXT_POST & 0xFFFF)) {
|
||||||
// hasLine + text
|
// hasLine(lineCode+line) + text
|
||||||
int cap = (4 + 32 + 4) + 2 + msgUtf8.length;
|
int cap = (4 + 4 + 32 + 4) + 2 + msgUtf8.length;
|
||||||
|
|
||||||
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
|
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
bb.putInt(lineCode);
|
||||||
bb.putInt(prevLineNumber);
|
bb.putInt(prevLineNumber);
|
||||||
bb.put(prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
|
bb.put(prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
|
||||||
bb.putInt(thisLineNumber);
|
bb.putInt(thisLineNumber);
|
||||||
@ -411,13 +408,14 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
return bb.array();
|
return bb.array();
|
||||||
|
|
||||||
} else if (st == (MsgSubType.TEXT_EDIT_POST & 0xFFFF)) {
|
} else if (st == (MsgSubType.TEXT_EDIT_POST & 0xFFFF)) {
|
||||||
// hasLine + target(no bch) + text
|
// hasLine(lineCode+line) + target(no bch) + text
|
||||||
if (toBlockGlobalNumber == null) throw new IllegalArgumentException("EDIT_POST missing toBlockGlobalNumber");
|
if (toBlockGlobalNumber == null) throw new IllegalArgumentException("EDIT_POST missing toBlockGlobalNumber");
|
||||||
if (toBlockHash32 == null || toBlockHash32.length != 32) throw new IllegalArgumentException("EDIT_POST toBlockHash32 != 32");
|
if (toBlockHash32 == null || toBlockHash32.length != 32) throw new IllegalArgumentException("EDIT_POST toBlockHash32 != 32");
|
||||||
|
|
||||||
int cap = (4 + 32 + 4) + (4 + 32) + 2 + msgUtf8.length;
|
int cap = (4 + 4 + 32 + 4) + (4 + 32) + 2 + msgUtf8.length;
|
||||||
|
|
||||||
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
|
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
bb.putInt(lineCode);
|
||||||
bb.putInt(prevLineNumber);
|
bb.putInt(prevLineNumber);
|
||||||
bb.put(prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
|
bb.put(prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
|
||||||
bb.putInt(thisLineNumber);
|
bb.putInt(thisLineNumber);
|
||||||
@ -506,6 +504,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ====================== BodyHasLine ====================== */
|
/* ====================== BodyHasLine ====================== */
|
||||||
|
@Override public int lineCode() { return lineCode; }
|
||||||
@Override public int prevLineNumber() { return prevLineNumber; }
|
@Override public int prevLineNumber() { return prevLineNumber; }
|
||||||
@Override public byte[] prevLineHash32() {
|
@Override public byte[] prevLineHash32() {
|
||||||
if (prevLineHash32 == null) return null;
|
if (prevLineHash32 == null) return null;
|
||||||
@ -518,8 +517,6 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
|
|||||||
@Override public Integer toBlockGlobalNumber() { return toBlockGlobalNumber; }
|
@Override public Integer toBlockGlobalNumber() { return toBlockGlobalNumber; }
|
||||||
@Override public byte[] toBlockHashBytes() { return toBlockHash32; }
|
@Override public byte[] toBlockHashBytes() { return toBlockHash32; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================== */
|
/* ===================================================================== */
|
||||||
/* ===================== Удобные хелперы (для ChainState) =============== */
|
/* ===================== Удобные хелперы (для ChainState) =============== */
|
||||||
/* ===================================================================== */
|
/* ===================================================================== */
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import java.util.Objects;
|
|||||||
* 1 = TEXT_TEXT
|
* 1 = TEXT_TEXT
|
||||||
*
|
*
|
||||||
* bodyBytes (BigEndian), новый формат:
|
* bodyBytes (BigEndian), новый формат:
|
||||||
|
* [4] lineCode
|
||||||
* [4] prevLineNumber
|
* [4] prevLineNumber
|
||||||
* [32] prevLineHash32
|
* [32] prevLineHash32
|
||||||
* [4] thisLineNumber
|
* [4] thisLineNumber
|
||||||
@ -38,6 +39,7 @@ public final class UserParamBody implements BodyRecord, BodyHasLine {
|
|||||||
public final short version; // из header
|
public final short version; // из header
|
||||||
|
|
||||||
// line
|
// line
|
||||||
|
public final int lineCode;
|
||||||
public final int prevLineNumber;
|
public final int prevLineNumber;
|
||||||
public final byte[] prevLineHash32;
|
public final byte[] prevLineHash32;
|
||||||
public final int thisLineNumber;
|
public final int thisLineNumber;
|
||||||
@ -58,13 +60,15 @@ public final class UserParamBody implements BodyRecord, BodyHasLine {
|
|||||||
throw new IllegalArgumentException("Bad UserParam subType: " + (this.subType & 0xFFFF));
|
throw new IllegalArgumentException("Bad UserParam subType: " + (this.subType & 0xFFFF));
|
||||||
}
|
}
|
||||||
|
|
||||||
// минимум: line(4+32+4) + keyLen(2)+key(1) + valLen(2)+val(1)
|
// минимум: lineCode(4)+line(4+32+4) + keyLen(2)+key(1) + valLen(2)+val(1)
|
||||||
if (bodyBytes.length < (4 + 32 + 4) + 2 + 1 + 2 + 1) {
|
if (bodyBytes.length < 4 + (4 + 32 + 4) + 2 + 1 + 2 + 1) {
|
||||||
throw new IllegalArgumentException("UserParamBody too short");
|
throw new IllegalArgumentException("UserParamBody too short");
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteBuffer bb = ByteBuffer.wrap(bodyBytes).order(ByteOrder.BIG_ENDIAN);
|
ByteBuffer bb = ByteBuffer.wrap(bodyBytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
this.lineCode = bb.getInt();
|
||||||
|
|
||||||
this.prevLineNumber = bb.getInt();
|
this.prevLineNumber = bb.getInt();
|
||||||
|
|
||||||
this.prevLineHash32 = new byte[32];
|
this.prevLineHash32 = new byte[32];
|
||||||
@ -95,7 +99,8 @@ public final class UserParamBody implements BodyRecord, BodyHasLine {
|
|||||||
if (this.paramValue.isBlank()) throw new IllegalArgumentException("paramValue is blank");
|
if (this.paramValue.isBlank()) throw new IllegalArgumentException("paramValue is blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserParamBody(int prevLineNumber,
|
public UserParamBody(int lineCode,
|
||||||
|
int prevLineNumber,
|
||||||
byte[] prevLineHash32,
|
byte[] prevLineHash32,
|
||||||
int thisLineNumber,
|
int thisLineNumber,
|
||||||
String paramKey,
|
String paramKey,
|
||||||
@ -104,9 +109,12 @@ public final class UserParamBody implements BodyRecord, BodyHasLine {
|
|||||||
Objects.requireNonNull(paramKey, "paramKey == null");
|
Objects.requireNonNull(paramKey, "paramKey == null");
|
||||||
Objects.requireNonNull(paramValue, "paramValue == null");
|
Objects.requireNonNull(paramValue, "paramValue == null");
|
||||||
|
|
||||||
|
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0");
|
||||||
|
|
||||||
this.subType = MsgSubType.USER_PARAM_TEXT_TEXT;
|
this.subType = MsgSubType.USER_PARAM_TEXT_TEXT;
|
||||||
this.version = VER;
|
this.version = VER;
|
||||||
|
|
||||||
|
this.lineCode = lineCode;
|
||||||
this.prevLineNumber = prevLineNumber;
|
this.prevLineNumber = prevLineNumber;
|
||||||
this.prevLineHash32 = (prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
|
this.prevLineHash32 = (prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
|
||||||
this.thisLineNumber = thisLineNumber;
|
this.thisLineNumber = thisLineNumber;
|
||||||
@ -120,6 +128,8 @@ public final class UserParamBody implements BodyRecord, BodyHasLine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserParamBody check() {
|
public UserParamBody check() {
|
||||||
|
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0");
|
||||||
|
|
||||||
if ((subType & 0xFFFF) != (MsgSubType.USER_PARAM_TEXT_TEXT & 0xFFFF))
|
if ((subType & 0xFFFF) != (MsgSubType.USER_PARAM_TEXT_TEXT & 0xFFFF))
|
||||||
throw new IllegalArgumentException("Bad UserParam subType: " + (subType & 0xFFFF));
|
throw new IllegalArgumentException("Bad UserParam subType: " + (subType & 0xFFFF));
|
||||||
|
|
||||||
@ -144,12 +154,14 @@ public final class UserParamBody implements BodyRecord, BodyHasLine {
|
|||||||
if (keyUtf8.length == 0 || keyUtf8.length > 65535) throw new IllegalArgumentException("paramKey utf8 len must be 1..65535");
|
if (keyUtf8.length == 0 || keyUtf8.length > 65535) throw new IllegalArgumentException("paramKey utf8 len must be 1..65535");
|
||||||
if (valUtf8.length == 0 || valUtf8.length > 65535) throw new IllegalArgumentException("paramValue utf8 len must be 1..65535");
|
if (valUtf8.length == 0 || valUtf8.length > 65535) throw new IllegalArgumentException("paramValue utf8 len must be 1..65535");
|
||||||
|
|
||||||
int cap = (4 + 32 + 4)
|
int cap = 4 + (4 + 32 + 4)
|
||||||
+ 2 + keyUtf8.length
|
+ 2 + keyUtf8.length
|
||||||
+ 2 + valUtf8.length;
|
+ 2 + valUtf8.length;
|
||||||
|
|
||||||
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
|
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
bb.putInt(lineCode);
|
||||||
|
|
||||||
bb.putInt(prevLineNumber);
|
bb.putInt(prevLineNumber);
|
||||||
bb.put(prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
|
bb.put(prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
|
||||||
bb.putInt(thisLineNumber);
|
bb.putInt(thisLineNumber);
|
||||||
@ -182,6 +194,7 @@ public final class UserParamBody implements BodyRecord, BodyHasLine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ====================== BodyHasLine ====================== */
|
/* ====================== BodyHasLine ====================== */
|
||||||
|
@Override public int lineCode() { return lineCode; }
|
||||||
@Override public int prevLineNumber() { return prevLineNumber; }
|
@Override public int prevLineNumber() { return prevLineNumber; }
|
||||||
@Override public byte[] prevLineHash32() { return prevLineHash32 == null ? null : Arrays.copyOf(prevLineHash32, 32); }
|
@Override public byte[] prevLineHash32() { return prevLineHash32 == null ? null : Arrays.copyOf(prevLineHash32, 32); }
|
||||||
@Override public int thisLineNumber() { return thisLineNumber; }
|
@Override public int thisLineNumber() { return thisLineNumber; }
|
||||||
|
|||||||
@ -182,8 +182,7 @@ public class DatabaseInitializer {
|
|||||||
ON ip_geo_cache (updated_at_ms);
|
ON ip_geo_cache (updated_at_ms);
|
||||||
""");
|
""");
|
||||||
|
|
||||||
// 5. blockchain_state (НОВЫЙ формат под BlockchainStateDAO/Entry)
|
// 5. blockchain_state
|
||||||
// ВАЖНО: last_block_number / last_block_hash (а не last_global_*)
|
|
||||||
st.executeUpdate("""
|
st.executeUpdate("""
|
||||||
CREATE TABLE IF NOT EXISTS blockchain_state (
|
CREATE TABLE IF NOT EXISTS blockchain_state (
|
||||||
blockchain_name TEXT NOT NULL PRIMARY KEY,
|
blockchain_name TEXT NOT NULL PRIMARY KEY,
|
||||||
@ -212,13 +211,12 @@ public class DatabaseInitializer {
|
|||||||
ON blockchain_state (updated_at_ms);
|
ON blockchain_state (updated_at_ms);
|
||||||
""");
|
""");
|
||||||
|
|
||||||
// 6. blocks (НОВЫЙ формат под BlocksDAO/BlockEntry)
|
// 6. blocks (+ line_code)
|
||||||
// Ключ: (bch_name, block_number)
|
|
||||||
st.executeUpdate("""
|
st.executeUpdate("""
|
||||||
CREATE TABLE IF NOT EXISTS blocks (
|
CREATE TABLE IF NOT EXISTS blocks (
|
||||||
login TEXT NOT NULL,
|
login TEXT NOT NULL,
|
||||||
bch_name TEXT NOT NULL,
|
bch_name TEXT NOT NULL,
|
||||||
block_number INTEGER NOT NULL,
|
block_number INTEGER NOT NULL CHECK(block_number >= 0),
|
||||||
|
|
||||||
msg_type INTEGER NOT NULL,
|
msg_type INTEGER NOT NULL,
|
||||||
msg_sub_type INTEGER NOT NULL,
|
msg_sub_type INTEGER NOT NULL,
|
||||||
@ -228,7 +226,7 @@ public class DatabaseInitializer {
|
|||||||
-- target (reply/like/edit и т.д.)
|
-- target (reply/like/edit и т.д.)
|
||||||
to_login TEXT,
|
to_login TEXT,
|
||||||
to_bch_name TEXT,
|
to_bch_name TEXT,
|
||||||
to_block_number INTEGER,
|
to_block_number INTEGER CHECK(to_block_number IS NULL OR to_block_number >= 0),
|
||||||
to_block_hash BLOB,
|
to_block_hash BLOB,
|
||||||
|
|
||||||
-- собственные данные
|
-- собственные данные
|
||||||
@ -236,12 +234,13 @@ public class DatabaseInitializer {
|
|||||||
block_signature BLOB NOT NULL,
|
block_signature BLOB NOT NULL,
|
||||||
|
|
||||||
-- если этот блок был изменён последним edit'ом
|
-- если этот блок был изменён последним edit'ом
|
||||||
edited_by_block_number INTEGER,
|
edited_by_block_number INTEGER CHECK(edited_by_block_number IS NULL OR edited_by_block_number >= 0),
|
||||||
|
|
||||||
-- линейность (опционально)
|
-- линейность (опционально)
|
||||||
prev_line_number INTEGER,
|
line_code INTEGER CHECK(line_code IS NULL OR line_code >= 0),
|
||||||
|
prev_line_number INTEGER CHECK(prev_line_number IS NULL OR prev_line_number >= 0),
|
||||||
prev_line_hash BLOB,
|
prev_line_hash BLOB,
|
||||||
this_line_number INTEGER,
|
this_line_number INTEGER CHECK(this_line_number IS NULL OR this_line_number >= 0),
|
||||||
|
|
||||||
FOREIGN KEY (login) REFERENCES solana_users(login),
|
FOREIGN KEY (login) REFERENCES solana_users(login),
|
||||||
FOREIGN KEY (bch_name) REFERENCES blockchain_state(blockchain_name),
|
FOREIGN KEY (bch_name) REFERENCES blockchain_state(blockchain_name),
|
||||||
@ -260,7 +259,143 @@ public class DatabaseInitializer {
|
|||||||
ON blocks (to_login, to_bch_name, to_block_number);
|
ON blocks (to_login, to_bch_name, to_block_number);
|
||||||
""");
|
""");
|
||||||
|
|
||||||
// 7) connections_state (под SubscriptionsDAO: rel_type + to_login/to_bch_name)
|
st.executeUpdate("""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_blocks_by_line
|
||||||
|
ON blocks (bch_name, line_code, this_line_number);
|
||||||
|
""");
|
||||||
|
|
||||||
|
// 6.1) TRIGGER: проверка целостности линии (только если line-поля реально переданы)
|
||||||
|
st.executeUpdate("""
|
||||||
|
CREATE TRIGGER IF NOT EXISTS trg_blocks_line_integrity_bi
|
||||||
|
BEFORE INSERT ON blocks
|
||||||
|
WHEN
|
||||||
|
NEW.line_code IS NOT NULL
|
||||||
|
OR NEW.prev_line_number IS NOT NULL
|
||||||
|
OR NEW.prev_line_hash IS NOT NULL
|
||||||
|
OR NEW.this_line_number IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
-- ============================================================
|
||||||
|
-- LINE-INTEGRITY (BodyHasLine)
|
||||||
|
--
|
||||||
|
-- Этот триггер срабатывает ТОЛЬКО если при вставке передали хотя бы одно line-поле.
|
||||||
|
--
|
||||||
|
-- Типы, которые МОГУТ быть линейными (BodyHasLine в коде проекта):
|
||||||
|
-- - TECH (msg_type=0): CreateChannelBody (и т.п. тех-блоки с линией)
|
||||||
|
-- - TEXT (msg_type=1): TextBody в режиме линии (пост/редактирование поста в канале)
|
||||||
|
-- - CONNECTION (msg_type=3): ConnectionBody
|
||||||
|
-- - USER_PARAM (msg_type=4): UserParamBody
|
||||||
|
--
|
||||||
|
-- Проверки:
|
||||||
|
-- 1) Если передали line-поля -> обязаны передать ВСЕ 4:
|
||||||
|
-- line_code, prev_line_number, prev_line_hash, this_line_number.
|
||||||
|
-- 2) prev блок линии существует и p.block_hash == NEW.prev_line_hash
|
||||||
|
-- 3) line_code корректный:
|
||||||
|
-- - либо NEW.prev_line_number == NEW.line_code (первый шаг после root),
|
||||||
|
-- - либо у prev блока p.line_code == NEW.line_code
|
||||||
|
-- 4) this_line_number корректный:
|
||||||
|
-- - первый шаг после root:
|
||||||
|
-- TEXT: this=0
|
||||||
|
-- TECH/CONNECTION/USER_PARAM: this=1
|
||||||
|
-- - дальше:
|
||||||
|
-- TEXT: допускаем this = prev.this или prev.this + 1
|
||||||
|
-- TECH/CONNECTION/USER_PARAM: строго this = prev.this + 1
|
||||||
|
--
|
||||||
|
-- Ошибки: RAISE(ABORT, 'LINE_ERR_...') — чтобы Java могла понять причину.
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- 0) line-поля нельзя у неожиданных типов
|
||||||
|
SELECT RAISE(ABORT,
|
||||||
|
'LINE_ERR_UNSUPPORTED_TYPE_WITH_LINE: msg_type=' || NEW.msg_type || ' msg_sub_type=' || NEW.msg_sub_type
|
||||||
|
)
|
||||||
|
WHERE NOT (NEW.msg_type IN (0, 1, 3, 4));
|
||||||
|
|
||||||
|
-- 1) line-поля должны быть заполнены полностью (без “частично”)
|
||||||
|
SELECT RAISE(ABORT,
|
||||||
|
'LINE_ERR_PARTIAL_FIELDS: all of (line_code, prev_line_number, prev_line_hash, this_line_number) must be NOT NULL'
|
||||||
|
)
|
||||||
|
WHERE NEW.line_code IS NULL
|
||||||
|
OR NEW.prev_line_number IS NULL
|
||||||
|
OR NEW.prev_line_hash IS NULL
|
||||||
|
OR NEW.this_line_number IS NULL;
|
||||||
|
|
||||||
|
-- 2) prev существует?
|
||||||
|
SELECT RAISE(ABORT,
|
||||||
|
'LINE_ERR_NO_PREV: bch=' || NEW.bch_name || ' block=' || NEW.block_number || ' prev=' || NEW.prev_line_number
|
||||||
|
)
|
||||||
|
WHERE NOT EXISTS(
|
||||||
|
SELECT 1
|
||||||
|
FROM blocks p
|
||||||
|
WHERE p.bch_name = NEW.bch_name
|
||||||
|
AND p.block_number = NEW.prev_line_number
|
||||||
|
LIMIT 1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 3) prev hash совпадает?
|
||||||
|
SELECT RAISE(ABORT,
|
||||||
|
'LINE_ERR_PREV_HASH_MISMATCH: bch=' || NEW.bch_name || ' block=' || NEW.block_number || ' prev=' || NEW.prev_line_number
|
||||||
|
)
|
||||||
|
WHERE NOT EXISTS(
|
||||||
|
SELECT 1
|
||||||
|
FROM blocks p
|
||||||
|
WHERE p.bch_name = NEW.bch_name
|
||||||
|
AND p.block_number = NEW.prev_line_number
|
||||||
|
AND p.block_hash = NEW.prev_line_hash
|
||||||
|
LIMIT 1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 4) line_code корректный:
|
||||||
|
-- либо это первый шаг после root (prev_line_number == line_code),
|
||||||
|
-- либо prev уже в этой линии (p.line_code == NEW.line_code).
|
||||||
|
SELECT RAISE(ABORT,
|
||||||
|
'LINE_ERR_LINE_CODE_MISMATCH: bch=' || NEW.bch_name || ' block=' || NEW.block_number ||
|
||||||
|
' line_code=' || NEW.line_code || ' prev=' || NEW.prev_line_number
|
||||||
|
)
|
||||||
|
WHERE NEW.prev_line_number <> NEW.line_code
|
||||||
|
AND NOT EXISTS(
|
||||||
|
SELECT 1
|
||||||
|
FROM blocks p
|
||||||
|
WHERE p.bch_name = NEW.bch_name
|
||||||
|
AND p.block_number = NEW.prev_line_number
|
||||||
|
AND p.line_code = NEW.line_code
|
||||||
|
LIMIT 1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 5) первый шаг после root: this_line_number
|
||||||
|
SELECT RAISE(ABORT,
|
||||||
|
'LINE_ERR_FIRST_STEP_BAD_THIS: expected this_line_number=0 for TEXT or =1 for other types'
|
||||||
|
)
|
||||||
|
WHERE NEW.prev_line_number = NEW.line_code
|
||||||
|
AND NEW.this_line_number <> (CASE WHEN NEW.msg_type = 1 THEN 0 ELSE 1 END);
|
||||||
|
|
||||||
|
-- 6) обычный шаг: this_line_number относительно prev
|
||||||
|
SELECT RAISE(ABORT,
|
||||||
|
'LINE_ERR_THIS_LINE_BAD_STEP: bch=' || NEW.bch_name || ' block=' || NEW.block_number ||
|
||||||
|
' this=' || NEW.this_line_number || ' prev=' || NEW.prev_line_number
|
||||||
|
)
|
||||||
|
WHERE NEW.prev_line_number <> NEW.line_code
|
||||||
|
AND NOT EXISTS(
|
||||||
|
SELECT 1
|
||||||
|
FROM blocks p
|
||||||
|
WHERE p.bch_name = NEW.bch_name
|
||||||
|
AND p.block_number = NEW.prev_line_number
|
||||||
|
AND p.this_line_number IS NOT NULL
|
||||||
|
AND (
|
||||||
|
-- TEXT: допускаем same или +1 (поддерживает “edit не увеличивает thisLineNumber”)
|
||||||
|
(NEW.msg_type = 1 AND
|
||||||
|
(NEW.this_line_number = p.this_line_number OR NEW.this_line_number = p.this_line_number + 1)
|
||||||
|
)
|
||||||
|
OR
|
||||||
|
-- TECH/CONNECTION/USER_PARAM: строго +1
|
||||||
|
(NEW.msg_type IN (0,3,4) AND
|
||||||
|
NEW.this_line_number = p.this_line_number + 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
LIMIT 1
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
""");
|
||||||
|
|
||||||
|
// 7) connections_state
|
||||||
st.executeUpdate("""
|
st.executeUpdate("""
|
||||||
CREATE TABLE IF NOT EXISTS connections_state (
|
CREATE TABLE IF NOT EXISTS connections_state (
|
||||||
login TEXT NOT NULL,
|
login TEXT NOT NULL,
|
||||||
@ -291,7 +426,7 @@ public class DatabaseInitializer {
|
|||||||
ON connections_state (login, to_login);
|
ON connections_state (login, to_login);
|
||||||
""");
|
""");
|
||||||
|
|
||||||
// 8) Trigger: connection state (под новые имена колонок)
|
// 8) Trigger: connection state
|
||||||
st.executeUpdate("""
|
st.executeUpdate("""
|
||||||
CREATE TRIGGER IF NOT EXISTS trg_blocks_connection_state_ai
|
CREATE TRIGGER IF NOT EXISTS trg_blocks_connection_state_ai
|
||||||
AFTER INSERT ON blocks
|
AFTER INSERT ON blocks
|
||||||
@ -343,7 +478,7 @@ public class DatabaseInitializer {
|
|||||||
(int) CONNECTION_UNFOLLOW
|
(int) CONNECTION_UNFOLLOW
|
||||||
));
|
));
|
||||||
|
|
||||||
// 9) message_stats (под новые to_* имена) + edits_count
|
// 9) message_stats
|
||||||
st.executeUpdate("""
|
st.executeUpdate("""
|
||||||
CREATE TABLE IF NOT EXISTS message_stats (
|
CREATE TABLE IF NOT EXISTS message_stats (
|
||||||
to_login TEXT NOT NULL,
|
to_login TEXT NOT NULL,
|
||||||
@ -440,20 +575,18 @@ public class DatabaseInitializer {
|
|||||||
END;
|
END;
|
||||||
""".formatted((int) TEXT_REPLY));
|
""".formatted((int) TEXT_REPLY));
|
||||||
|
|
||||||
// 12) Trigger: EDIT — пометить исходный блок + увеличить edits_count
|
// 12) Trigger: EDIT
|
||||||
st.executeUpdate("""
|
st.executeUpdate("""
|
||||||
CREATE TRIGGER IF NOT EXISTS trg_blocks_edit_apply_ai
|
CREATE TRIGGER IF NOT EXISTS trg_blocks_edit_apply_ai
|
||||||
AFTER INSERT ON blocks
|
AFTER INSERT ON blocks
|
||||||
WHEN NEW.msg_type = 1 AND NEW.msg_sub_type = %d
|
WHEN NEW.msg_type = 1 AND NEW.msg_sub_type = %d
|
||||||
BEGIN
|
BEGIN
|
||||||
-- 1) Помечаем исходный блок, что его изменили последним edit'ом
|
|
||||||
UPDATE blocks
|
UPDATE blocks
|
||||||
SET edited_by_block_number = NEW.block_number
|
SET edited_by_block_number = NEW.block_number
|
||||||
WHERE login = NEW.login
|
WHERE login = NEW.login
|
||||||
AND bch_name = NEW.bch_name
|
AND bch_name = NEW.bch_name
|
||||||
AND block_number = NEW.to_block_number;
|
AND block_number = NEW.to_block_number;
|
||||||
|
|
||||||
-- 2) edits_count +1 в message_stats (upsert)
|
|
||||||
INSERT INTO message_stats (
|
INSERT INTO message_stats (
|
||||||
to_login,
|
to_login,
|
||||||
to_bch_name,
|
to_bch_name,
|
||||||
|
|||||||
@ -50,10 +50,11 @@ public final class BlocksDAO {
|
|||||||
block_hash,
|
block_hash,
|
||||||
block_signature,
|
block_signature,
|
||||||
edited_by_block_number,
|
edited_by_block_number,
|
||||||
|
line_code,
|
||||||
prev_line_number,
|
prev_line_number,
|
||||||
prev_line_hash,
|
prev_line_hash,
|
||||||
this_line_number
|
this_line_number
|
||||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||||
""";
|
""";
|
||||||
|
|
||||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||||
@ -86,6 +87,10 @@ public final class BlocksDAO {
|
|||||||
if (e.getEditedByBlockNumber() != null) ps.setInt(i++, e.getEditedByBlockNumber());
|
if (e.getEditedByBlockNumber() != null) ps.setInt(i++, e.getEditedByBlockNumber());
|
||||||
else ps.setNull(i++, Types.INTEGER);
|
else ps.setNull(i++, Types.INTEGER);
|
||||||
|
|
||||||
|
// NEW: line_code
|
||||||
|
if (e.getLineCode() != null) ps.setInt(i++, e.getLineCode());
|
||||||
|
else ps.setNull(i++, Types.INTEGER);
|
||||||
|
|
||||||
if (e.getPrevLineNumber() != null) ps.setInt(i++, e.getPrevLineNumber());
|
if (e.getPrevLineNumber() != null) ps.setInt(i++, e.getPrevLineNumber());
|
||||||
else ps.setNull(i++, Types.INTEGER);
|
else ps.setNull(i++, Types.INTEGER);
|
||||||
|
|
||||||
@ -151,6 +156,7 @@ public final class BlocksDAO {
|
|||||||
block_hash,
|
block_hash,
|
||||||
block_signature,
|
block_signature,
|
||||||
edited_by_block_number,
|
edited_by_block_number,
|
||||||
|
line_code,
|
||||||
prev_line_number,
|
prev_line_number,
|
||||||
prev_line_hash,
|
prev_line_hash,
|
||||||
this_line_number
|
this_line_number
|
||||||
@ -211,6 +217,10 @@ public final class BlocksDAO {
|
|||||||
Integer editedBy = (Integer) rs.getObject("edited_by_block_number");
|
Integer editedBy = (Integer) rs.getObject("edited_by_block_number");
|
||||||
e.setEditedByBlockNumber(editedBy);
|
e.setEditedByBlockNumber(editedBy);
|
||||||
|
|
||||||
|
// NEW: line_code
|
||||||
|
Integer lineCode = (Integer) rs.getObject("line_code");
|
||||||
|
e.setLineCode(lineCode);
|
||||||
|
|
||||||
Integer prevLn = (Integer) rs.getObject("prev_line_number");
|
Integer prevLn = (Integer) rs.getObject("prev_line_number");
|
||||||
e.setPrevLineNumber(prevLn);
|
e.setPrevLineNumber(prevLn);
|
||||||
|
|
||||||
|
|||||||
@ -11,9 +11,9 @@ package shine.db.entities;
|
|||||||
* - block_signature (64 байта)
|
* - block_signature (64 байта)
|
||||||
*
|
*
|
||||||
* Опционально:
|
* Опционально:
|
||||||
* - prev_line_number / prev_line_hash / this_line_number
|
* - line_code / prev_line_number / prev_line_hash / this_line_number
|
||||||
*
|
*
|
||||||
* Плюс поля индексации (как раньше было удобно):
|
* Плюс поля индексации:
|
||||||
* - msg_type / msg_sub_type
|
* - msg_type / msg_sub_type
|
||||||
* - to_* (если есть target)
|
* - to_* (если есть target)
|
||||||
* - edited_by_block_number (для TEXT_EDIT)
|
* - edited_by_block_number (для TEXT_EDIT)
|
||||||
@ -40,6 +40,9 @@ public class BlockEntry {
|
|||||||
|
|
||||||
private Integer editedByBlockNumber;
|
private Integer editedByBlockNumber;
|
||||||
|
|
||||||
|
// NEW:
|
||||||
|
private Integer lineCode;
|
||||||
|
|
||||||
private Integer prevLineNumber;
|
private Integer prevLineNumber;
|
||||||
private byte[] prevLineHash;
|
private byte[] prevLineHash;
|
||||||
private Integer thisLineNumber;
|
private Integer thisLineNumber;
|
||||||
@ -85,6 +88,10 @@ public class BlockEntry {
|
|||||||
public Integer getEditedByBlockNumber() { return editedByBlockNumber; }
|
public Integer getEditedByBlockNumber() { return editedByBlockNumber; }
|
||||||
public void setEditedByBlockNumber(Integer editedByBlockNumber) { this.editedByBlockNumber = editedByBlockNumber; }
|
public void setEditedByBlockNumber(Integer editedByBlockNumber) { this.editedByBlockNumber = editedByBlockNumber; }
|
||||||
|
|
||||||
|
// NEW:
|
||||||
|
public Integer getLineCode() { return lineCode; }
|
||||||
|
public void setLineCode(Integer lineCode) { this.lineCode = lineCode; }
|
||||||
|
|
||||||
public Integer getPrevLineNumber() { return prevLineNumber; }
|
public Integer getPrevLineNumber() { return prevLineNumber; }
|
||||||
public void setPrevLineNumber(Integer prevLineNumber) { this.prevLineNumber = prevLineNumber; }
|
public void setPrevLineNumber(Integer prevLineNumber) { this.prevLineNumber = prevLineNumber; }
|
||||||
|
|
||||||
|
|||||||
39
src/test/addblocks.sh
Executable file
39
src/test/addblocks.sh
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
OUTFILE="all_files.txt"
|
||||||
|
|
||||||
|
# === Список файлов (ТОЛЬКО имена без расширений) ===
|
||||||
|
# пример: Main значит Main.java, Utils значит Utils.java
|
||||||
|
NAMES=(
|
||||||
|
"IT_04_UserParams_NoAuth"
|
||||||
|
"AddBlockSender"
|
||||||
|
"ChainState"
|
||||||
|
"JsonBuilders"
|
||||||
|
)
|
||||||
|
|
||||||
|
# очищаем или создаём файл
|
||||||
|
: > "$OUTFILE"
|
||||||
|
|
||||||
|
# Быстрый фильтр: сделаем хеш-таблицу из имён (ассоц. массив)
|
||||||
|
declare -A WANT=()
|
||||||
|
for name in "${NAMES[@]}"; do
|
||||||
|
WANT["$name"]=1
|
||||||
|
done
|
||||||
|
|
||||||
|
# собрать только нужные *.java по базовому имени
|
||||||
|
find . -type f -name "*.java" | sort | while read -r f; do
|
||||||
|
base="$(basename "$f" .java)"
|
||||||
|
if [[ -n "${WANT[$base]+x}" ]]; then
|
||||||
|
cat "$f" >> "$OUTFILE"
|
||||||
|
echo >> "$OUTFILE" # пустая строка-разделитель
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# скопировать весь файл в буфер обмена (Wayland)
|
||||||
|
wl-copy < "$OUTFILE"
|
||||||
|
|
||||||
|
echo "Готово!"
|
||||||
|
echo "Выбрано имён: ${#NAMES[@]}"
|
||||||
|
echo "Все нужные .java файлы собраны в $OUTFILE"
|
||||||
|
echo "Содержимое скопировано в буфер обмена (Wayland)"
|
||||||
@ -1,20 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
OUTFILE="all_files.txt"
|
|
||||||
|
|
||||||
# очищаем или создаём файл
|
|
||||||
: > "$OUTFILE"
|
|
||||||
|
|
||||||
# собрать только *.java файлы и вывести их содержимое в файл
|
|
||||||
find . -type f -name "*.java" | sort | while read -r f; do
|
|
||||||
cat "$f" >> "$OUTFILE"
|
|
||||||
echo >> "$OUTFILE" # пустая строка-разделитель
|
|
||||||
done
|
|
||||||
|
|
||||||
# скопировать весь файл в буфер обмена (Wayland)
|
|
||||||
wl-copy < "$OUTFILE"
|
|
||||||
|
|
||||||
echo "Готово!"
|
|
||||||
echo "Все .java файлы собраны в $OUTFILE"
|
|
||||||
echo "Содержимое скопировано в буфер обмена (Wayland)"
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
package test.it.blockchain;
|
package test.it.blockchain;
|
||||||
|
|
||||||
import blockchain.MsgSubType;
|
|
||||||
import blockchain.body.BodyRecord;
|
import blockchain.body.BodyRecord;
|
||||||
import blockchain.body.BodyHasLine;
|
import blockchain.body.BodyHasLine;
|
||||||
import blockchain.body.CreateChannelBody;
|
import blockchain.body.CreateChannelBody;
|
||||||
@ -22,9 +21,12 @@ import java.util.Map;
|
|||||||
* - CONNECTION (type=3): одна линия
|
* - CONNECTION (type=3): одна линия
|
||||||
* - USER_PARAM (type=4): одна линия
|
* - USER_PARAM (type=4): одна линия
|
||||||
*
|
*
|
||||||
* Важно:
|
* ВАЖНО:
|
||||||
* - prevLineNumber — это GLOBAL blockNumber предыдущего блока линии.
|
* - prevLineNumber — это GLOBAL blockNumber предыдущего блока линии.
|
||||||
* - thisLineNumber — внутренний номер линии (для постов: 0,1,2...; для тех-линии: 1,2,3...)
|
* - thisLineNumber — внутренний номер линии (для постов: 0,1,2...; для тех-линии: 1,2,3...)
|
||||||
|
* - lineCode — код линии:
|
||||||
|
* * 0 для канала "0" и для "простых" линий (connection/user_param/tech)
|
||||||
|
* * для каналов !=0: lineCode = blockNumber "заглавия" канала (CREATE_CHANNEL)
|
||||||
*/
|
*/
|
||||||
public final class ChainState {
|
public final class ChainState {
|
||||||
|
|
||||||
@ -79,14 +81,16 @@ public final class ChainState {
|
|||||||
|
|
||||||
// ---------- TEXT channels ----------
|
// ---------- TEXT channels ----------
|
||||||
public static final class ChannelLineState {
|
public static final class ChannelLineState {
|
||||||
final int rootBlockNumber;
|
final int lineCode; // для каналов: = rootBlockNumber; для канала 0: 0
|
||||||
|
final int rootBlockNumber; // 0 для канала 0, иначе blockNumber CREATE_CHANNEL
|
||||||
final String rootHashHex;
|
final String rootHashHex;
|
||||||
|
|
||||||
int lastGlobalNumber;
|
int lastGlobalNumber;
|
||||||
String lastHashHex;
|
String lastHashHex;
|
||||||
int lastThisLineNumber; // перед первым постом = -1, чтобы первый был 0
|
int lastThisLineNumber; // перед первым постом = -1, чтобы первый был 0
|
||||||
|
|
||||||
ChannelLineState(int rootBlockNumber, String rootHashHex) {
|
ChannelLineState(int lineCode, int rootBlockNumber, String rootHashHex) {
|
||||||
|
this.lineCode = lineCode;
|
||||||
this.rootBlockNumber = rootBlockNumber;
|
this.rootBlockNumber = rootBlockNumber;
|
||||||
this.rootHashHex = rootHashHex;
|
this.rootHashHex = rootHashHex;
|
||||||
this.lastGlobalNumber = rootBlockNumber;
|
this.lastGlobalNumber = rootBlockNumber;
|
||||||
@ -95,7 +99,7 @@ public final class ChainState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// rootBlockNumber -> state
|
// lineCode -> state (для канала 0 lineCode=0)
|
||||||
private final Map<Integer, ChannelLineState> textChannels = new HashMap<>();
|
private final Map<Integer, ChannelLineState> textChannels = new HashMap<>();
|
||||||
|
|
||||||
public ChainState() {
|
public ChainState() {
|
||||||
@ -134,18 +138,20 @@ public final class ChainState {
|
|||||||
// -------------------- line helpers --------------------
|
// -------------------- line helpers --------------------
|
||||||
|
|
||||||
public static final class NextLine {
|
public static final class NextLine {
|
||||||
|
public final int lineCode;
|
||||||
public final int prevLineNumber; // GLOBAL blockNumber
|
public final int prevLineNumber; // GLOBAL blockNumber
|
||||||
public final byte[] prevLineHash32; // 32 bytes
|
public final byte[] prevLineHash32; // 32 bytes
|
||||||
public final int thisLineNumber; // внутр. номер линии
|
public final int thisLineNumber; // внутр. номер линии
|
||||||
|
|
||||||
public NextLine(int prevLineNumber, byte[] prevLineHash32, int thisLineNumber) {
|
public NextLine(int lineCode, int prevLineNumber, byte[] prevLineHash32, int thisLineNumber) {
|
||||||
|
this.lineCode = lineCode;
|
||||||
this.prevLineNumber = prevLineNumber;
|
this.prevLineNumber = prevLineNumber;
|
||||||
this.prevLineHash32 = (prevLineHash32 == null ? null : prevLineHash32.clone());
|
this.prevLineHash32 = (prevLineHash32 == null ? null : prevLineHash32.clone());
|
||||||
this.thisLineNumber = thisLineNumber;
|
this.thisLineNumber = thisLineNumber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Следующие line-поля для TECH/CONNECTION/USER_PARAM. */
|
/** Следующие line-поля для TECH/CONNECTION/USER_PARAM. lineCode=0. */
|
||||||
public NextLine nextLineByType(short type) {
|
public NextLine nextLineByType(short type) {
|
||||||
if (!hasHeader()) {
|
if (!hasHeader()) {
|
||||||
throw new IllegalStateException("Нельзя формировать line-поля до HEADER (нет headerHash32)");
|
throw new IllegalStateException("Нельзя формировать line-поля до HEADER (нет headerHash32)");
|
||||||
@ -154,12 +160,15 @@ public final class ChainState {
|
|||||||
int t = type & 0xFFFF;
|
int t = type & 0xFFFF;
|
||||||
|
|
||||||
if (t == TYPE_TECH) {
|
if (t == TYPE_TECH) {
|
||||||
// tech-line: prev = последний TECH; первый CREATE_CHANNEL -> prev = HEADER
|
|
||||||
if (techLine.lastGlobalNumber == -1) {
|
if (techLine.lastGlobalNumber == -1) {
|
||||||
// после HEADER мы должны инициализировать techLine (делаем в applyHeader)
|
|
||||||
throw new IllegalStateException("TECH line is not initialized yet");
|
throw new IllegalStateException("TECH line is not initialized yet");
|
||||||
}
|
}
|
||||||
return new NextLine(techLine.lastGlobalNumber, hexToBytes32(techLine.lastHashHex), techLine.lastThisLineNumber + 1);
|
return new NextLine(
|
||||||
|
0,
|
||||||
|
techLine.lastGlobalNumber,
|
||||||
|
hexToBytes32(techLine.lastHashHex),
|
||||||
|
techLine.lastThisLineNumber + 1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t == TYPE_CONNECTION) {
|
if (t == TYPE_CONNECTION) {
|
||||||
@ -175,35 +184,55 @@ public final class ChainState {
|
|||||||
private NextLine nextSimpleLine(SimpleLineState ls) {
|
private NextLine nextSimpleLine(SimpleLineState ls) {
|
||||||
if (ls.lastGlobalNumber == -1) {
|
if (ls.lastGlobalNumber == -1) {
|
||||||
// первый блок линии ссылается на HEADER (block#0)
|
// первый блок линии ссылается на HEADER (block#0)
|
||||||
return new NextLine(0, headerHash32.clone(), 1);
|
return new NextLine(0, 0, headerHash32.clone(), 1);
|
||||||
}
|
}
|
||||||
if (ls.lastHashHex == null || ls.lastHashHex.isBlank()) {
|
if (ls.lastHashHex == null || ls.lastHashHex.isBlank()) {
|
||||||
throw new IllegalStateException("LineState.lastHashHex пуст, но lastGlobalNumber!=-1");
|
throw new IllegalStateException("LineState.lastHashHex пуст, но lastGlobalNumber!=-1");
|
||||||
}
|
}
|
||||||
return new NextLine(ls.lastGlobalNumber, hexToBytes32(ls.lastHashHex), ls.lastThisLineNumber + 1);
|
return new NextLine(0, ls.lastGlobalNumber, hexToBytes32(ls.lastHashHex), ls.lastThisLineNumber + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Следующие line-поля для TEXT-канала по rootBlockNumber. */
|
/**
|
||||||
public NextLine nextTextLineByRoot(int rootBlockNumber) {
|
* Следующие line-поля для TEXT-канала по lineCode.
|
||||||
|
* Для канала 0: lineCode=0.
|
||||||
|
* Для других каналов: lineCode = rootBlockNumber (CREATE_CHANNEL blockNumber).
|
||||||
|
*/
|
||||||
|
public NextLine nextTextLineByCode(int lineCode) {
|
||||||
if (!hasHeader()) throw new IllegalStateException("No HEADER");
|
if (!hasHeader()) throw new IllegalStateException("No HEADER");
|
||||||
ChannelLineState cs = textChannels.get(rootBlockNumber);
|
ChannelLineState cs = textChannels.get(lineCode);
|
||||||
if (cs == null) throw new IllegalStateException("Unknown TEXT channel rootBlockNumber=" + rootBlockNumber);
|
if (cs == null) throw new IllegalStateException("Unknown TEXT channel lineCode=" + lineCode);
|
||||||
|
|
||||||
return new NextLine(
|
return new NextLine(
|
||||||
|
lineCode,
|
||||||
cs.lastGlobalNumber,
|
cs.lastGlobalNumber,
|
||||||
hexToBytes32(cs.lastHashHex),
|
hexToBytes32(cs.lastHashHex),
|
||||||
cs.lastThisLineNumber + 1
|
cs.lastThisLineNumber + 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Зарегистрировать новый канал TEXT по root = CREATE_CHANNEL block. */
|
/** Старое имя — оставил для удобства: rootBlockNumber == lineCode для каналов. */
|
||||||
public void registerTextChannelRoot(int rootBlockNumber, byte[] rootHash32) {
|
public NextLine nextTextLineByRoot(int rootBlockNumber) {
|
||||||
if (rootBlockNumber <= 0) throw new IllegalArgumentException("rootBlockNumber must be > 0 for custom channel");
|
return nextTextLineByCode(rootBlockNumber);
|
||||||
if (rootHash32 == null || rootHash32.length != 32) throw new IllegalArgumentException("rootHash32 invalid");
|
|
||||||
textChannels.put(rootBlockNumber, new ChannelLineState(rootBlockNumber, bytesToHex64(rootHash32)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** root канала "0" (по умолчанию) — это HEADER block#0. */
|
/**
|
||||||
|
* Зарегистрировать новый канал TEXT:
|
||||||
|
* - lineCode = rootBlockNumber (blockNumber CREATE_CHANNEL)
|
||||||
|
* ИДЕМПОТЕНТНО: если уже зарегистрирован — ничего не делаем.
|
||||||
|
*/
|
||||||
|
public void registerTextChannelRoot(int rootBlockNumber, byte[] rootHash32) {
|
||||||
|
if (rootBlockNumber < 0) throw new IllegalArgumentException("rootBlockNumber must be >= 0");
|
||||||
|
if (rootHash32 == null || rootHash32.length != 32) throw new IllegalArgumentException("rootHash32 invalid");
|
||||||
|
|
||||||
|
if (textChannels.containsKey(rootBlockNumber)) {
|
||||||
|
return; // уже есть — не трогаем, чтобы не сбросить lastThisLineNumber и т.д.
|
||||||
|
}
|
||||||
|
|
||||||
|
int lineCode = rootBlockNumber;
|
||||||
|
textChannels.put(lineCode, new ChannelLineState(lineCode, rootBlockNumber, bytesToHex64(rootHash32)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** root/lineCode канала "0" (по умолчанию) — это HEADER block#0, lineCode=0. */
|
||||||
public int rootChannel0() {
|
public int rootChannel0() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -240,8 +269,8 @@ public final class ChainState {
|
|||||||
techLine.lastHashHex = hex64;
|
techLine.lastHashHex = hex64;
|
||||||
techLine.lastThisLineNumber = 0;
|
techLine.lastThisLineNumber = 0;
|
||||||
|
|
||||||
// TEXT channel "0" root = HEADER, первый пост будет thisLineNumber=0
|
// TEXT channel "0" root = HEADER, lineCode=0
|
||||||
textChannels.put(0, new ChannelLineState(0, hex64));
|
registerTextChannelRoot(0, hash32);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -253,6 +282,11 @@ public final class ChainState {
|
|||||||
techLine.lastGlobalNumber = blockNumber;
|
techLine.lastGlobalNumber = blockNumber;
|
||||||
techLine.lastHashHex = hex64;
|
techLine.lastHashHex = hex64;
|
||||||
techLine.lastThisLineNumber = ccb.thisLineNumber;
|
techLine.lastThisLineNumber = ccb.thisLineNumber;
|
||||||
|
|
||||||
|
// ВАЖНО: CREATE_CHANNEL — это root нового текстового канала:
|
||||||
|
// lineCode для этого канала = blockNumber CREATE_CHANNEL
|
||||||
|
registerTextChannelRoot(blockNumber, hash32);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,10 +307,14 @@ public final class ChainState {
|
|||||||
// ---- TEXT channels (POST/EDIT_POST) ----
|
// ---- TEXT channels (POST/EDIT_POST) ----
|
||||||
if (t == TYPE_TEXT && body instanceof TextBody tb) {
|
if (t == TYPE_TEXT && body instanceof TextBody tb) {
|
||||||
if (tb.isLineMessage()) {
|
if (tb.isLineMessage()) {
|
||||||
// ищем канал по совпадению prevLineNumber с lastGlobalNumber канала
|
int lineCode = tb.lineCode;
|
||||||
ChannelLineState channel = findTextChannelByLastGlobal(tb.prevLineNumber);
|
|
||||||
|
ChannelLineState channel = textChannels.get(lineCode);
|
||||||
if (channel == null) {
|
if (channel == null) {
|
||||||
throw new IllegalStateException("TEXT line message prevLineNumber=" + tb.prevLineNumber + " не привязан ни к одному каналу (канал root не зарегистрирован?)");
|
throw new IllegalStateException(
|
||||||
|
"TEXT line message has unknown lineCode=" + lineCode +
|
||||||
|
" (канал не зарегистрирован; ждали CREATE_CHANNEL или HEADER)"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.lastGlobalNumber = blockNumber;
|
channel.lastGlobalNumber = blockNumber;
|
||||||
@ -286,13 +324,6 @@ public final class ChainState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChannelLineState findTextChannelByLastGlobal(int prevLineNumber) {
|
|
||||||
for (ChannelLineState cs : textChannels.values()) {
|
|
||||||
if (cs.lastGlobalNumber == prevLineNumber) return cs;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- utils --------------------
|
// -------------------- utils --------------------
|
||||||
|
|
||||||
private static byte[] hexToBytes32(String hex) {
|
private static byte[] hexToBytes32(String hex) {
|
||||||
|
|||||||
@ -64,13 +64,14 @@ public class IT_03_AddBlock_NoAuth {
|
|||||||
assertTrue(st1.hasHeader());
|
assertTrue(st1.hasHeader());
|
||||||
|
|
||||||
// канал "0" (root=HEADER) — по умолчанию существует
|
// канал "0" (root=HEADER) — по умолчанию существует
|
||||||
int root0 = st1.rootChannel0();
|
int root0 = st1.rootChannel0(); // lineCode для канала "0" = 0
|
||||||
|
|
||||||
// POST в канал "0"
|
// POST в канал "0"
|
||||||
{
|
{
|
||||||
var ln = st1.nextTextLineByRoot(root0);
|
var ln = st1.nextTextLineByRoot(root0);
|
||||||
sender1.send(new TextBody(
|
sender1.send(new TextBody(
|
||||||
MsgSubType.TEXT_POST,
|
MsgSubType.TEXT_POST,
|
||||||
|
root0, // lineCode
|
||||||
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
|
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
|
||||||
"U1: story/post in channel 0",
|
"U1: story/post in channel 0",
|
||||||
null, null, null
|
null, null, null
|
||||||
@ -87,11 +88,12 @@ public class IT_03_AddBlock_NoAuth {
|
|||||||
{
|
{
|
||||||
var ln = st1.nextLineByType(ChainState.TYPE_TECH);
|
var ln = st1.nextLineByType(ChainState.TYPE_TECH);
|
||||||
sender1.send(new CreateChannelBody(
|
sender1.send(new CreateChannelBody(
|
||||||
|
0, // lineCode для TECH линии
|
||||||
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
|
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
|
||||||
"News"
|
"News"
|
||||||
), t);
|
), t);
|
||||||
|
|
||||||
newsRootBlock = st1.lastBlockNumber();
|
newsRootBlock = st1.lastBlockNumber(); // root канала = blockNumber CREATE_CHANNEL
|
||||||
newsRootHash = st1.getHash32(newsRootBlock);
|
newsRootHash = st1.getHash32(newsRootBlock);
|
||||||
assertNotNull(newsRootHash);
|
assertNotNull(newsRootHash);
|
||||||
|
|
||||||
@ -106,6 +108,7 @@ public class IT_03_AddBlock_NoAuth {
|
|||||||
var ln = st1.nextTextLineByRoot(newsRootBlock);
|
var ln = st1.nextTextLineByRoot(newsRootBlock);
|
||||||
sender1.send(new TextBody(
|
sender1.send(new TextBody(
|
||||||
MsgSubType.TEXT_POST,
|
MsgSubType.TEXT_POST,
|
||||||
|
newsRootBlock, // lineCode = root блока канала (CREATE_CHANNEL)
|
||||||
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
|
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
|
||||||
"U1: News post #0",
|
"U1: News post #0",
|
||||||
null, null, null
|
null, null, null
|
||||||
@ -121,18 +124,19 @@ public class IT_03_AddBlock_NoAuth {
|
|||||||
var ln = st1.nextTextLineByRoot(newsRootBlock);
|
var ln = st1.nextTextLineByRoot(newsRootBlock);
|
||||||
sender1.send(new TextBody(
|
sender1.send(new TextBody(
|
||||||
MsgSubType.TEXT_POST,
|
MsgSubType.TEXT_POST,
|
||||||
|
newsRootBlock, // lineCode
|
||||||
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
|
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
|
||||||
"U1: News post #1",
|
"U1: News post #1",
|
||||||
null, null, null
|
null, null, null
|
||||||
), t);
|
), t);
|
||||||
}
|
}
|
||||||
|
|
||||||
// EDIT_POST (не увеличивает thisLineNumber, но является частью линии)
|
// EDIT_POST (является частью линии; lineCode обязателен)
|
||||||
{
|
{
|
||||||
var ln = st1.nextTextLineByRoot(newsRootBlock);
|
var ln = st1.nextTextLineByRoot(newsRootBlock);
|
||||||
// edit должен иметь thisLineNumber как у предыдущего сообщения линии (ChainState это уже даёт)
|
|
||||||
sender1.send(new TextBody(
|
sender1.send(new TextBody(
|
||||||
MsgSubType.TEXT_EDIT_POST,
|
MsgSubType.TEXT_EDIT_POST,
|
||||||
|
newsRootBlock, // lineCode
|
||||||
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
|
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
|
||||||
"U1: News post #0 (EDIT)",
|
"U1: News post #0 (EDIT)",
|
||||||
null,
|
null,
|
||||||
@ -151,14 +155,13 @@ public class IT_03_AddBlock_NoAuth {
|
|||||||
assertTrue(st2.hasHeader());
|
assertTrue(st2.hasHeader());
|
||||||
|
|
||||||
// REPLY (20): ответ на post в чужом блокчейне/канале
|
// REPLY (20): ответ на post в чужом блокчейне/канале
|
||||||
|
// ВАЖНО: REPLY не имеет line-полей вообще, поэтому используем фабрику newReply().
|
||||||
{
|
{
|
||||||
sender2.send(new TextBody(
|
sender2.send(TextBody.newReply(
|
||||||
MsgSubType.TEXT_REPLY,
|
|
||||||
-1, new byte[32], -1, // для replies линии нет
|
|
||||||
"U2: reply to U1 News post #0 (cross-chain)",
|
|
||||||
bch1,
|
bch1,
|
||||||
newsPost0Block,
|
newsPost0Block,
|
||||||
newsPost0Hash
|
newsPost0Hash,
|
||||||
|
"U2: reply to U1 News post #0 (cross-chain)"
|
||||||
), t);
|
), t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user