Доделал типы сообщений посты в линии и едиты на них.ответы на них
И ответы в другие блокчейны

(Все тесты тесты проходят)
This commit is contained in:
AidarKC
2026-01-15 18:55:03 +03:00
parent b69075cbac
commit 376d42cd79
8 changed files with 863 additions and 362 deletions
@@ -21,8 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
* - signature = Ed25519.sign(hash32)
*
* ВАЖНО:
* - Линии (prevLine/thisLine) по ТЗ нужны только для TEXT/CONNECTION/USER_PARAM.
* - Здесь НЕТ обращения к blockchain.LineIndex.
* - Линии по ТЗ ведём на стороне сервера/БД (триггеры), а в тестах считаем локально.
*/
public final class AddBlockSender {
@@ -75,7 +74,6 @@ public final class AddBlockSender {
byte[] bodyBytes = body.toBytes();
// 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);
@@ -102,7 +100,6 @@ public final class AddBlockSender {
String serverLastHash = JsonMini.extractPayloadString(resp, "serverLastBlockHash");
if (serverLastHash == null) {
// на всякий случай, но ты говорил старое не поддерживаем — оставил мягко
serverLastHash = JsonMini.extractPayloadString(resp, "serverLastGlobalHash");
}
@@ -118,15 +115,7 @@ public final class AddBlockSender {
assertEquals(localHashHex, serverLastHash, op + ": serverLastBlockHash must match local hash");
// фиксируем в state глобальную цепочку + (если нужно) line-state по TYPE
state.applyAppendedBlock(blockNumber, entry.getHash32(), isHeader, type);
// если это line-body — обновим thisLineNumber в state (для nextLineByType())
if (body instanceof BodyHasLine hl) {
if (ChainState.isLineType(type)) {
state.applyThisLineNumberByType(type, hl.thisLineNumber());
}
}
state.applyAppendedBlock(blockNumber, entry.getHash32(), isHeader, type, body);
if (TestConfig.DEBUG()) TestLog.info(op + ": state updated");
}
@@ -174,6 +163,7 @@ public final class AddBlockSender {
private static short typeOf(BodyRecord body) {
if (body instanceof HeaderBody) return HeaderBody.TYPE;
if (body instanceof CreateChannelBody) return CreateChannelBody.TYPE;
if (body instanceof TextBody) return TextBody.TYPE;
if (body instanceof ReactionBody) return ReactionBody.TYPE;
if (body instanceof ConnectionBody) return ConnectionBody.TYPE;
@@ -183,6 +173,7 @@ public final class AddBlockSender {
private static short subTypeOf(BodyRecord body) {
if (body instanceof HeaderBody hb) return hb.subType;
if (body instanceof CreateChannelBody cb) return cb.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;
@@ -192,6 +183,7 @@ public final class AddBlockSender {
private static short versionOf(BodyRecord body) {
if (body instanceof HeaderBody hb) return hb.version;
if (body instanceof CreateChannelBody cb) return cb.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;
+163 -63
View File
@@ -1,32 +1,34 @@
package test.it.blockchain;
import java.util.Arrays;
import blockchain.MsgSubType;
import blockchain.body.BodyRecord;
import blockchain.body.BodyHasLine;
import blockchain.body.CreateChannelBody;
import blockchain.body.TextBody;
import java.util.HashMap;
import java.util.Map;
/**
* ChainState — состояние глобальной цепочки + состояние линий (только тех, где они нужны).
* ChainState — состояние глобальной цепочки + состояние линий.
*
* Глобальная цепочка:
* - lastBlockNumber / lastBlockHashHex
* - map blockNumber -> hash32 (для ссылок reply/edit/reaction)
* - map blockNumber -> hash32
*
* Линии по ТЗ нужны только для:
* - TEXT (type=1)
* - CONNECTION (type=3)
* - USER_PARAM (type=4)
* Линии:
* - TECH (type=0): только CREATE_CHANNEL (hasLine), root = HEADER
* - TEXT (type=1): линии каналов, root = HEADER (канал "0") или CREATE_CHANNEL (канал "X")
* - CONNECTION (type=3): одна линия
* - USER_PARAM (type=4): одна линия
*
* prevLineNumber по ТЗ — это GLOBAL blockNumber предыдущего блока линии.
* thisLineNumber — внутренний номер линии (мы ведём локально: 1,2,3...)
*
* ВАЖНО:
* - Здесь НЕТ обращения к blockchain.LineIndex.
* - Линии адресуются по msg_type (type).
* Важно:
* - prevLineNumber — это GLOBAL blockNumber предыдущего блока линии.
* - thisLineNumber — внутренний номер линии (для постов: 0,1,2...; для тех-линии: 1,2,3...)
*/
public final class ChainState {
// какие msg_type имеют линейную цепочку по ТЗ
public static final short TYPE_HEADER = 0;
public static final short TYPE_TECH = 0; // header/create_channel
public static final short TYPE_TEXT = 1;
public static final short TYPE_REACTION = 2;
public static final short TYPE_CONNECTION = 3;
@@ -42,13 +44,25 @@ public final class ChainState {
// header (block#0)
private byte[] headerHash32 = null;
/**
* line state per TYPE (только для TEXT/CONNECTION/USER_PARAM):
* - lastGlobalNumber: последний GLOBAL blockNumber в линии
* - lastHashHex: hash последнего блока линии
* - lastThisLineNumber: последний thisLineNumber (внутренний)
*/
private static final class LineState {
private final Map<Integer, byte[]> hash32ByNumber = new HashMap<>();
// ---------- TECH line state ----------
private static final class TechLineState {
int lastGlobalNumber = -1; // последний TECH-блок (HEADER или CREATE_CHANNEL)
String lastHashHex = "";
int lastThisLineNumber = 0; // 0 у HEADER (логически), дальше 1,2,3...
void reset() {
lastGlobalNumber = -1;
lastHashHex = "";
lastThisLineNumber = 0;
}
}
private final TechLineState techLine = new TechLineState();
// ---------- CONNECTION/USER_PARAM line state ----------
private static final class SimpleLineState {
int lastGlobalNumber = -1;
String lastHashHex = "";
int lastThisLineNumber = 0;
@@ -60,14 +74,32 @@ public final class ChainState {
}
}
private final LineState textLine = new LineState();
private final LineState connectionLine = new LineState();
private final LineState userParamLine = new LineState();
private final SimpleLineState connectionLine = new SimpleLineState();
private final SimpleLineState userParamLine = new SimpleLineState();
private final Map<Integer, byte[]> hash32ByNumber = new HashMap<>();
// ---------- TEXT channels ----------
public static final class ChannelLineState {
final int rootBlockNumber;
final String rootHashHex;
int lastGlobalNumber;
String lastHashHex;
int lastThisLineNumber; // перед первым постом = -1, чтобы первый был 0
ChannelLineState(int rootBlockNumber, String rootHashHex) {
this.rootBlockNumber = rootBlockNumber;
this.rootHashHex = rootHashHex;
this.lastGlobalNumber = rootBlockNumber;
this.lastHashHex = rootHashHex;
this.lastThisLineNumber = -1;
}
}
// rootBlockNumber -> state
private final Map<Integer, ChannelLineState> textChannels = new HashMap<>();
public ChainState() {
textLine.reset();
techLine.reset();
connectionLine.reset();
userParamLine.reset();
}
@@ -113,38 +145,72 @@ public final class ChainState {
}
}
/** Является ли type "линейным" по ТЗ (т.е. нужно вести prevLine/thisLine). */
public static boolean isLineType(short type) {
int t = type & 0xFFFF;
return t == TYPE_TEXT || t == TYPE_CONNECTION || t == TYPE_USER_PARAM;
}
/** Следующие line-поля для указанного TYPE (только TEXT/CONNECTION/USER_PARAM). */
/** Следующие line-поля для TECH/CONNECTION/USER_PARAM. */
public NextLine nextLineByType(short type) {
if (!isLineType(type)) {
throw new IllegalArgumentException("Type " + (type & 0xFFFF) + " не использует line-поля по ТЗ");
}
if (!hasHeader()) {
throw new IllegalStateException("Нельзя формировать line-поля до HEADER (нет headerHash32)");
}
LineState ls = lineStateByType(type);
int t = type & 0xFFFF;
if (t == TYPE_TECH) {
// tech-line: prev = последний TECH; первый CREATE_CHANNEL -> prev = HEADER
if (techLine.lastGlobalNumber == -1) {
// после HEADER мы должны инициализировать techLine (делаем в applyHeader)
throw new IllegalStateException("TECH line is not initialized yet");
}
return new NextLine(techLine.lastGlobalNumber, hexToBytes32(techLine.lastHashHex), techLine.lastThisLineNumber + 1);
}
if (t == TYPE_CONNECTION) {
return nextSimpleLine(connectionLine);
}
if (t == TYPE_USER_PARAM) {
return nextSimpleLine(userParamLine);
}
throw new IllegalArgumentException("Type " + t + " не поддерживает nextLineByType()");
}
private NextLine nextSimpleLine(SimpleLineState ls) {
if (ls.lastGlobalNumber == -1) {
// первый блок линии ссылается на HEADER (block#0)
return new NextLine(0, headerHash32.clone(), 1);
}
if (ls.lastHashHex == null || ls.lastHashHex.isBlank()) {
throw new IllegalStateException("LineState.lastHashHex пуст, но lastGlobalNumber!=-1 (type=" + (type & 0xFFFF) + ")");
throw new IllegalStateException("LineState.lastHashHex пуст, но lastGlobalNumber!=-1");
}
return new NextLine(ls.lastGlobalNumber, hexToBytes32(ls.lastHashHex), ls.lastThisLineNumber + 1);
}
/** Следующие line-поля для TEXT-канала по rootBlockNumber. */
public NextLine nextTextLineByRoot(int rootBlockNumber) {
if (!hasHeader()) throw new IllegalStateException("No HEADER");
ChannelLineState cs = textChannels.get(rootBlockNumber);
if (cs == null) throw new IllegalStateException("Unknown TEXT channel rootBlockNumber=" + rootBlockNumber);
return new NextLine(
cs.lastGlobalNumber,
hexToBytes32(cs.lastHashHex),
cs.lastThisLineNumber + 1
);
}
/** Зарегистрировать новый канал TEXT по root = CREATE_CHANNEL block. */
public void registerTextChannelRoot(int rootBlockNumber, byte[] rootHash32) {
if (rootBlockNumber <= 0) throw new IllegalArgumentException("rootBlockNumber must be > 0 for custom channel");
if (rootHash32 == null || rootHash32.length != 32) throw new IllegalArgumentException("rootHash32 invalid");
textChannels.put(rootBlockNumber, new ChannelLineState(rootBlockNumber, bytesToHex64(rootHash32)));
}
/** root канала "0" (по умолчанию) — это HEADER block#0. */
public int rootChannel0() {
return 0;
}
// -------------------- apply --------------------
public void applyAppendedBlock(int blockNumber, byte[] hash32, boolean isHeader, short type) {
public void applyAppendedBlock(int blockNumber, byte[] hash32, boolean isHeader, short type, BodyRecord body) {
if (hash32 == null || hash32.length != 32) {
throw new IllegalArgumentException("hash32 must be 32 bytes");
}
@@ -167,30 +233,64 @@ public final class ChainState {
hash32ByNumber.put(blockNumber, hash32.clone());
// обновляем line-state только если этот type по ТЗ линейный
if (isLineType(type)) {
LineState ls = lineStateByType(type);
ls.lastGlobalNumber = blockNumber;
ls.lastHashHex = hex64;
// thisLineNumber обновляется отдельным вызовом (см. applyThisLineNumberByType)
// ---- init after HEADER ----
if (isHeader) {
// TECH line root = HEADER
techLine.lastGlobalNumber = 0;
techLine.lastHashHex = hex64;
techLine.lastThisLineNumber = 0;
// TEXT channel "0" root = HEADER, первый пост будет thisLineNumber=0
textChannels.put(0, new ChannelLineState(0, hex64));
return;
}
int t = type & 0xFFFF;
// ---- TECH (CREATE_CHANNEL) ----
if (t == TYPE_TECH && body instanceof CreateChannelBody ccb) {
techLine.lastGlobalNumber = blockNumber;
techLine.lastHashHex = hex64;
techLine.lastThisLineNumber = ccb.thisLineNumber;
return;
}
// ---- CONNECTION / USER_PARAM ----
if (t == TYPE_CONNECTION && body instanceof BodyHasLine hlc) {
connectionLine.lastGlobalNumber = blockNumber;
connectionLine.lastHashHex = hex64;
connectionLine.lastThisLineNumber = hlc.thisLineNumber();
return;
}
if (t == TYPE_USER_PARAM && body instanceof BodyHasLine hlu) {
userParamLine.lastGlobalNumber = blockNumber;
userParamLine.lastHashHex = hex64;
userParamLine.lastThisLineNumber = hlu.thisLineNumber();
return;
}
// ---- TEXT channels (POST/EDIT_POST) ----
if (t == TYPE_TEXT && body instanceof TextBody tb) {
if (tb.isLineMessage()) {
// ищем канал по совпадению prevLineNumber с lastGlobalNumber канала
ChannelLineState channel = findTextChannelByLastGlobal(tb.prevLineNumber);
if (channel == null) {
throw new IllegalStateException("TEXT line message prevLineNumber=" + tb.prevLineNumber + " не привязан ни к одному каналу (канал root не зарегистрирован?)");
}
channel.lastGlobalNumber = blockNumber;
channel.lastHashHex = hex64;
channel.lastThisLineNumber = tb.thisLineNumber;
}
}
}
/** В тестах удобно явно обновлять thisLineNumber после успешной отправки line-body. */
public void applyThisLineNumberByType(short type, int thisLineNumber) {
if (!isLineType(type)) return;
LineState ls = lineStateByType(type);
ls.lastThisLineNumber = thisLineNumber;
}
private LineState lineStateByType(short type) {
int t = type & 0xFFFF;
return switch (t) {
case TYPE_TEXT -> textLine;
case TYPE_CONNECTION -> connectionLine;
case TYPE_USER_PARAM -> userParamLine;
default -> throw new IllegalArgumentException("Type " + t + " не имеет LineState по ТЗ");
};
private ChannelLineState findTextChannelByLastGlobal(int prevLineNumber) {
for (ChannelLineState cs : textChannels.values()) {
if (cs.lastGlobalNumber == prevLineNumber) return cs;
}
return null;
}
// -------------------- utils --------------------
@@ -1,7 +1,7 @@
package test.it.cases;
import blockchain.body.*;
import blockchain.MsgSubType;
import blockchain.body.*;
import test.it.blockchain.AddBlockSender;
import test.it.blockchain.ChainState;
import test.it.utils.TestConfig;
@@ -14,12 +14,12 @@ import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;
/**
* IT_03_AddBlock_NoAuth — обновлён под новый формат блоков (ТЗ).
* IT_03_AddBlock_NoAuth — сценарий блоков (новый формат + каналы).
*
* ВАЖНО:
* - НЕТ обращения к blockchain.LineIndex (можно удалить LineIndex.java).
* - Линии берём через ChainState.nextLineByType(TYPE_...).
* - ConnectionBody: toLogin в байтах НЕ хранится, вычисляется из toBlockchainName.
* - TECH: Header + CreateChannel идут по тех-линии (hasLine у CreateChannel).
* - TEXT: посты в каналах — отдельные линии, root = Header(канал "0") или CreateChannel(канал "X").
* - REPLY (subType=20): без линии, target может указывать на чужой блокчейн, и ОБЯЗАТЕЛЬНО содержит toBlockNumber+toBlockHash32.
*/
public class IT_03_AddBlock_NoAuth {
@@ -54,184 +54,123 @@ public class IT_03_AddBlock_NoAuth {
);
}
// =========================
// USER1
// =========================
ChainState st1 = new ChainState();
AddBlockSender sender1 = new AddBlockSender(ws, st1, u1, bch1, TestConfig.getBlockchainPrivatKey(u1));
sender1.send(new HeaderBody(u1), t);
assertTrue(st1.hasHeader());
// TEXT_NEW x3 (с line)
// канал "0" (root=HEADER) — по умолчанию существует
int root0 = st1.rootChannel0();
// POST в канал "0"
{
var ln = st1.nextLineByType(ChainState.TYPE_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.nextLineByType(ChainState.TYPE_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.nextLineByType(ChainState.TYPE_TEXT);
sender1.send(new TextBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.TEXT_NEW,
"Hello #3 (NEW) from IT_03 test",
var ln = st1.nextTextLineByRoot(root0);
sender1.send(new TextBody(
MsgSubType.TEXT_POST,
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
"U1: story/post in channel 0",
null, null, null
), t);
}
byte[] text1Hash = st1.getHash32(1);
byte[] text2Hash = st1.getHash32(2);
byte[] text3Hash = st1.getHash32(3);
assertNotNull(text1Hash);
assertNotNull(text2Hash);
assertNotNull(text3Hash);
int post0Block = st1.lastBlockNumber();
byte[] post0Hash = st1.getHash32(post0Block);
assertNotNull(post0Hash);
// TEXT_REPLY x2 (с line + target)
// CREATE_CHANNEL "News" (TECH line)
int newsRootBlock;
byte[] newsRootHash;
{
var ln = st1.nextLineByType(ChainState.TYPE_TEXT);
sender1.send(new TextBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.TEXT_REPLY,
"Reply to TEXT#1",
bch1, 1, text1Hash
var ln = st1.nextLineByType(ChainState.TYPE_TECH);
sender1.send(new CreateChannelBody(
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
"News"
), t);
newsRootBlock = st1.lastBlockNumber();
newsRootHash = st1.getHash32(newsRootBlock);
assertNotNull(newsRootHash);
// зарегистрируем root канала для тестового state, чтобы nextTextLineByRoot() работал
st1.registerTextChannelRoot(newsRootBlock, newsRootHash);
}
// POST #0 в канал "News"
int newsPost0Block;
byte[] newsPost0Hash;
{
var ln = st1.nextLineByType(ChainState.TYPE_TEXT);
sender1.send(new TextBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.TEXT_REPLY,
"Reply to TEXT#3",
bch1, 3, text3Hash
var ln = st1.nextTextLineByRoot(newsRootBlock);
sender1.send(new TextBody(
MsgSubType.TEXT_POST,
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
"U1: News post #0",
null, null, null
), t);
newsPost0Block = st1.lastBlockNumber();
newsPost0Hash = st1.getHash32(newsPost0Block);
assertNotNull(newsPost0Hash);
}
// POST #1 в канал "News"
{
var ln = st1.nextTextLineByRoot(newsRootBlock);
sender1.send(new TextBody(
MsgSubType.TEXT_POST,
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
"U1: News post #1",
null, null, null
), t);
}
// REACTION_LIKE x2 (без line)
sender1.send(new ReactionBody(bch1, 1, text1Hash), t);
sender1.send(new ReactionBody(bch1, 2, text2Hash), t);
// TEXT_EDIT x3 (с line + target)
// EDIT_POST (не увеличивает thisLineNumber, но является частью линии)
{
var ln = st1.nextLineByType(ChainState.TYPE_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.nextLineByType(ChainState.TYPE_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.nextLineByType(ChainState.TYPE_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
var ln = st1.nextTextLineByRoot(newsRootBlock);
// edit должен иметь thisLineNumber как у предыдущего сообщения линии (ChainState это уже даёт)
sender1.send(new TextBody(
MsgSubType.TEXT_EDIT_POST,
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
"U1: News post #0 (EDIT)",
null,
newsPost0Block,
newsPost0Hash
), t);
}
assertEquals(10, st1.lastBlockNumber(), "USER1: lastBlockNumber должен быть 10 (всего 11 блоков включая HEADER)");
// USER2
// =========================
// USER2 (ответ в чужой канал)
// =========================
ChainState st2 = new ChainState();
AddBlockSender sender2 = new AddBlockSender(ws, st2, u2, bch2, TestConfig.getBlockchainPrivatKey(u2));
sender2.send(new HeaderBody(u2), t);
assertTrue(st2.hasHeader());
// USER_PARAM (с line)
// REPLY (20): ответ на post в чужом блокчейне/канале
{
var ln = st2.nextLineByType(ChainState.TYPE_USER_PARAM);
sender2.send(new UserParamBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
"Anya", "Amsterdam, Example street 10"
sender2.send(new TextBody(
MsgSubType.TEXT_REPLY,
-1, new byte[32], -1, // для replies линии нет
"U2: reply to U1 News post #0 (cross-chain)",
bch1,
newsPost0Block,
newsPost0Hash
), t);
}
// USER3 (нужен, чтобы u1 мог подписаться на существующий блокчейн)
// =========================
// USER3 (просто чтобы оставалось как раньше)
// =========================
ChainState st3 = new ChainState();
AddBlockSender sender3 = new AddBlockSender(ws, st3, u3, bch3, TestConfig.getBlockchainPrivatKey(u3));
sender3.send(new HeaderBody(u3), t);
assertTrue(st3.hasHeader());
// -----------------------------------------------------------------
// Подписки:
// - u1 follows u2 и u3
// - u2 follows только u1
// Все CONNECTION идут по линии CONNECTION (по ТЗ "да надо")
// -----------------------------------------------------------------
// u1 -> follow u2
{
var ln = st1.nextLineByType(ChainState.TYPE_CONNECTION);
sender1.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_FOLLOW,
bch2, 0, new byte[32]
), t);
}
// u1 -> follow u3
{
var ln = st1.nextLineByType(ChainState.TYPE_CONNECTION);
sender1.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_FOLLOW,
bch3, 0, new byte[32]
), t);
}
// u2 -> follow u1
{
var ln = st2.nextLineByType(ChainState.TYPE_CONNECTION);
sender2.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_FOLLOW,
bch1, 0, new byte[32]
), t);
}
// friend/unfriend как было, но тоже по CONNECTION линии
{
var ln = st2.nextLineByType(ChainState.TYPE_CONNECTION);
sender2.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_FRIEND,
bch1, 0, new byte[32]
), t);
}
// user1 param + friend to u2
{
var ln = st1.nextLineByType(ChainState.TYPE_USER_PARAM);
sender1.send(new UserParamBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
"Anna", "Gareeva"
), t);
}
{
var ln = st1.nextLineByType(ChainState.TYPE_CONNECTION);
sender1.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_FRIEND,
bch2, 0, new byte[32]
), t);
}
{
var ln = st2.nextLineByType(ChainState.TYPE_CONNECTION);
sender2.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_UNFRIEND,
bch1, 0, new byte[32]
), t);
}
r.ok("IT_03 сценарий блоков выполнен");
} catch (Throwable e) {