refactor: перевели хэши на BLOB и добавили поля block_hash / block_signature / edited_by_block_global_number

и главное добавили тип блока изменение сообщение и сслку на последнее изменение в табл блокс
This commit is contained in:
AidarKC
2026-01-07 19:58:50 +03:00
parent 8bcaa192c5
commit 06c77b1c1f
13 changed files with 306 additions and 284 deletions
@@ -151,41 +151,40 @@ public class DatabaseInitializer {
ON ip_geo_cache (updated_at_ms);
""");
// 5. blockchain_state
// 5. blockchain_state (хэши -> BLOB NULLABLE)
st.executeUpdate("""
CREATE TABLE IF NOT EXISTS blockchain_state (
blockchain_name TEXT NOT NULL PRIMARY KEY,
login TEXT NOT NULL,
blockchain_key TEXT NOT NULL,
size_limit INTEGER NOT NULL,
file_size_bytes INTEGER NOT NULL,
last_global_number INTEGER NOT NULL,
last_global_hash TEXT NOT NULL,
last_global_hash BLOB,
updated_at_ms INTEGER NOT NULL,
line0_last_number INTEGER NOT NULL,
line0_last_hash TEXT NOT NULL,
line0_last_hash BLOB,
line1_last_number INTEGER NOT NULL,
line1_last_hash TEXT NOT NULL,
line1_last_hash BLOB,
line2_last_number INTEGER NOT NULL,
line2_last_hash TEXT NOT NULL,
line2_last_hash BLOB,
line3_last_number INTEGER NOT NULL,
line3_last_hash TEXT NOT NULL,
line3_last_hash BLOB,
line4_last_number INTEGER NOT NULL,
line4_last_hash TEXT NOT NULL,
line4_last_hash BLOB,
line5_last_number INTEGER NOT NULL,
line5_last_hash TEXT NOT NULL,
line5_last_hash BLOB,
line6_last_number INTEGER NOT NULL,
line6_last_hash TEXT NOT NULL,
line6_last_hash BLOB,
line7_last_number INTEGER NOT NULL,
line7_last_hash TEXT NOT NULL,
line7_last_hash BLOB,
FOREIGN KEY (login) REFERENCES solana_users(login)
);
""");
st.executeUpdate("""
CREATE INDEX IF NOT EXISTS idx_blockchain_state_login
ON blockchain_state (login);
@@ -196,27 +195,35 @@ public class DatabaseInitializer {
ON blockchain_state (updated_at_ms);
""");
// 6. blocks
// 6. blocks (хэши/подпись -> BLOB, + edited_by_block_global_number)
st.executeUpdate("""
CREATE TABLE IF NOT EXISTS blocks (
login TEXT NOT NULL,
bch_name TEXT NOT NULL,
block_global_number INTEGER NOT NULL,
block_global_pre_hashe TEXT NOT NULL,
block_global_pre_hashe BLOB NOT NULL,
block_line_index INTEGER NOT NULL,
block_line_number INTEGER NOT NULL,
block_line_pre_hashe TEXT NOT NULL,
block_line_pre_hashe BLOB NOT NULL,
msg_type INTEGER NOT NULL,
msg_sub_type INTEGER NOT NULL,
block_byte BLOB,
-- Ссылка на целевой блок (для reply/like/edit и т.д.)
to_login TEXT,
to_bch_name TEXT,
to_block_global_number INTEGER,
to_block_hashe TEXT,
to_block_hashe BLOB,
-- Собственные данные блока (по просьбе)
block_hash BLOB NOT NULL,
block_signature BLOB NOT NULL,
-- Последний edit, который изменил этот блок (NULL если не редактировали)
edited_by_block_global_number INTEGER,
FOREIGN KEY (login) REFERENCES solana_users(login),
FOREIGN KEY (bch_name) REFERENCES blockchain_state(blockchain_name)
@@ -233,7 +240,7 @@ public class DatabaseInitializer {
ON blocks (to_login, to_bch_name, to_block_global_number);
""");
// 7) connections_state
// 7) connections_state (to_block_hashe -> BLOB)
st.executeUpdate("""
CREATE TABLE IF NOT EXISTS connections_state (
login TEXT NOT NULL,
@@ -241,7 +248,7 @@ public class DatabaseInitializer {
to_login TEXT NOT NULL,
to_bch_name TEXT NOT NULL,
to_block_global_number INTEGER,
to_block_hashe TEXT,
to_block_hashe BLOB,
FOREIGN KEY (login) REFERENCES solana_users(login),
@@ -264,7 +271,7 @@ public class DatabaseInitializer {
ON connections_state (login, to_login);
""");
// 8) Trigger: connection state
// 8) Trigger: connection state (логика та же)
st.executeUpdate("""
CREATE TRIGGER IF NOT EXISTS trg_blocks_connection_state_ai
AFTER INSERT ON blocks
@@ -304,13 +311,13 @@ public class DatabaseInitializer {
END;
""");
// 9) message_stats
// 9) message_stats (to_block_hash -> BLOB)
st.executeUpdate("""
CREATE TABLE IF NOT EXISTS message_stats (
to_login TEXT NOT NULL,
to_bch_name TEXT NOT NULL,
to_block_global_number INTEGER NOT NULL,
to_block_hash TEXT NOT NULL,
to_block_hash BLOB NOT NULL,
likes_count INTEGER NOT NULL DEFAULT 0,
replies_count INTEGER NOT NULL DEFAULT 0,
@@ -334,7 +341,7 @@ public class DatabaseInitializer {
ON message_stats (to_login);
""");
// 10) Trigger: LIKE
// 10) Trigger: LIKE (to_block_hashe -> to_block_hash BLOB)
st.executeUpdate("""
CREATE TRIGGER IF NOT EXISTS trg_blocks_message_stats_like_ai
AFTER INSERT ON blocks
@@ -365,7 +372,7 @@ public class DatabaseInitializer {
END;
""");
// 11) Trigger: REPLY
// 11) Trigger: REPLY (to_block_hashe -> to_block_hash BLOB)
st.executeUpdate("""
CREATE TRIGGER IF NOT EXISTS trg_blocks_message_stats_reply_ai
AFTER INSERT ON blocks
@@ -395,6 +402,20 @@ public class DatabaseInitializer {
replies_count = message_stats.replies_count + 1;
END;
""");
// 12) Trigger: EDIT — пометить исходный блок
st.executeUpdate("""
CREATE TRIGGER IF NOT EXISTS trg_blocks_edit_apply_ai
AFTER INSERT ON blocks
WHEN NEW.msg_type = 1 AND NEW.msg_sub_type = 10
BEGIN
UPDATE blocks
SET edited_by_block_global_number = NEW.block_global_number
WHERE login = NEW.login
AND bch_name = NEW.bch_name
AND block_global_number = NEW.to_block_global_number;
END;
""");
}
}
}
@@ -1,3 +1,6 @@
// =======================
// BlockchainStateDAO.java (НОВАЯ ВЕРСИЯ)
// =======================
package shine.db.dao;
import shine.db.SqliteDbController;
@@ -71,11 +74,8 @@ public final class BlockchainStateDAO {
/** UPSERT с внешним соединением. Соединение НЕ закрывает. */
public void upsert(Connection c, BlockchainStateEntry e) throws SQLException {
// ВАЖНО:
// Колонок должно быть ровно 24:
// 8 основных + (8 линий * 2 поля) = 8 + 16 = 24
//
// size_bytes УДАЛЁН ИЗ ПРОЕКТА, здесь его быть не должно.
// Колонок ровно 24:
// 8 основных + (8 линий * 2 поля) = 24
String sql = """
INSERT INTO blockchain_state (
@@ -144,12 +144,12 @@ public final class BlockchainStateDAO {
ps.setLong(i++, e.getFileSizeBytes());
ps.setInt(i++, e.getLastGlobalNumber());
ps.setString(i++, nn(e.getLastGlobalHash()));
setBytesNullable(ps, i++, e.getLastGlobalHash());
ps.setLong(i++, e.getUpdatedAtMs());
for (int line = 0; line < 8; line++) {
ps.setInt(i++, e.getLastLineNumber(line));
ps.setString(i++, nn(e.getLastLineHash(line)));
setBytesNullable(ps, i++, e.getLastLineHash(line));
}
ps.executeUpdate();
@@ -204,17 +204,22 @@ public final class BlockchainStateDAO {
e.setFileSizeBytes(rs.getLong("file_size_bytes"));
e.setLastGlobalNumber(rs.getInt("last_global_number"));
e.setLastGlobalHash(rs.getString("last_global_hash"));
e.setLastGlobalHash(rs.getBytes("last_global_hash")); // может быть null
e.setUpdatedAtMs(rs.getLong("updated_at_ms"));
for (int line = 0; line < 8; line++) {
e.setLastLineNumber(line, rs.getInt("line" + line + "_last_number"));
e.setLastLineHash(line, rs.getString("line" + line + "_last_hash"));
e.setLastLineHash(line, rs.getBytes("line" + line + "_last_hash")); // может быть null
}
return e;
}
private static void setBytesNullable(PreparedStatement ps, int index, byte[] b) throws SQLException {
if (b != null) ps.setBytes(index, b);
else ps.setNull(index, Types.BLOB);
}
private static String nn(String s) { return s == null ? "" : s; }
}
@@ -50,8 +50,11 @@ public final class BlocksDAO {
to_login,
to_bch_name,
to_block_global_number,
to_block_hashe
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
to_block_hashe,
block_hash,
block_signature,
edited_by_block_global_number
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""";
try (PreparedStatement ps = c.prepareStatement(sql)) {
@@ -104,7 +107,10 @@ public final class BlocksDAO {
to_login,
to_bch_name,
to_block_global_number,
to_block_hashe
to_block_hashe,
block_hash,
block_signature,
edited_by_block_global_number
FROM blocks
WHERE
login = ?
@@ -145,15 +151,18 @@ public final class BlocksDAO {
String sql = """
UPDATE blocks
SET
block_global_pre_hashe = ?,
block_line_pre_hashe = ?,
msg_type = ?,
msg_sub_type = ?,
block_byte = ?,
to_login = ?,
to_bch_name = ?,
to_block_global_number = ?,
to_block_hashe = ?
block_global_pre_hashe = ?,
block_line_pre_hashe = ?,
msg_type = ?,
msg_sub_type = ?,
block_byte = ?,
to_login = ?,
to_bch_name = ?,
to_block_global_number = ?,
to_block_hashe = ?,
block_hash = ?,
block_signature = ?,
edited_by_block_global_number = ?
WHERE
login = ?
AND bch_name = ?
@@ -165,8 +174,8 @@ public final class BlocksDAO {
try (PreparedStatement ps = c.prepareStatement(sql)) {
int i = 1;
ps.setString(i++, nn(e.getBlockGlobalPreHashe()));
ps.setString(i++, nn(e.getBlockLinePreHashe()));
ps.setBytes(i++, bb(e.getBlockGlobalPreHashe()));
ps.setBytes(i++, bb(e.getBlockLinePreHashe()));
ps.setInt(i++, e.getMsgType());
ps.setInt(i++, e.getMsgSubType());
@@ -183,8 +192,14 @@ public final class BlocksDAO {
if (e.getToBlockGlobalNumber() != null) ps.setInt(i++, e.getToBlockGlobalNumber());
else ps.setNull(i++, Types.INTEGER);
if (e.getToBlockHashe() != null) ps.setString(i++, e.getToBlockHashe());
else ps.setNull(i++, Types.VARCHAR);
if (e.getToBlockHashe() != null) ps.setBytes(i++, e.getToBlockHashe());
else ps.setNull(i++, Types.BLOB);
ps.setBytes(i++, bb(e.getBlockHash()));
ps.setBytes(i++, bb(e.getBlockSignature()));
if (e.getEditedByBlockGlobalNumber() != null) ps.setInt(i++, e.getEditedByBlockGlobalNumber());
else ps.setNull(i++, Types.INTEGER);
ps.setString(i++, e.getLogin());
ps.setString(i++, e.getBchName());
@@ -249,11 +264,11 @@ public final class BlocksDAO {
ps.setString(i++, e.getLogin());
ps.setString(i++, e.getBchName());
ps.setInt(i++, e.getBlockGlobalNumber());
ps.setString(i++, nn(e.getBlockGlobalPreHashe()));
ps.setBytes(i++, bb(e.getBlockGlobalPreHashe()));
ps.setInt(i++, e.getBlockLineIndex());
ps.setInt(i++, e.getBlockLineNumber());
ps.setString(i++, nn(e.getBlockLinePreHashe()));
ps.setBytes(i++, bb(e.getBlockLinePreHashe()));
ps.setInt(i++, e.getMsgType());
ps.setInt(i++, e.getMsgSubType());
@@ -271,8 +286,14 @@ public final class BlocksDAO {
if (e.getToBlockGlobalNumber() != null) ps.setInt(i++, e.getToBlockGlobalNumber());
else ps.setNull(i++, Types.INTEGER);
if (e.getToBlockHashe() != null) ps.setString(i++, e.getToBlockHashe());
else ps.setNull(i++, Types.VARCHAR);
if (e.getToBlockHashe() != null) ps.setBytes(i++, e.getToBlockHashe());
else ps.setNull(i++, Types.BLOB);
ps.setBytes(i++, bb(e.getBlockHash()));
ps.setBytes(i++, bb(e.getBlockSignature()));
if (e.getEditedByBlockGlobalNumber() != null) ps.setInt(i++, e.getEditedByBlockGlobalNumber());
else ps.setNull(i++, Types.INTEGER);
}
private BlockEntry mapRow(ResultSet rs) throws SQLException {
@@ -281,11 +302,11 @@ public final class BlocksDAO {
e.setLogin(rs.getString("login"));
e.setBchName(rs.getString("bch_name"));
e.setBlockGlobalNumber(rs.getInt("block_global_number"));
e.setBlockGlobalPreHashe(rs.getString("block_global_pre_hashe"));
e.setBlockGlobalPreHashe(rs.getBytes("block_global_pre_hashe"));
e.setBlockLineIndex(rs.getInt("block_line_index"));
e.setBlockLineNumber(rs.getInt("block_line_number"));
e.setBlockLinePreHashe(rs.getString("block_line_pre_hashe"));
e.setBlockLinePreHashe(rs.getBytes("block_line_pre_hashe"));
e.setMsgType(rs.getInt("msg_type"));
e.setMsgSubType(rs.getInt("msg_sub_type"));
@@ -301,12 +322,18 @@ public final class BlocksDAO {
Integer toBlockGlobalNumber = (Integer) rs.getObject("to_block_global_number");
e.setToBlockGlobalNumber(toBlockGlobalNumber);
String toBlockHashe = rs.getString("to_block_hashe");
byte[] toBlockHashe = rs.getBytes("to_block_hashe");
if (rs.wasNull()) toBlockHashe = null;
e.setToBlockHashe(toBlockHashe);
e.setBlockHash(rs.getBytes("block_hash"));
e.setBlockSignature(rs.getBytes("block_signature"));
Integer editedBy = (Integer) rs.getObject("edited_by_block_global_number");
e.setEditedByBlockGlobalNumber(editedBy);
return e;
}
private static String nn(String s) { return s == null ? "" : s; }
private static byte[] bb(byte[] b) { return b == null ? new byte[0] : b; }
}
@@ -12,7 +12,7 @@ import java.sql.*;
* - blockchain_state (blockchain_name, login, blockchain_key, size_limit, ... last_global_number=-1 ...)
*
* ВАЖНО:
* - только INSERT
* - только INSERT/UPSERT
* - если login или blockchainName заняты — возвращаем false (пользователь уже есть/занято)
*/
public final class UserCreateDAO {
@@ -69,11 +69,11 @@ public final class UserCreateDAO {
// старт: глобальных блоков ещё нет
st.setLastGlobalNumber(-1);
st.setLastGlobalHash("");
st.setLastGlobalHash(null);
for (int line = 0; line < 8; line++) {
st.setLastLineNumber(line, 0);
st.setLastLineHash(line, "");
st.setLastLineHash(line, null);
}
st.setUpdatedAtMs(nowMs);
@@ -9,11 +9,11 @@ public class BlockEntry {
private String bchName;
private int blockGlobalNumber;
private String blockGlobalPreHashe;
private byte[] blockGlobalPreHashe;
private int blockLineIndex;
private int blockLineNumber;
private String blockLinePreHashe;
private byte[] blockLinePreHashe;
private int msgType;
private int msgSubType;
@@ -23,24 +23,32 @@ public class BlockEntry {
private String toLogin;
private String toBchName;
private Integer toBlockGlobalNumber;
private String toBlockHashe;
private byte[] toBlockHashe;
// новое
private byte[] blockHash;
private byte[] blockSignature;
private Integer editedByBlockGlobalNumber;
public BlockEntry() {}
public BlockEntry(String login,
String bchName,
int blockGlobalNumber,
String blockGlobalPreHashe,
byte[] blockGlobalPreHashe,
int blockLineIndex,
int blockLineNumber,
String blockLinePreHashe,
byte[] blockLinePreHashe,
int msgType,
int msgSubType,
byte[] blockByte,
String toLogin,
String toBchName,
Integer toBlockGlobalNumber,
String toBlockHashe) {
byte[] toBlockHashe,
byte[] blockHash,
byte[] blockSignature,
Integer editedByBlockGlobalNumber) {
this.login = login;
this.bchName = bchName;
this.blockGlobalNumber = blockGlobalNumber;
@@ -55,6 +63,9 @@ public class BlockEntry {
this.toBchName = toBchName;
this.toBlockGlobalNumber = toBlockGlobalNumber;
this.toBlockHashe = toBlockHashe;
this.blockHash = blockHash;
this.blockSignature = blockSignature;
this.editedByBlockGlobalNumber = editedByBlockGlobalNumber;
}
public String getLogin() { return login; }
@@ -66,8 +77,8 @@ public class BlockEntry {
public int getBlockGlobalNumber() { return blockGlobalNumber; }
public void setBlockGlobalNumber(int blockGlobalNumber) { this.blockGlobalNumber = blockGlobalNumber; }
public String getBlockGlobalPreHashe() { return blockGlobalPreHashe; }
public void setBlockGlobalPreHashe(String blockGlobalPreHashe) { this.blockGlobalPreHashe = blockGlobalPreHashe; }
public byte[] getBlockGlobalPreHashe() { return blockGlobalPreHashe; }
public void setBlockGlobalPreHashe(byte[] blockGlobalPreHashe) { this.blockGlobalPreHashe = blockGlobalPreHashe; }
public int getBlockLineIndex() { return blockLineIndex; }
public void setBlockLineIndex(int blockLineIndex) { this.blockLineIndex = blockLineIndex; }
@@ -75,8 +86,8 @@ public class BlockEntry {
public int getBlockLineNumber() { return blockLineNumber; }
public void setBlockLineNumber(int blockLineNumber) { this.blockLineNumber = blockLineNumber; }
public String getBlockLinePreHashe() { return blockLinePreHashe; }
public void setBlockLinePreHashe(String blockLinePreHashe) { this.blockLinePreHashe = blockLinePreHashe; }
public byte[] getBlockLinePreHashe() { return blockLinePreHashe; }
public void setBlockLinePreHashe(byte[] blockLinePreHashe) { this.blockLinePreHashe = blockLinePreHashe; }
public int getMsgType() { return msgType; }
public void setMsgType(int msgType) { this.msgType = msgType; }
@@ -96,6 +107,15 @@ public class BlockEntry {
public Integer getToBlockGlobalNumber() { return toBlockGlobalNumber; }
public void setToBlockGlobalNumber(Integer toBlockGlobalNumber) { this.toBlockGlobalNumber = toBlockGlobalNumber; }
public String getToBlockHashe() { return toBlockHashe; }
public void setToBlockHashe(String toBlockHashe) { this.toBlockHashe = toBlockHashe; }
public byte[] getToBlockHashe() { return toBlockHashe; }
public void setToBlockHashe(byte[] toBlockHashe) { this.toBlockHashe = toBlockHashe; }
public byte[] getBlockHash() { return blockHash; }
public void setBlockHash(byte[] blockHash) { this.blockHash = blockHash; }
public byte[] getBlockSignature() { return blockSignature; }
public void setBlockSignature(byte[] blockSignature) { this.blockSignature = blockSignature; }
public Integer getEditedByBlockGlobalNumber() { return editedByBlockGlobalNumber; }
public void setEditedByBlockGlobalNumber(Integer editedByBlockGlobalNumber) { this.editedByBlockGlobalNumber = editedByBlockGlobalNumber; }
}
@@ -1,3 +1,6 @@
// =======================
// BlockchainStateEntry.java (НОВАЯ ВЕРСИЯ)
// =======================
package shine.db.entities;
import java.util.Arrays;
@@ -6,6 +9,11 @@ import java.util.Base64;
/**
* Агрегатная сущность текущего состояния блокчейна.
* 1 строка = 1 blockchain_name, плюс состояние линий 0..7.
*
* ВАЖНО:
* - hash-поля теперь храним как byte[] и допускаем NULL:
* * NULL = "ещё не было ни одного блока" (genesis и т.п.)
* * не подменяем на new byte[0], чтобы не терять смысл
*/
public final class BlockchainStateEntry {
@@ -18,16 +26,15 @@ public final class BlockchainStateEntry {
private long fileSizeBytes;
private int lastGlobalNumber;
private String lastGlobalHash;
private byte[] lastGlobalHash; // nullable
private final int[] lastLineNumbers = new int[8];
private final String[] lastLineHashes = new String[8];
private final byte[][] lastLineHashes = new byte[8][]; // nullable elements
private long updatedAtMs;
public BlockchainStateEntry() {
for (int i = 0; i < 8; i++) lastLineHashes[i] = "";
this.lastGlobalHash = "";
// hashes остаются null по умолчанию (genesis)
}
public BlockchainStateEntry(String blockchainName,
@@ -36,9 +43,9 @@ public final class BlockchainStateEntry {
long sizeLimit,
long fileSizeBytes,
int lastGlobalNumber,
String lastGlobalHash,
byte[] lastGlobalHash,
int[] lastLineNumbers,
String[] lastLineHashes,
byte[][] lastLineHashes,
long updatedAtMs) {
this.blockchainName = blockchainName;
this.login = login;
@@ -46,17 +53,16 @@ public final class BlockchainStateEntry {
this.sizeLimit = sizeLimit;
this.fileSizeBytes = fileSizeBytes;
this.lastGlobalNumber = lastGlobalNumber;
this.lastGlobalHash = lastGlobalHash == null ? "" : lastGlobalHash;
this.lastGlobalHash = lastGlobalHash;
if (lastLineNumbers != null) {
if (lastLineNumbers.length != 8) throw new IllegalArgumentException("lastLineNumbers must be len=8");
System.arraycopy(lastLineNumbers, 0, this.lastLineNumbers, 0, 8);
}
if (lastLineHashes != null) {
if (lastLineHashes.length != 8) throw new IllegalArgumentException("lastLineHashes must be len=8");
for (int i = 0; i < 8; i++) this.lastLineHashes[i] = lastLineHashes[i] == null ? "" : lastLineHashes[i];
} else {
for (int i = 0; i < 8; i++) this.lastLineHashes[i] = "";
System.arraycopy(lastLineHashes, 0, this.lastLineHashes, 0, 8);
}
this.updatedAtMs = updatedAtMs;
@@ -92,8 +98,8 @@ public final class BlockchainStateEntry {
public int getLastGlobalNumber() { return lastGlobalNumber; }
public void setLastGlobalNumber(int lastGlobalNumber) { this.lastGlobalNumber = lastGlobalNumber; }
public String getLastGlobalHash() { return lastGlobalHash; }
public void setLastGlobalHash(String lastGlobalHash) { this.lastGlobalHash = lastGlobalHash == null ? "" : lastGlobalHash; }
public byte[] getLastGlobalHash() { return lastGlobalHash; }
public void setLastGlobalHash(byte[] lastGlobalHash) { this.lastGlobalHash = lastGlobalHash; }
public int getLastLineNumber(int line) {
checkLine(line);
@@ -104,17 +110,22 @@ public final class BlockchainStateEntry {
lastLineNumbers[line] = value;
}
public String getLastLineHash(int line) {
public byte[] getLastLineHash(int line) {
checkLine(line);
return lastLineHashes[line];
}
public void setLastLineHash(int line, String value) {
public void setLastLineHash(int line, byte[] value) {
checkLine(line);
lastLineHashes[line] = value == null ? "" : value;
lastLineHashes[line] = value;
}
public int[] getLastLineNumbersCopy() { return Arrays.copyOf(lastLineNumbers, 8); }
public String[] getLastLineHashesCopy() { return Arrays.copyOf(lastLineHashes, 8); }
public int[] getLastLineNumbersCopy() {
return Arrays.copyOf(lastLineNumbers, 8);
}
public byte[][] getLastLineHashesCopy() {
return Arrays.copyOf(lastLineHashes, 8);
}
public long getUpdatedAtMs() { return updatedAtMs; }
public void setUpdatedAtMs(long updatedAtMs) { this.updatedAtMs = updatedAtMs; }