мелкие исправления. Убрал оставшиеся странные связи линии
This commit is contained in:
AidarKC 2026-01-13 17:54:10 +03:00
parent 9cf6fabe64
commit 5fe41c7656
4 changed files with 116 additions and 112 deletions

View File

@ -1,17 +1,17 @@
package blockchain;
/**
* LineIndex канонические номера линий блокчейна.
*
* Линия = независимая последовательность блоков внутри одного блокчейна.
*/
public final class LineIndex {
private LineIndex() {}
public static final short HEADER = 0; // genesis / идентификация
public static final short TEXT = 1; // сообщения да надо
public static final short REACTION = 2; // реакции не надо
public static final short CONNECTION = 3; // связи (friend/contact/follow) да надо
public static final short USER_PARAM = 4; // параметры профиля да надо
}
//package blockchain;
//
///**
// * LineIndex канонические номера линий блокчейна.
// *
// * Линия = независимая последовательность блоков внутри одного блокчейна.
// */
//public final class LineIndex {
//
// private LineIndex() {}
//
// public static final short HEADER = 0; // genesis / идентификация
// public static final short TEXT = 1; // сообщения да надо
// public static final short REACTION = 2; // реакции не надо
// public static final short CONNECTION = 3; // связи (friend/contact/follow) да надо
// public static final short USER_PARAM = 4; // параметры профиля да надо
//}

View File

@ -19,6 +19,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
* - block хранит только preimage + signature
* - hash32 вычисляется как sha256(preimage)
* - signature = Ed25519.sign(hash32)
*
* ВАЖНО:
* - Линии (prevLine/thisLine) по ТЗ нужны только для TEXT/CONNECTION/USER_PARAM.
* - Здесь НЕТ обращения к blockchain.LineIndex.
*/
public final class AddBlockSender {
@ -38,6 +42,7 @@ public final class AddBlockSender {
this.blockchainName = blockchainName;
this.loginPrivKey = (loginPrivKey == null ? null : loginPrivKey.clone());
if (this.ws == null) throw new IllegalArgumentException("ws == null");
if (this.state == null) throw new IllegalArgumentException("state == null");
if (this.loginPrivKey == null) throw new IllegalArgumentException("loginPrivKey == null");
}
@ -97,7 +102,7 @@ public final class AddBlockSender {
String serverLastHash = JsonMini.extractPayloadString(resp, "serverLastBlockHash");
if (serverLastHash == null) {
// на случай старого имени, но по твоей просьбе мы на это больше не опираемся
// на всякий случай, но ты говорил старое не поддерживаем оставил мягко
serverLastHash = JsonMini.extractPayloadString(resp, "serverLastGlobalHash");
}
@ -113,13 +118,13 @@ 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 (для nextLine())
// если это line-body обновим thisLineNumber в state (для nextLineByType())
if (body instanceof BodyHasLine hl) {
short lineIndex = lineIndexByType(type);
if (lineIndex != -1) {
state.applyThisLineNumber(lineIndex, hl.thisLineNumber());
if (ChainState.isLineType(type)) {
state.applyThisLineNumberByType(type, hl.thisLineNumber());
}
}
@ -219,15 +224,4 @@ public final class AddBlockSender {
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;
};
}
}

View File

@ -1,29 +1,36 @@
package test.it.blockchain;
import blockchain.LineIndex;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* ChainState состояние цепочки + состояние линий (только тех, где они нужны):
* ChainState состояние глобальной цепочки + состояние линий (только тех, где они нужны).
*
* Глобальная цепочка:
* - lastBlockNumber / lastBlockHashHex
* - map blockNumber -> hash32 (для ссылок reply/edit/reaction)
*
* Линии (по ТЗ нужны):
* - TEXT (1)
* - CONNECTION (3)
* - USER_PARAM (4)
* Линии по ТЗ нужны только для:
* - TEXT (type=1)
* - CONNECTION (type=3)
* - USER_PARAM (type=4)
*
* prevLineNumber по ТЗ это GLOBAL blockNumber предыдущего блока линии.
* thisLineNumber внутренний номер линии (мы ведём локально: 1,2,3...)
*
* ВАЖНО:
* - Здесь НЕТ обращения к blockchain.LineIndex.
* - Линии адресуются по msg_type (type).
*/
public final class ChainState {
public static final int LINES_MAX = 8;
// какие msg_type имеют линейную цепочку по ТЗ
public static final short TYPE_HEADER = 0;
public static final short TYPE_TEXT = 1;
public static final short TYPE_REACTION = 2;
public static final short TYPE_CONNECTION = 3;
public static final short TYPE_USER_PARAM = 4;
private static final byte[] ZERO32 = new byte[32];
private static final String ZERO64 = "0".repeat(64);
@ -35,17 +42,34 @@ public final class ChainState {
// header (block#0)
private byte[] headerHash32 = null;
// 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 (внутренний)
/**
* line state per TYPE (только для TEXT/CONNECTION/USER_PARAM):
* - lastGlobalNumber: последний GLOBAL blockNumber в линии
* - lastHashHex: hash последнего блока линии
* - lastThisLineNumber: последний thisLineNumber (внутренний)
*/
private static final class LineState {
int lastGlobalNumber = -1;
String lastHashHex = "";
int lastThisLineNumber = 0;
void reset() {
lastGlobalNumber = -1;
lastHashHex = "";
lastThisLineNumber = 0;
}
}
private final LineState textLine = new LineState();
private final LineState connectionLine = new LineState();
private final LineState userParamLine = new LineState();
private final Map<Integer, byte[]> hash32ByNumber = new HashMap<>();
public ChainState() {
Arrays.fill(lineLastGlobalNumber, -1);
Arrays.fill(lineLastHashHex, "");
Arrays.fill(lineLastThisLineNumber, 0);
textLine.reset();
connectionLine.reset();
userParamLine.reset();
}
// -------------------- global getters --------------------
@ -89,30 +113,33 @@ public final class ChainState {
}
}
/** Следующие line-поля для указанной линии (только TEXT/CONNECTION/USER_PARAM). */
public NextLine nextLine(short lineIndex) {
checkLine(lineIndex);
if (!isLineUsed(lineIndex)) {
throw new IllegalArgumentException("Line " + lineIndex + " не используется для BodyHasLine по ТЗ");
/** Является ли 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). */
public NextLine nextLineByType(short type) {
if (!isLineType(type)) {
throw new IllegalArgumentException("Type " + (type & 0xFFFF) + " не использует line-поля по ТЗ");
}
if (!hasHeader()) {
throw new IllegalStateException("Нельзя формировать line-поля до HEADER (нет headerHash32)");
}
int lastGlobal = lineLastGlobalNumber[lineIndex];
int lastThis = lineLastThisLineNumber[lineIndex];
LineState ls = lineStateByType(type);
if (lastGlobal == -1) {
if (ls.lastGlobalNumber == -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 + "] пуст, но lastGlobal!=-1");
if (ls.lastHashHex == null || ls.lastHashHex.isBlank()) {
throw new IllegalStateException("LineState.lastHashHex пуст, но lastGlobalNumber!=-1 (type=" + (type & 0xFFFF) + ")");
}
return new NextLine(lastGlobal, hexToBytes32(lastHex), lastThis + 1);
return new NextLine(ls.lastGlobalNumber, hexToBytes32(ls.lastHashHex), ls.lastThisLineNumber + 1);
}
// -------------------- apply --------------------
@ -140,52 +167,32 @@ public final class ChainState {
hash32ByNumber.put(blockNumber, hash32.clone());
// обновляем line-state только для линий, которые "надо" по ТЗ
short lineIndex = lineIndexByType(type);
if (lineIndex != -1 && isLineUsed(lineIndex)) {
lineLastGlobalNumber[lineIndex] = blockNumber;
lineLastHashHex[lineIndex] = hex64;
// thisLineNumber мы берём из тела, но здесь его нет.
// Поэтому thisLineNumber должен обновляться там, где формируются тела (в тестах),
// либо AddBlockSender может прокинуть его отдельно.
// Чтобы не дублировать контракт здесь оставляем как есть.
// обновляем line-state только если этот type по ТЗ линейный
if (isLineType(type)) {
LineState ls = lineStateByType(type);
ls.lastGlobalNumber = blockNumber;
ls.lastHashHex = hex64;
// thisLineNumber обновляется отдельным вызовом (см. applyThisLineNumberByType)
}
}
/** В тестах удобно явно обновлять thisLineNumber после успешной отправки line-body. */
public void applyThisLineNumber(short lineIndex, int thisLineNumber) {
checkLine(lineIndex);
if (!isLineUsed(lineIndex)) return;
lineLastThisLineNumber[lineIndex] = thisLineNumber;
public void applyThisLineNumberByType(short type, int thisLineNumber) {
if (!isLineType(type)) return;
LineState ls = lineStateByType(type);
ls.lastThisLineNumber = thisLineNumber;
}
// -------------------- mapping --------------------
/** По type блока определяем lineIndex. Reaction line по твоему ТЗ "не надо". */
private static short lineIndexByType(short type) {
private LineState lineStateByType(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
case TYPE_TEXT -> textLine;
case TYPE_CONNECTION -> connectionLine;
case TYPE_USER_PARAM -> userParamLine;
default -> throw new IllegalArgumentException("Type " + t + " не имеет LineState по ТЗ");
};
}
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) {
throw new IllegalArgumentException("lineIndex must be 0.." + (LINES_MAX - 1));
}
}
// -------------------- utils --------------------
private static byte[] hexToBytes32(String hex) {

View File

@ -1,6 +1,5 @@
package test.it.cases;
import blockchain.LineIndex;
import blockchain.body.*;
import blockchain.MsgSubType;
import test.it.blockchain.AddBlockSender;
@ -16,6 +15,10 @@ import static org.junit.jupiter.api.Assertions.*;
/**
* IT_03_AddBlock_NoAuth обновлён под новый формат блоков (ТЗ).
*
* ВАЖНО:
* - НЕТ обращения к blockchain.LineIndex (можно удалить LineIndex.java).
* - Линии берём через ChainState.nextLineByType(TYPE_...).
*/
public class IT_03_AddBlock_NoAuth {
@ -59,7 +62,7 @@ public class IT_03_AddBlock_NoAuth {
// TEXT_NEW x3 (с line)
{
var ln = st1.nextLine(LineIndex.TEXT);
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",
@ -67,7 +70,7 @@ public class IT_03_AddBlock_NoAuth {
), t);
}
{
var ln = st1.nextLine(LineIndex.TEXT);
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",
@ -75,7 +78,7 @@ public class IT_03_AddBlock_NoAuth {
), t);
}
{
var ln = st1.nextLine(LineIndex.TEXT);
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",
@ -92,7 +95,7 @@ public class IT_03_AddBlock_NoAuth {
// TEXT_REPLY x2 (с line + target)
{
var ln = st1.nextLine(LineIndex.TEXT);
var ln = st1.nextLineByType(ChainState.TYPE_TEXT);
sender1.send(new TextBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.TEXT_REPLY,
"Reply to TEXT#1",
@ -100,7 +103,7 @@ public class IT_03_AddBlock_NoAuth {
), t);
}
{
var ln = st1.nextLine(LineIndex.TEXT);
var ln = st1.nextLineByType(ChainState.TYPE_TEXT);
sender1.send(new TextBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.TEXT_REPLY,
"Reply to TEXT#3",
@ -114,7 +117,7 @@ public class IT_03_AddBlock_NoAuth {
// TEXT_EDIT x3 (с line + target)
{
var ln = st1.nextLine(LineIndex.TEXT);
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",
@ -122,7 +125,7 @@ public class IT_03_AddBlock_NoAuth {
), t);
}
{
var ln = st1.nextLine(LineIndex.TEXT);
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",
@ -130,7 +133,7 @@ public class IT_03_AddBlock_NoAuth {
), t);
}
{
var ln = st1.nextLine(LineIndex.TEXT);
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",
@ -149,7 +152,7 @@ public class IT_03_AddBlock_NoAuth {
// USER_PARAM (с line)
{
var ln = st2.nextLine(LineIndex.USER_PARAM);
var ln = st2.nextLineByType(ChainState.TYPE_USER_PARAM);
sender2.send(new UserParamBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
"Anya", "Amsterdam, Example street 10"
), t);
@ -171,7 +174,7 @@ public class IT_03_AddBlock_NoAuth {
// u1 -> follow u2
{
var ln = st1.nextLine(LineIndex.CONNECTION);
var ln = st1.nextLineByType(ChainState.TYPE_CONNECTION);
sender1.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_FOLLOW,
u2, bch2, 0, new byte[32]
@ -180,7 +183,7 @@ public class IT_03_AddBlock_NoAuth {
// u1 -> follow u3
{
var ln = st1.nextLine(LineIndex.CONNECTION);
var ln = st1.nextLineByType(ChainState.TYPE_CONNECTION);
sender1.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_FOLLOW,
u3, bch3, 0, new byte[32]
@ -189,7 +192,7 @@ public class IT_03_AddBlock_NoAuth {
// u2 -> follow u1
{
var ln = st2.nextLine(LineIndex.CONNECTION);
var ln = st2.nextLineByType(ChainState.TYPE_CONNECTION);
sender2.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_FOLLOW,
u1, bch1, 0, new byte[32]
@ -198,7 +201,7 @@ public class IT_03_AddBlock_NoAuth {
// friend/unfriend как было, но тоже по CONNECTION линии
{
var ln = st2.nextLine(LineIndex.CONNECTION);
var ln = st2.nextLineByType(ChainState.TYPE_CONNECTION);
sender2.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_FRIEND,
u1, bch1, 0, new byte[32]
@ -207,13 +210,13 @@ public class IT_03_AddBlock_NoAuth {
// user1 param + friend to u2
{
var ln = st1.nextLine(LineIndex.USER_PARAM);
var ln = st1.nextLineByType(ChainState.TYPE_USER_PARAM);
sender1.send(new UserParamBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
"Anna", "Gareeva"
), t);
}
{
var ln = st1.nextLine(LineIndex.CONNECTION);
var ln = st1.nextLineByType(ChainState.TYPE_CONNECTION);
sender1.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_FRIEND,
u2, bch2, 0, new byte[32]
@ -221,7 +224,7 @@ public class IT_03_AddBlock_NoAuth {
}
{
var ln = st2.nextLine(LineIndex.CONNECTION);
var ln = st2.nextLineByType(ChainState.TYPE_CONNECTION);
sender2.send(new ConnectionBody(ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
MsgSubType.CONNECTION_UNFRIEND,
u1, bch1, 0, new byte[32]