SHiNE-server/shine-server-blockchain/src/main/java/blockchain/body/ReactionBody.java
AidarKC 06c77b1c1f 07 01 25
refactor: перевели хэши на BLOB и добавили поля block_hash / block_signature / edited_by_block_global_number

и главное добавили тип блока изменение сообщение и сслку на последнее изменение в табл блокс
2026-01-07 19:58:50 +03:00

200 lines
6.9 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package blockchain.body;
import blockchain.LineIndex;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Objects;
/**
* ReactionBody — type=2, version=1.
*
* Формат bodyBytes (BigEndian):
* [2] type=2
* [2] ver=1
*
* [2] subType (uint16) — подтип реакции
* 1 = LIKE (лайк)
*
* [1] toBlockchainNameLen (uint8)
* [N] toBlockchainName UTF-8
* [4] toBlockGlobalNumber (int32)
* [32] toBlockHash32 (raw 32 bytes)
*
* ЛИНИЯ:
* - строго lineIndex=2
*/
public final class ReactionBody implements BodyRecord, BodyHasTarget {
public static final short TYPE = 2;
public static final short VER = 1;
public static final int KEY = ((TYPE & 0xFFFF) << 16) | (VER & 0xFFFF);
// subType:
public static final short SUB_LIKE = 1;
public final short subType;
public final String toBlockchainName;
public final int toBlockGlobalNumber;
public final byte[] toBlockHash32;
/** Десериализация из полного bodyBytes (включая type/version/subType). */
public ReactionBody(byte[] bodyBytes) {
Objects.requireNonNull(bodyBytes, "bodyBytes == null");
// минимум: type[2]+ver[2]+subType[2]+nameLen[1]+name[1]+global[4]+hash[32]
if (bodyBytes.length < 2 + 2 + 2 + 1 + 1 + 4 + 32) {
throw new IllegalArgumentException("ReactionBody too short");
}
ByteBuffer bb = ByteBuffer.wrap(bodyBytes).order(ByteOrder.BIG_ENDIAN);
short type = bb.getShort();
short ver = bb.getShort();
if (type != TYPE || ver != VER)
throw new IllegalArgumentException("Not ReactionBody: type=" + type + " ver=" + ver);
this.subType = bb.getShort();
if (this.subType != SUB_LIKE) {
throw new IllegalArgumentException("Bad reaction subType: " + (this.subType & 0xFFFF));
}
int nameLen = Byte.toUnsignedInt(bb.get());
if (nameLen <= 0) throw new IllegalArgumentException("toBlockchainNameLen is 0");
if (bb.remaining() < nameLen + 4 + 32)
throw new IllegalArgumentException("ReactionBody payload too short");
byte[] nameBytes = new byte[nameLen];
bb.get(nameBytes);
this.toBlockchainName = new String(nameBytes, StandardCharsets.UTF_8);
this.toBlockGlobalNumber = bb.getInt();
this.toBlockHash32 = new byte[32];
bb.get(this.toBlockHash32);
// запрет мусора в конце
if (bb.remaining() != 0) {
throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining());
}
}
/** Создание “вручную”. */
public ReactionBody(short subType,
String toBlockchainName,
int toBlockGlobalNumber,
byte[] toBlockHash32) {
Objects.requireNonNull(toBlockchainName, "toBlockchainName == null");
Objects.requireNonNull(toBlockHash32, "toBlockHash32 == null");
if (subType != SUB_LIKE)
throw new IllegalArgumentException("Unknown reaction subType: " + (subType & 0xFFFF));
if (toBlockchainName.isBlank()) throw new IllegalArgumentException("toBlockchainName is blank");
if (toBlockGlobalNumber < 0) throw new IllegalArgumentException("toBlockGlobalNumber < 0");
if (toBlockHash32.length != 32) throw new IllegalArgumentException("toBlockHash32 != 32");
this.subType = subType;
this.toBlockchainName = toBlockchainName;
this.toBlockGlobalNumber = toBlockGlobalNumber;
this.toBlockHash32 = Arrays.copyOf(toBlockHash32, 32);
}
@Override public short type() { return TYPE; }
@Override public short version() { return VER; }
@Override public short subType() { return subType; }
@Override
public short expectedLineIndex() {
return LineIndex.REACTION;
}
@Override
public ReactionBody check() {
if (subType != SUB_LIKE)
throw new IllegalArgumentException("Bad reaction subType: " + (subType & 0xFFFF));
if (toBlockchainName == null || toBlockchainName.isBlank())
throw new IllegalArgumentException("toBlockchainName is blank");
if (toBlockGlobalNumber < 0)
throw new IllegalArgumentException("toBlockGlobalNumber < 0");
if (toBlockHash32 == null || toBlockHash32.length != 32)
throw new IllegalArgumentException("toBlockHash32 invalid");
return this;
}
@Override
public byte[] toBytes() {
byte[] nameBytes = toBlockchainName.getBytes(StandardCharsets.UTF_8);
if (nameBytes.length == 0 || nameBytes.length > 255)
throw new IllegalArgumentException("toBlockchainName utf8 len must be 1..255");
// type[2]+ver[2]+subType[2] + nameLen[1]+name[N] + global[4] + hash[32]
int cap = 2 + 2 + 2 + 1 + nameBytes.length + 4 + 32;
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
bb.putShort(TYPE);
bb.putShort(VER);
bb.putShort(subType);
bb.put((byte) nameBytes.length);
bb.put(nameBytes);
bb.putInt(toBlockGlobalNumber);
bb.put(toBlockHash32);
return bb.array();
}
@Override
public String toString() {
String st = (subType == SUB_LIKE) ? "LIKE (1)" : "UNKNOWN";
return """
ReactionBody {
тип записи : REACTION (type=2, ver=1)
ожидаемая линия : 2
subType : %s
целевой блокчейн : "%s"
globalNumber цели : %d
hash цели (hex) : %s
}
""".formatted(
st,
toBlockchainName,
toBlockGlobalNumber,
toBlockHashHex()
);
}
public String toBlockHashHex() {
char[] HEX = "0123456789abcdef".toCharArray();
char[] out = new char[64];
for (int i = 0; i < 32; i++) {
int v = toBlockHash32[i] & 0xFF;
out[i * 2] = HEX[v >>> 4];
out[i * 2 + 1] = HEX[v & 0x0F];
}
return new String(out);
}
/* ===================================================================== */
/* ====================== BodyHasTarget контракт ========================= */
/* ===================================================================== */
/** В самом формате ReactionBody login цели не хранится => null. */
@Override public String toLogin() { return null; }
@Override public String toBchName() { return toBlockchainName; }
@Override public Integer toBlockGlobalNumber() { return toBlockGlobalNumber; }
@Override public byte[] toBlockHasheBytes() { return toBlockHash32; }
}