Переписал код кучи классов перешёл на новый надеюсь теперь подходящий формат блоков

и тесты переделал.

Но пока остались баги и тесты не проходят (в частности пользователи не создаются - ошибка в бд)
This commit is contained in:
AidarKC
2026-01-13 16:18:38 +03:00
parent b7025dde59
commit e9e05c1192
23 changed files with 1516 additions and 2379 deletions
@@ -1,16 +1,13 @@
package test.it.blockchain;
import blockchain.BchBlockEntry;
import blockchain.BchCryptoVerifier;
import blockchain.body.BodyRecord;
import test.it.utils.json.JsonParsers;
import blockchain.body.*;
import test.it.utils.TestConfig;
import test.it.utils.TestIds;
import test.it.utils.json.JsonParsers;
import test.it.utils.log.TestLog;
import test.it.utils.ws.WsSession;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.time.Duration;
import java.util.Base64;
@@ -18,16 +15,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* AddBlockSender — отправка AddBlock поверх одного WsSession:
* - берёт номера/prev-hash из ChainState
* - строит raw/hash/signature
* - отправляет AddBlock
* - проверяет serverLastGlobalHash == localHash
* - обновляет ChainState
* AddBlockSender — под новый формат BchBlockEntry:
* - block хранит только preimage + signature
* - hash32 вычисляется как sha256(preimage)
* - signature = Ed25519.sign(hash32)
*/
public final class AddBlockSender {
private static final byte[] ZERO32 = new byte[32];
private static final String ZERO64 = "0".repeat(64);
private final WsSession ws;
@@ -52,69 +46,89 @@ public final class AddBlockSender {
public void send(BodyRecord body, Duration timeout) {
if (body == null) throw new IllegalArgumentException("body == null");
short lineIndex = body.expectedLineIndex();
body.check();
if (lineIndex == 0) {
if (state.globalLastNumber() != -1) throw new IllegalStateException("HEADER должен быть первым: globalLastNumber уже " + state.globalLastNumber());
boolean isHeader = (body instanceof HeaderBody);
if (isHeader) {
if (state.lastBlockNumber() != -1) {
throw new IllegalStateException("HEADER должен быть первым: lastBlockNumber уже " + state.lastBlockNumber());
}
} else {
if (!state.hasHeader()) throw new IllegalStateException("Нельзя слать line=" + lineIndex + " до HEADER (нет headerHash32)");
if (!state.hasHeader()) {
throw new IllegalStateException("Нельзя слать блоки до HEADER (нет headerHash32)");
}
}
int globalNumber = state.nextGlobalNumber();
int lineNumber = state.nextLineNumber(lineIndex);
int blockNumber = state.nextBlockNumber();
byte[] prevHash32 = state.prevHash32ForNext();
long tsSec = System.currentTimeMillis() / 1000L;
byte[] prevGlobalHash32 = (lineIndex == 0) ? ZERO32 : state.prevGlobalHash32ForNext(lineIndex);
byte[] prevLineHash32 = (lineIndex == 0) ? ZERO32 : state.prevLineHash32ForNext(lineIndex);
short type = typeOf(body);
short subType = subTypeOf(body);
short version = versionOf(body);
long ts = System.currentTimeMillis() / 1000L;
byte[] bodyBytes = body.toBytes();
int recordSize = BchBlockEntry.RAW_HEADER_SIZE + bodyBytes.length;
byte[] rawBytes = ByteBuffer.allocate(recordSize)
.order(ByteOrder.BIG_ENDIAN)
.putInt(recordSize)
.putInt(globalNumber)
.putLong(ts)
.putShort(lineIndex)
.putInt(lineNumber)
.put(bodyBytes)
.array();
byte[] preimage = BchCryptoVerifier.buildPreimage(login, prevGlobalHash32, prevLineHash32, rawBytes);
byte[] hash32 = BchCryptoVerifier.sha256(preimage);
// preimage -> hash32 -> signature
byte[] preimage = buildPreimage(prevHash32, blockNumber, tsSec, type, subType, version, bodyBytes);
byte[] hash32 = blockchain.BchCryptoVerifier.sha256(preimage);
byte[] signature64 = utils.crypto.Ed25519Util.sign(hash32, loginPrivKey);
BchBlockEntry entry = new BchBlockEntry(globalNumber, ts, lineIndex, lineNumber, bodyBytes, signature64, hash32);
BchBlockEntry entry = new BchBlockEntry(
prevHash32,
blockNumber,
tsSec,
type,
subType,
version,
bodyBytes,
signature64
);
String prevGlobalHashHex = (globalNumber == 0) ? ZERO64 : state.globalLastHashHex();
String prevHashHexForReq = (blockNumber == 0) ? ZERO64 : state.lastBlockHashHex();
String reqJson = buildAddBlockJson(blockchainName, globalNumber, prevGlobalHashHex, base64(entry.toBytes()));
String op = "AddBlock(user=" + login + ", global=" + globalNumber + ", line=" + lineIndex + ", lineNum=" + lineNumber + ")";
String reqJson = buildAddBlockJson(blockchainName, blockNumber, prevHashHexForReq, base64(entry.toBytes()));
String op = "AddBlock(user=" + login + ", block=" + blockNumber + ", type=" + (type & 0xFFFF) + ", sub=" + (subType & 0xFFFF) + ")";
String resp = ws.call(op, reqJson, timeout);
assert200(op, resp);
String serverLastGlobalHash = JsonMini.extractPayloadString(resp, "serverLastGlobalHash");
assertNotNull(serverLastGlobalHash, op + ": payload.serverLastGlobalHash must not be null");
assertEquals(64, serverLastGlobalHash.trim().length(), op + ": serverLastGlobalHash must be 64 hex chars");
String serverLastHash = JsonMini.extractPayloadString(resp, "serverLastBlockHash");
if (serverLastHash == null) {
// на случай старого имени, но по твоей просьбе мы на это больше не опираемся
serverLastHash = JsonMini.extractPayloadString(resp, "serverLastGlobalHash");
}
String localHashHex = bytesToHex64(hash32);
assertNotNull(serverLastHash, op + ": payload.serverLastBlockHash must not be null");
assertEquals(64, serverLastHash.trim().length(), op + ": serverLastBlockHash must be 64 hex chars");
String localHashHex = bytesToHex64(entry.getHash32());
if (TestConfig.DEBUG()) {
TestLog.info(op + ": localHash=" + localHashHex);
TestLog.info(op + ": serverLastGlobalHash=" + serverLastGlobalHash);
TestLog.info(op + ": serverLastBlockHash=" + serverLastHash);
}
assertEquals(localHashHex, serverLastGlobalHash, op + ": serverLastGlobalHash must match local hash");
assertEquals(localHashHex, serverLastHash, op + ": serverLastBlockHash must match local hash");
state.applyAppendedBlock(globalNumber, lineIndex, lineNumber, hash32);
state.applyAppendedBlock(blockNumber, entry.getHash32(), isHeader, type);
// если это line-body — обновим thisLineNumber в state (для nextLine())
if (body instanceof BodyHasLine hl) {
short lineIndex = lineIndexByType(type);
if (lineIndex != -1) {
state.applyThisLineNumber(lineIndex, hl.thisLineNumber());
}
}
if (TestConfig.DEBUG()) TestLog.info(op + ": state updated");
}
private static String buildAddBlockJson(String blockchainName, int globalNumber, String prevGlobalHashHex, String blockBytesB64) {
// ---------- request JSON ----------
private static String buildAddBlockJson(String blockchainName, int blockNumber, String prevBlockHashHex, String blockBytesB64) {
String requestId = TestIds.next("addblock");
return """
{
@@ -122,12 +136,12 @@ public final class AddBlockSender {
"requestId": "%s",
"payload": {
"blockchainName": "%s",
"globalNumber": %d,
"prevGlobalHash": "%s",
"blockNumber": %d,
"prevBlockHash": "%s",
"blockBytesB64": "%s"
}
}
""".formatted(requestId, blockchainName, globalNumber, prevGlobalHashHex, blockBytesB64);
""".formatted(requestId, blockchainName, blockNumber, prevBlockHashHex, blockBytesB64);
}
private static void assert200(String op, String resp) {
@@ -150,4 +164,70 @@ public final class AddBlockSender {
}
return new String(out);
}
// ---------- header extraction from body ----------
private static short typeOf(BodyRecord body) {
if (body instanceof HeaderBody) return HeaderBody.TYPE;
if (body instanceof TextBody) return TextBody.TYPE;
if (body instanceof ReactionBody) return ReactionBody.TYPE;
if (body instanceof ConnectionBody) return ConnectionBody.TYPE;
if (body instanceof UserParamBody) return UserParamBody.TYPE;
throw new IllegalArgumentException("Unknown body class: " + body.getClass());
}
private static short subTypeOf(BodyRecord body) {
if (body instanceof HeaderBody hb) return hb.subType;
if (body instanceof TextBody tb) return tb.subType;
if (body instanceof ReactionBody rb) return rb.subType;
if (body instanceof ConnectionBody cb) return cb.subType;
if (body instanceof UserParamBody ub) return ub.subType;
throw new IllegalArgumentException("Unknown body class: " + body.getClass());
}
private static short versionOf(BodyRecord body) {
if (body instanceof HeaderBody hb) return hb.version;
if (body instanceof TextBody tb) return tb.version;
if (body instanceof ReactionBody rb) return rb.version;
if (body instanceof ConnectionBody cb) return cb.version;
if (body instanceof UserParamBody ub) return ub.version;
throw new IllegalArgumentException("Unknown body class: " + body.getClass());
}
// ---------- preimage builder (строго по BchBlockEntry) ----------
private static byte[] buildPreimage(byte[] prevHash32,
int blockNumber,
long tsSec,
short type,
short subType,
short version,
byte[] bodyBytes) {
int blockSize = BchBlockEntry.RAW_HEADER_SIZE + (bodyBytes == null ? 0 : bodyBytes.length);
java.nio.ByteBuffer bb = java.nio.ByteBuffer.allocate(blockSize).order(java.nio.ByteOrder.BIG_ENDIAN);
bb.put(prevHash32);
bb.putInt(blockSize);
bb.putInt(blockNumber);
bb.putLong(tsSec);
bb.putShort(type);
bb.putShort(subType);
bb.putShort(version);
if (bodyBytes != null) bb.put(bodyBytes);
return bb.array();
}
private static short lineIndexByType(short type) {
int t = type & 0xFFFF;
return switch (t) {
case 0 -> blockchain.LineIndex.HEADER;
case 1 -> blockchain.LineIndex.TEXT;
case 3 -> blockchain.LineIndex.CONNECTION;
case 4 -> blockchain.LineIndex.USER_PARAM;
default -> (short) -1;
};
}
}
+123 -90
View File
@@ -1,17 +1,25 @@
package test.it.blockchain;
import blockchain.LineIndex;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* ChainState — только состояние цепочки (номера/хэши).
* ChainState — состояние цепочки + состояние линий (только тех, где они нужны):
*
* Хранит:
* - last globalNumber / last globalHash
* - last lineNum / last lineHash по каждой линии
* - hash32 нулевого блока (headerHash32) — нужен как prevLineHash для первого блока каждой линии
* - map globalNumber -> hash32 (для ссылок reply/reaction на старые блоки)
* Глобальная цепочка:
* - lastBlockNumber / lastBlockHashHex
* - map blockNumber -> hash32 (для ссылок reply/edit/reaction)
*
* Линии (по ТЗ нужны):
* - TEXT (1)
* - CONNECTION (3)
* - USER_PARAM (4)
*
* prevLineNumber по ТЗ — это GLOBAL blockNumber предыдущего блока линии.
* thisLineNumber — внутренний номер линии (мы ведём локально: 1,2,3...)
*/
public final class ChainState {
@@ -20,134 +28,157 @@ public final class ChainState {
private static final byte[] ZERO32 = new byte[32];
private static final String ZERO64 = "0".repeat(64);
private final int[] lineLastNumber = new int[LINES_MAX];
private final String[] lineLastHashHex = new String[LINES_MAX];
private int globalLastNumber = -1;
private String globalLastHashHex = ZERO64;
// global chain
private int lastBlockNumber = -1;
private String lastBlockHashHex = ZERO64;
// header (block#0)
private byte[] headerHash32 = null;
// Для удобства тестов: чтобы можно было делать reply/like на любой уже отправленный globalNumber
private final Map<Integer, byte[]> globalHash32ByNumber = new HashMap<>();
// per-line state (только для LineIndex.TEXT/CONNECTION/USER_PARAM)
private final int[] lineLastGlobalNumber = new int[LINES_MAX]; // последний GLOBAL номер блока в линии
private final String[] lineLastHashHex = new String[LINES_MAX]; // hash последнего блока линии
private final int[] lineLastThisLineNumber = new int[LINES_MAX]; // последний thisLineNumber (внутренний)
private final Map<Integer, byte[]> hash32ByNumber = new HashMap<>();
public ChainState() {
Arrays.fill(lineLastGlobalNumber, -1);
Arrays.fill(lineLastHashHex, "");
// lineLastNumber по умолчанию = 0
Arrays.fill(lineLastThisLineNumber, 0);
}
// -------------------- getters --------------------
// -------------------- global getters --------------------
public int globalLastNumber() { return globalLastNumber; }
public String globalLastHashHex() { return globalLastHashHex; }
public int lastBlockNumber() { return lastBlockNumber; }
public String lastBlockHashHex() { return lastBlockHashHex; }
public int lineLastNumber(short line) { return lineLastNumber[line]; }
public String lineLastHashHex(short line) { return lineLastHashHex[line]; }
public boolean hasHeader() {
return headerHash32 != null && headerHash32.length == 32 && lastBlockNumber >= 0;
}
public byte[] headerHash32() { return headerHash32 == null ? null : headerHash32.clone(); }
public int nextBlockNumber() {
return lastBlockNumber + 1;
}
public byte[] getGlobalHash32(int globalNumber) {
byte[] h = globalHash32ByNumber.get(globalNumber);
public byte[] prevHash32ForNext() {
if (lastBlockNumber < 0) return ZERO32;
return hexToBytes32(lastBlockHashHex);
}
public byte[] headerHash32() {
return headerHash32 == null ? null : headerHash32.clone();
}
public byte[] getHash32(int blockNumber) {
byte[] h = hash32ByNumber.get(blockNumber);
return h == null ? null : h.clone();
}
// -------------------- state helpers --------------------
// -------------------- line helpers --------------------
public boolean hasHeader() {
return headerHash32 != null && headerHash32.length == 32 && globalLastNumber >= 0;
public static final class NextLine {
public final int prevLineNumber; // GLOBAL blockNumber
public final byte[] prevLineHash32; // 32 bytes
public final int thisLineNumber; // внутр. номер линии
public NextLine(int prevLineNumber, byte[] prevLineHash32, int thisLineNumber) {
this.prevLineNumber = prevLineNumber;
this.prevLineHash32 = (prevLineHash32 == null ? null : prevLineHash32.clone());
this.thisLineNumber = thisLineNumber;
}
}
/** Следующий globalNumber. */
public int nextGlobalNumber() {
return globalLastNumber + 1;
}
/** Следующий lineNumber: для line>0 — last+1. Для line0 — всегда 0 (header). */
public int nextLineNumber(short lineIndex) {
/** Следующие line-поля для указанной линии (только TEXT/CONNECTION/USER_PARAM). */
public NextLine nextLine(short lineIndex) {
checkLine(lineIndex);
if (lineIndex == 0) return 0;
return lineLastNumber[lineIndex] + 1;
}
if (!isLineUsed(lineIndex)) {
throw new IllegalArgumentException("Line " + lineIndex + " не используется для BodyHasLine по ТЗ");
}
if (!hasHeader()) {
throw new IllegalStateException("Нельзя формировать line-поля до HEADER (нет headerHash32)");
}
/** prevGlobalHash32: для header это ZERO32, иначе hash последнего глобального блока. */
public byte[] prevGlobalHash32ForNext(short nextLineIndex) {
// Для genesis/header prevGlobalHash = ZERO32
if (globalLastNumber < 0) return ZERO32;
return hexToBytes32(globalLastHashHex);
}
int lastGlobal = lineLastGlobalNumber[lineIndex];
int lastThis = lineLastThisLineNumber[lineIndex];
/**
* prevLineHash32 по твоему правилу:
* - для line0 (header) — ZERO32
* - для первого блока линии (lineLastNumber[line]==0) — hash нулевого блока (headerHash32)
* - иначе — hash последнего блока этой линии
*/
public byte[] prevLineHash32ForNext(short lineIndex) {
checkLine(lineIndex);
if (lineIndex == 0) return ZERO32;
if (lineLastNumber[lineIndex] == 0) {
if (headerHash32 == null) {
throw new IllegalStateException("headerHash32 is not set but required for first block of line " + lineIndex);
}
return headerHash32.clone();
if (lastGlobal == -1) {
// первый блок линии ссылается на HEADER (block#0)
return new NextLine(0, headerHash32.clone(), 1);
}
String lastHex = lineLastHashHex[lineIndex];
if (lastHex == null || lastHex.isBlank()) {
throw new IllegalStateException("lineLastHashHex[" + lineIndex + "] is blank but lineLastNumber>0");
throw new IllegalStateException("lineLastHashHex[" + lineIndex + "] пуст, но lastGlobal!=-1");
}
return hexToBytes32(lastHex);
return new NextLine(lastGlobal, hexToBytes32(lastHex), lastThis + 1);
}
/**
* Применить факт успешного добавления блока:
* - обновить global last
* - обновить line last
* - сохранить globalNumber->hash32
* - если это header: сохранить headerHash32
*/
public void applyAppendedBlock(int globalNumber,
short lineIndex,
int lineNumber,
byte[] hash32) {
// -------------------- apply --------------------
public void applyAppendedBlock(int blockNumber, byte[] hash32, boolean isHeader, short type) {
if (hash32 == null || hash32.length != 32) {
throw new IllegalArgumentException("hash32 must be 32 bytes");
}
// базовые ожидания по номерам (для тестов строго)
if (globalNumber != globalLastNumber + 1) {
throw new IllegalStateException("globalNumber sequence broken: expected=" + (globalLastNumber + 1) + " got=" + globalNumber);
if (blockNumber != lastBlockNumber + 1) {
throw new IllegalStateException("blockNumber sequence broken: expected=" + (lastBlockNumber + 1) + " got=" + blockNumber);
}
checkLine(lineIndex);
if (lineIndex == 0) {
if (globalNumber != 0 || lineNumber != 0) {
throw new IllegalStateException("Header must be global=0 line=0 lineNum=0");
}
if (isHeader) {
if (blockNumber != 0) throw new IllegalStateException("HEADER must be blockNumber=0");
headerHash32 = hash32.clone();
} else {
int expectedLineNum = lineLastNumber[lineIndex] + 1;
if (lineNumber != expectedLineNum) {
throw new IllegalStateException("lineNumber sequence broken for line=" + lineIndex +
": expected=" + expectedLineNum + " got=" + lineNumber);
}
if (blockNumber == 0) throw new IllegalStateException("Non-header block can't be blockNumber=0");
if (headerHash32 == null) throw new IllegalStateException("Header must be sent before non-header blocks");
}
String hex64 = bytesToHex64(hash32);
globalLastNumber = globalNumber;
globalLastHashHex = hex64;
lastBlockNumber = blockNumber;
lastBlockHashHex = hex64;
lineLastNumber[lineIndex] = lineNumber;
lineLastHashHex[lineIndex] = hex64;
hash32ByNumber.put(blockNumber, hash32.clone());
globalHash32ByNumber.put(globalNumber, hash32.clone());
// обновляем line-state только для линий, которые "надо" по ТЗ
short lineIndex = lineIndexByType(type);
if (lineIndex != -1 && isLineUsed(lineIndex)) {
lineLastGlobalNumber[lineIndex] = blockNumber;
lineLastHashHex[lineIndex] = hex64;
// thisLineNumber мы берём из тела, но здесь его нет.
// Поэтому thisLineNumber должен обновляться там, где формируются тела (в тестах),
// либо AddBlockSender может прокинуть его отдельно.
// Чтобы не дублировать контракт — здесь оставляем как есть.
}
}
// -------------------- utils --------------------
/** В тестах удобно явно обновлять thisLineNumber после успешной отправки line-body. */
public void applyThisLineNumber(short lineIndex, int thisLineNumber) {
checkLine(lineIndex);
if (!isLineUsed(lineIndex)) return;
lineLastThisLineNumber[lineIndex] = thisLineNumber;
}
// -------------------- mapping --------------------
/** По type блока определяем lineIndex. Reaction line по твоему ТЗ "не надо". */
private static short lineIndexByType(short type) {
int t = type & 0xFFFF;
return switch (t) {
case 0 -> LineIndex.HEADER;
case 1 -> LineIndex.TEXT;
case 3 -> LineIndex.CONNECTION;
case 4 -> LineIndex.USER_PARAM;
default -> (short) -1; // reaction/unknown => line state not used
};
}
private static boolean isLineUsed(short lineIndex) {
return lineIndex == LineIndex.TEXT
|| lineIndex == LineIndex.CONNECTION
|| lineIndex == LineIndex.USER_PARAM;
}
private static void checkLine(short lineIndex) {
if (lineIndex < 0 || lineIndex >= LINES_MAX) {
@@ -155,6 +186,8 @@ public final class ChainState {
}
}
// -------------------- utils --------------------
private static byte[] hexToBytes32(String hex) {
if (hex == null) throw new IllegalArgumentException("hex is null");
String s = hex.trim();
@@ -1,10 +1,8 @@
package test.it.cases;
import blockchain.body.ConnectionBody;
import blockchain.body.HeaderBody;
import blockchain.body.ReactionBody;
import blockchain.body.TextBody;
import blockchain.body.UserParamBody;
import blockchain.LineIndex;
import blockchain.body.*;
import shine.db.MsgSubType;
import test.it.blockchain.AddBlockSender;
import test.it.blockchain.ChainState;
import test.it.utils.TestConfig;
@@ -17,11 +15,7 @@ import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;
/**
* IT_03_AddBlock_NoAuth
*
* ВАЖНО:
* - пользователей НЕ создаём (их создаёт IT_01)
* - ключи берём только из TestConfig по login
* IT_03_AddBlock_NoAuth — обновлён под новый формат блоков (ТЗ).
*/
public class IT_03_AddBlock_NoAuth {
@@ -63,30 +57,88 @@ public class IT_03_AddBlock_NoAuth {
sender1.send(new HeaderBody(u1), t);
assertTrue(st1.hasHeader());
sender1.send(new TextBody(TextBody.SUB_NEW, "Hello #1 (NEW) from IT_03 test"), t);
sender1.send(new TextBody(TextBody.SUB_NEW, "Hello #2 (NEW) from IT_03 test"), t);
sender1.send(new TextBody(TextBody.SUB_NEW, "Hello #3 (NEW) from IT_03 test"), t);
// TEXT_NEW x3 (с line)
{
var ln = st1.nextLine(LineIndex.TEXT);
sender1.send(new TextBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.TEXT_NEW,
"Hello #1 (NEW) from IT_03 test",
null, null, null
), t);
}
{
var ln = st1.nextLine(LineIndex.TEXT);
sender1.send(new TextBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.TEXT_NEW,
"Hello #2 (NEW) from IT_03 test",
null, null, null
), t);
}
{
var ln = st1.nextLine(LineIndex.TEXT);
sender1.send(new TextBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.TEXT_NEW,
"Hello #3 (NEW) from IT_03 test",
null, null, null
), t);
}
byte[] text1Hash = st1.getGlobalHash32(1);
byte[] text2Hash = st1.getGlobalHash32(2);
byte[] text3Hash = st1.getGlobalHash32(3);
byte[] text1Hash = st1.getHash32(1);
byte[] text2Hash = st1.getHash32(2);
byte[] text3Hash = st1.getHash32(3);
assertNotNull(text1Hash);
assertNotNull(text2Hash);
assertNotNull(text3Hash);
sender1.send(new TextBody(TextBody.SUB_REPLY, "Reply to TEXT#1", bch1, 1, text1Hash), t);
sender1.send(new TextBody(TextBody.SUB_REPLY, "Reply to TEXT#3", bch1, 3, text3Hash), t);
// TEXT_REPLY x2 (с line + target)
{
var ln = st1.nextLine(LineIndex.TEXT);
sender1.send(new TextBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.TEXT_REPLY,
"Reply to TEXT#1",
bch1, 1, text1Hash
), t);
}
{
var ln = st1.nextLine(LineIndex.TEXT);
sender1.send(new TextBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.TEXT_REPLY,
"Reply to TEXT#3",
bch1, 3, text3Hash
), t);
}
sender1.send(new ReactionBody(ReactionBody.SUB_LIKE, bch1, 1, text1Hash), t);
sender1.send(new ReactionBody(ReactionBody.SUB_LIKE, bch1, 2, text2Hash), t);
// REACTION_LIKE x2 (без line)
sender1.send(new ReactionBody(bch1, 1, text1Hash), t);
sender1.send(new ReactionBody(bch1, 2, text2Hash), t);
sender1.send(new TextBody(TextBody.SUB_EDIT, "Hello #2 (EDIT#1) from IT_03 test", bch1, 2, text2Hash), t);
sender1.send(new TextBody(TextBody.SUB_EDIT, "Hello #2 (EDIT#2) from IT_03 test", bch1, 2, text2Hash), t);
sender1.send(new TextBody(TextBody.SUB_EDIT, "Hello #3 (EDIT#1) from IT_03 test", bch1, 3, text3Hash), t);
// TEXT_EDIT x3 (с line + target)
{
var ln = st1.nextLine(LineIndex.TEXT);
sender1.send(new TextBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.TEXT_EDIT,
"Hello #2 (EDIT#1) from IT_03 test",
bch1, 2, text2Hash
), t);
}
{
var ln = st1.nextLine(LineIndex.TEXT);
sender1.send(new TextBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.TEXT_EDIT,
"Hello #2 (EDIT#2) from IT_03 test",
bch1, 2, text2Hash
), t);
}
{
var ln = st1.nextLine(LineIndex.TEXT);
sender1.send(new TextBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.TEXT_EDIT,
"Hello #3 (EDIT#1) from IT_03 test",
bch1, 3, text3Hash
), t);
}
assertEquals(10, st1.globalLastNumber(), "USER1: globalLastNumber должен быть 10 (11 блоков)");
assertEquals(8, st1.lineLastNumber((short) 1), "USER1: line=1 должно быть 8 TEXT блоков");
assertEquals(2, st1.lineLastNumber((short) 2), "USER1: line=2 должно быть 2 REACTION блока");
assertEquals(10, st1.lastBlockNumber(), "USER1: lastBlockNumber должен быть 10 (всего 11 блоков включая HEADER)");
// USER2
ChainState st2 = new ChainState();
@@ -95,7 +147,13 @@ public class IT_03_AddBlock_NoAuth {
sender2.send(new HeaderBody(u2), t);
assertTrue(st2.hasHeader());
sender2.send(new UserParamBody("Anya", "Amsterdam, Example street 10"), t);
// USER_PARAM (с line)
{
var ln = st2.nextLine(LineIndex.USER_PARAM);
sender2.send(new UserParamBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
"Anya", "Amsterdam, Example street 10"
), t);
}
// USER3 (нужен, чтобы u1 мог подписаться на существующий блокчейн)
ChainState st3 = new ChainState();
@@ -105,27 +163,70 @@ public class IT_03_AddBlock_NoAuth {
assertTrue(st3.hasHeader());
// -----------------------------------------------------------------
// Подписки (как ты просил):
// Подписки:
// - u1 follows u2 и u3
// - u2 follows только u1
// Все CONNECTION идут по линии CONNECTION (по ТЗ "да надо")
// -----------------------------------------------------------------
// u1 -> follow u2
sender1.send(new ConnectionBody(ConnectionBody.SUB_FOLLOW, u2, bch2, 0, new byte[32]), t);
{
var ln = st1.nextLine(LineIndex.CONNECTION);
sender1.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_FOLLOW,
u2, bch2, 0, new byte[32]
), t);
}
// u1 -> follow u3
sender1.send(new ConnectionBody(ConnectionBody.SUB_FOLLOW, u3, bch3, 0, new byte[32]), t);
{
var ln = st1.nextLine(LineIndex.CONNECTION);
sender1.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_FOLLOW,
u3, bch3, 0, new byte[32]
), t);
}
// u2 -> follow u1
sender2.send(new ConnectionBody(ConnectionBody.SUB_FOLLOW, u1, bch1, 0, new byte[32]), t);
{
var ln = st2.nextLine(LineIndex.CONNECTION);
sender2.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_FOLLOW,
u1, bch1, 0, new byte[32]
), t);
}
// (оставил твои friend/unfriend как было но они уже не обязательны для подписок)
sender2.send(new ConnectionBody(ConnectionBody.SUB_FRIEND, u1, bch1, 0, new byte[32]), t);
// friend/unfriend как было, но тоже по CONNECTION линии
{
var ln = st2.nextLine(LineIndex.CONNECTION);
sender2.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_FRIEND,
u1, bch1, 0, new byte[32]
), t);
}
sender1.send(new UserParamBody("Anna", "Gareeva"), t);
sender1.send(new ConnectionBody(ConnectionBody.SUB_FRIEND, u2, bch2, 0, new byte[32]), t);
// user1 param + friend to u2
{
var ln = st1.nextLine(LineIndex.USER_PARAM);
sender1.send(new UserParamBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
"Anna", "Gareeva"
), t);
}
{
var ln = st1.nextLine(LineIndex.CONNECTION);
sender1.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_FRIEND,
u2, bch2, 0, new byte[32]
), t);
}
sender2.send(new ConnectionBody(ConnectionBody.SUB_UNFRIEND, u1, bch1, 0, new byte[32]), t);
{
var ln = st2.nextLine(LineIndex.CONNECTION);
sender2.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_UNFRIEND,
u1, bch1, 0, new byte[32]
), t);
}
r.ok("IT_03 сценарий блоков выполнен");