24 12 25
Дорабатываю добавление блоков. Поставил todo что доделать
This commit is contained in:
parent
33635886e0
commit
5ecaf67bcb
@ -12,7 +12,7 @@ import java.util.Objects;
|
|||||||
* [4] recordSize (int) = размер RAW (включая этот заголовок), БЕЗ signature+hash
|
* [4] recordSize (int) = размер RAW (включая этот заголовок), БЕЗ signature+hash
|
||||||
* [4] recordNumber (int) глобальный номер блока
|
* [4] recordNumber (int) глобальный номер блока
|
||||||
* [8] timestamp (long) unix seconds
|
* [8] timestamp (long) unix seconds
|
||||||
* [2] line (short)
|
* [2] lineIndex (short)
|
||||||
* [4] lineNumber (int)
|
* [4] lineNumber (int)
|
||||||
* [N] bodyBytes (body, начинается с [type][version])
|
* [N] bodyBytes (body, начинается с [type][version])
|
||||||
*
|
*
|
||||||
@ -32,7 +32,7 @@ public final class BchBlockEntry {
|
|||||||
public final int recordSize; // только RAW, без signature+hash
|
public final int recordSize; // только RAW, без signature+hash
|
||||||
public final int recordNumber;
|
public final int recordNumber;
|
||||||
public final long timestamp;
|
public final long timestamp;
|
||||||
public final short line;
|
public final short lineIndex;
|
||||||
public final int lineNumber;
|
public final int lineNumber;
|
||||||
public final byte[] bodyBytes;
|
public final byte[] bodyBytes;
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ public final class BchBlockEntry {
|
|||||||
|
|
||||||
this.recordNumber = bb.getInt();
|
this.recordNumber = bb.getInt();
|
||||||
this.timestamp = bb.getLong();
|
this.timestamp = bb.getLong();
|
||||||
this.line = bb.getShort();
|
this.lineIndex = bb.getShort();
|
||||||
this.lineNumber = bb.getInt();
|
this.lineNumber = bb.getInt();
|
||||||
|
|
||||||
int bodyLen = recordSize - RAW_HEADER_SIZE;
|
int bodyLen = recordSize - RAW_HEADER_SIZE;
|
||||||
@ -85,7 +85,7 @@ public final class BchBlockEntry {
|
|||||||
|
|
||||||
public BchBlockEntry(int recordNumber,
|
public BchBlockEntry(int recordNumber,
|
||||||
long timestamp,
|
long timestamp,
|
||||||
short line,
|
short lineIndex,
|
||||||
int lineNumber,
|
int lineNumber,
|
||||||
byte[] bodyBytes,
|
byte[] bodyBytes,
|
||||||
byte[] signature64,
|
byte[] signature64,
|
||||||
@ -102,7 +102,7 @@ public final class BchBlockEntry {
|
|||||||
|
|
||||||
this.recordNumber = recordNumber;
|
this.recordNumber = recordNumber;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.line = line;
|
this.lineIndex = lineIndex;
|
||||||
this.lineNumber = lineNumber;
|
this.lineNumber = lineNumber;
|
||||||
this.bodyBytes = Arrays.copyOf(bodyBytes, bodyBytes.length);
|
this.bodyBytes = Arrays.copyOf(bodyBytes, bodyBytes.length);
|
||||||
this.signature64 = Arrays.copyOf(signature64, SIGNATURE_LEN);
|
this.signature64 = Arrays.copyOf(signature64, SIGNATURE_LEN);
|
||||||
@ -117,7 +117,7 @@ public final class BchBlockEntry {
|
|||||||
bb.putInt(this.recordSize);
|
bb.putInt(this.recordSize);
|
||||||
bb.putInt(recordNumber);
|
bb.putInt(recordNumber);
|
||||||
bb.putLong(timestamp);
|
bb.putLong(timestamp);
|
||||||
bb.putShort(line);
|
bb.putShort(lineIndex);
|
||||||
bb.putInt(lineNumber);
|
bb.putInt(lineNumber);
|
||||||
bb.put(bodyBytes);
|
bb.put(bodyBytes);
|
||||||
bb.put(this.signature64);
|
bb.put(this.signature64);
|
||||||
|
|||||||
@ -3,13 +3,13 @@ package utils.blockchain;
|
|||||||
public final class BlockchainNameUtil {
|
public final class BlockchainNameUtil {
|
||||||
|
|
||||||
/** Сколько символов отрезаем с конца blockchainName, чтобы получить login. */
|
/** Сколько символов отрезаем с конца blockchainName, чтобы получить login. */
|
||||||
public static final int BLOCKCHAIN_NAME_LOGIN_SUFFIX_LEN = 4;
|
public static final int BLOCKCHAIN_NAME_LOGIN_SUFFIX_LEN = 3;
|
||||||
|
|
||||||
private BlockchainNameUtil() {}
|
private BlockchainNameUtil() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Извлечь login из blockchainName: отрезаем последние 4 символа.
|
* Извлечь login из blockchainName: отрезаем последние 3 символа.
|
||||||
* Пример: "aidar.bch" -> "aidar"
|
* Пример: "Dima001" -> "Dima"
|
||||||
*/
|
*/
|
||||||
public static String loginFromBlockchainName(String blockchainName) {
|
public static String loginFromBlockchainName(String blockchainName) {
|
||||||
if (blockchainName == null) return null;
|
if (blockchainName == null) return null;
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import server.logic.ws_protocol.JSON.handlers.auth.Net_CreateAuthSession__Handle
|
|||||||
import server.logic.ws_protocol.JSON.handlers.auth.Net_RefreshSession_Handler;
|
import server.logic.ws_protocol.JSON.handlers.auth.Net_RefreshSession_Handler;
|
||||||
import server.logic.ws_protocol.JSON.handlers.auth.Net_CloseActiveSession_Handler;
|
import server.logic.ws_protocol.JSON.handlers.auth.Net_CloseActiveSession_Handler;
|
||||||
import server.logic.ws_protocol.JSON.handlers.auth.Net_ListSessions_Handler;
|
import server.logic.ws_protocol.JSON.handlers.auth.Net_ListSessions_Handler;
|
||||||
import server.logic.ws_protocol.JSON.handlers.blockchain.Net_AddBlock_new_Handler;
|
import server.logic.ws_protocol.JSON.handlers.blockchain.Net_AddBlock_Handler;
|
||||||
import server.logic.ws_protocol.JSON.handlers.tempToTest.Net_AddUser_Handler;
|
import server.logic.ws_protocol.JSON.handlers.tempToTest.Net_AddUser_Handler;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -37,7 +37,7 @@ public final class JsonHandlerRegistry {
|
|||||||
"CreateAuthSession", new Net_CreateAuthSession__Handler(),
|
"CreateAuthSession", new Net_CreateAuthSession__Handler(),
|
||||||
"CloseActiveSession", new Net_CloseActiveSession_Handler(),
|
"CloseActiveSession", new Net_CloseActiveSession_Handler(),
|
||||||
"ListSessions", new Net_ListSessions_Handler(),
|
"ListSessions", new Net_ListSessions_Handler(),
|
||||||
"AddBlock", new Net_AddBlock_new_Handler()
|
"AddBlock", new Net_AddBlock_Handler()
|
||||||
// сюда потом добавишь другие операции
|
// сюда потом добавишь другие операции
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import java.util.Base64;
|
|||||||
*
|
*
|
||||||
* Ответ наружу: только reasonCode + serverLastGlobalNumber/serverLastGlobalHash
|
* Ответ наружу: только reasonCode + serverLastGlobalNumber/serverLastGlobalHash
|
||||||
*/
|
*/
|
||||||
public final class BlockchainStateService_new {
|
public final class BlockchainStateService {
|
||||||
|
|
||||||
/** Результат атомарного addBlock */
|
/** Результат атомарного addBlock */
|
||||||
public static final class AddBlockResult {
|
public static final class AddBlockResult {
|
||||||
@ -49,19 +49,19 @@ public final class BlockchainStateService_new {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static volatile BlockchainStateService_new instance;
|
private static volatile BlockchainStateService instance;
|
||||||
|
|
||||||
private final SqliteDbController db = SqliteDbController.getInstance();
|
private final SqliteDbController db = SqliteDbController.getInstance();
|
||||||
private final BlocksDAO blocksDAO = BlocksDAO.getInstance();
|
private final BlocksDAO blocksDAO = BlocksDAO.getInstance();
|
||||||
private final BlockchainStateDAO stateDAO = BlockchainStateDAO.getInstance();
|
private final BlockchainStateDAO stateDAO = BlockchainStateDAO.getInstance();
|
||||||
private final SolanaUsersDAO solanaUsersDAO = SolanaUsersDAO.getInstance();
|
private final SolanaUsersDAO solanaUsersDAO = SolanaUsersDAO.getInstance();
|
||||||
|
|
||||||
private BlockchainStateService_new() {}
|
private BlockchainStateService() {}
|
||||||
|
|
||||||
public static BlockchainStateService_new getInstance() {
|
public static BlockchainStateService getInstance() {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
synchronized (BlockchainStateService_new.class) {
|
synchronized (BlockchainStateService.class) {
|
||||||
if (instance == null) instance = new BlockchainStateService_new();
|
if (instance == null) instance = new BlockchainStateService();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return instance;
|
return instance;
|
||||||
@ -80,10 +80,12 @@ public final class BlockchainStateService_new {
|
|||||||
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "empty_blockchain_name", 0, "");
|
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "empty_blockchain_name", 0, "");
|
||||||
|
|
||||||
// Можно быстро проверить, что login согласован с blockchainName (если хочешь строгость)
|
// Можно быстро проверить, что login согласован с blockchainName (если хочешь строгость)
|
||||||
String loginFromName = BlockchainNameUtil.loginFromBlockchainName(blockchainName);
|
String loginFromBlockchainName = BlockchainNameUtil.loginFromBlockchainName(blockchainName);
|
||||||
if (loginFromName == null || loginFromName.isBlank())
|
if (loginFromBlockchainName == null || loginFromBlockchainName.isBlank())
|
||||||
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_blockchain_name", 0, "");
|
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_blockchain_name", 0, "");
|
||||||
|
|
||||||
|
|
||||||
|
// todo действительно давай прото брать логин из имени блокчена и не передавать его отдельно в запросе!
|
||||||
if (login == null || login.isBlank()) {
|
if (login == null || login.isBlank()) {
|
||||||
// раз уж у нас есть loginFromName — можно принимать login пустым,
|
// раз уж у нас есть loginFromName — можно принимать login пустым,
|
||||||
// но ты явно передаёшь login, поэтому пока так:
|
// но ты явно передаёшь login, поэтому пока так:
|
||||||
@ -91,7 +93,7 @@ public final class BlockchainStateService_new {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// (опционально) сверка
|
// (опционально) сверка
|
||||||
if (!loginFromName.equals(login)) {
|
if (!loginFromBlockchainName.equals(login)) {
|
||||||
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "login_not_match_blockchain_name", 0, "");
|
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "login_not_match_blockchain_name", 0, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +104,10 @@ public final class BlockchainStateService_new {
|
|||||||
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_block_base64", 0, "");
|
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_block_base64", 0, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo ну и ещё тут нужно проверить что не только сам формат блока верный, но и запись в этом блоке верная - тоесть что её можно распарсить!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
final BchBlockEntry block;
|
final BchBlockEntry block;
|
||||||
try {
|
try {
|
||||||
block = new BchBlockEntry(blockBytes);
|
block = new BchBlockEntry(blockBytes);
|
||||||
@ -133,6 +139,8 @@ public final class BlockchainStateService_new {
|
|||||||
|
|
||||||
// 2) состояние блокчейна
|
// 2) состояние блокчейна
|
||||||
BlockchainStateEntry st = stateDAO.getByBlockchainName(c, blockchainName);
|
BlockchainStateEntry st = stateDAO.getByBlockchainName(c, blockchainName);
|
||||||
|
|
||||||
|
//todo тут надо учесть тот случай что если это 0 блок тоесть начало блокчейна то логично что ещё нет самого файла блокчейна и по этому нет и BlockchainStateEntry
|
||||||
if (st == null) {
|
if (st == null) {
|
||||||
c.rollback();
|
c.rollback();
|
||||||
return new AddBlockResult(WireCodes.Status.NOT_FOUND, "blockchain_state_not_found", 0, "");
|
return new AddBlockResult(WireCodes.Status.NOT_FOUND, "blockchain_state_not_found", 0, "");
|
||||||
@ -156,6 +164,9 @@ public final class BlockchainStateService_new {
|
|||||||
}
|
}
|
||||||
|
|
||||||
byte[] prevLineHash32 = prevGlobalHash32; // пока линии не используем
|
byte[] prevLineHash32 = prevGlobalHash32; // пока линии не используем
|
||||||
|
//todo точно так же как и глобальный проверяем преведущий хэш по линии. он пока не используется, в том плане что у нас везде линия 0 и приведущий хэш по линии по сути равен приведущему глобальному хэшу, но его тоже надо проверять.
|
||||||
|
// по сути можно сказать он используется просто пока везде только одна линия с индексом 0
|
||||||
|
|
||||||
|
|
||||||
// 5) крипто-проверка
|
// 5) крипто-проверка
|
||||||
boolean ok = BchCryptoVerifier.verifyAll(
|
boolean ok = BchCryptoVerifier.verifyAll(
|
||||||
@ -173,6 +184,8 @@ public final class BlockchainStateService_new {
|
|||||||
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_signature_or_hash", st.getLastGlobalNumber(), nn(st.getLastGlobalHash()));
|
return new AddBlockResult(WireCodes.Status.BAD_REQUEST, "bad_signature_or_hash", st.getLastGlobalNumber(), nn(st.getLastGlobalHash()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//todo сам код добавление блока вынести в отдельный класс - тк там надо потом усложнить действие
|
||||||
// 6) вставка блока
|
// 6) вставка блока
|
||||||
insertBlockRow(c, login, blockchainName, globalNumber, prevGlobalHashHex, blockBytes);
|
insertBlockRow(c, login, blockchainName, globalNumber, prevGlobalHashHex, blockBytes);
|
||||||
|
|
||||||
@ -8,13 +8,14 @@ import server.logic.ws_protocol.JSON.entyties.blockchain.Net_AddBlock_Response;
|
|||||||
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||||
import server.logic.ws_protocol.WireCodes;
|
import server.logic.ws_protocol.WireCodes;
|
||||||
|
|
||||||
public final class Net_AddBlock_new_Handler implements JsonMessageHandler {
|
public final class Net_AddBlock_Handler implements JsonMessageHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Net_Response handle(Net_Request baseReq, ConnectionContext ctx) throws Exception {
|
public Net_Response handle(Net_Request baseReq, ConnectionContext ctx) throws Exception {
|
||||||
|
|
||||||
Net_AddBlock_Request req = (Net_AddBlock_Request) baseReq;
|
Net_AddBlock_Request req = (Net_AddBlock_Request) baseReq;
|
||||||
|
|
||||||
var r = BlockchainStateService_new.getInstance().addBlockAtomically(
|
var r = BlockchainStateService.getInstance().addBlockAtomically(
|
||||||
req.getLogin(),
|
req.getLogin(),
|
||||||
req.getBlockchainName(),
|
req.getBlockchainName(),
|
||||||
req.getGlobalNumber(),
|
req.getGlobalNumber(),
|
||||||
@ -22,6 +23,10 @@ public final class Net_AddBlock_new_Handler implements JsonMessageHandler {
|
|||||||
req.getBlockBytesB64()
|
req.getBlockBytesB64()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// todo если пришёл запрос на добавление то надо блочить работу с этим блокчейном по req.getBlockchainName(),
|
||||||
|
// с помощью класса BlockchainLocks и разлочивать работу только в конце завершения работы этого хэндлера, что бы не случилось паралельной работы двух потоков с одним и тем же блокчейном!
|
||||||
|
|
||||||
|
|
||||||
Net_AddBlock_Response resp = new Net_AddBlock_Response();
|
Net_AddBlock_Response resp = new Net_AddBlock_Response();
|
||||||
resp.setOp(req.getOp());
|
resp.setOp(req.getOp());
|
||||||
resp.setRequestId(req.getRequestId());
|
resp.setRequestId(req.getRequestId());
|
||||||
@ -1,322 +1,317 @@
|
|||||||
//package Test;
|
package Test;
|
||||||
//
|
|
||||||
//import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
//import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
//import utils.crypto.Ed25519Util;
|
import utils.crypto.Ed25519Util;
|
||||||
//import blockchain.body.HeaderBody;
|
import blockchain.body.HeaderBody;
|
||||||
//import blockchain.body.TextBody;
|
import blockchain.body.TextBody;
|
||||||
//import blockchain_new.BchCryptoVerifier_new;
|
import blockchain.BchCryptoVerifier;
|
||||||
//import blockchain_new.BchBlockEntry_new;
|
import blockchain.BchBlockEntry;
|
||||||
//
|
|
||||||
//import java.net.URI;
|
import java.net.URI;
|
||||||
//import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
//import java.net.http.WebSocket;
|
import java.net.http.WebSocket;
|
||||||
//import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
//import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
//import java.util.Base64;
|
import java.util.Base64;
|
||||||
//import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
//import java.util.concurrent.CompletionStage;
|
import java.util.concurrent.CompletionStage;
|
||||||
//import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
//
|
|
||||||
//public class Test_AddBlock_new_NoAuth {
|
public class Test_AddBlock_new_NoAuth {
|
||||||
//
|
|
||||||
// private static final String WS_URI = "ws://localhost:7070/ws";
|
private static final String WS_URI = "ws://localhost:7070/ws";
|
||||||
// private static final ObjectMapper JSON = new ObjectMapper();
|
private static final ObjectMapper JSON = new ObjectMapper();
|
||||||
//
|
|
||||||
// private static final String TEST_LOGIN = "anya24";
|
private static final String TEST_LOGIN = "anya24";
|
||||||
// // По твоему правилу: blockchainName = login + 4 цифры
|
// По твоему правилу: blockchainName = login + 4 цифры
|
||||||
// private static final String TEST_BCH_NAME = TEST_LOGIN + "0001";
|
private static final String TEST_BCH_NAME = TEST_LOGIN + "0001";
|
||||||
//
|
|
||||||
// private static final byte[] LOGIN_PRIV_KEY;
|
private static final byte[] LOGIN_PRIV_KEY;
|
||||||
// private static final byte[] LOGIN_PUB_KEY;
|
private static final byte[] LOGIN_PUB_KEY;
|
||||||
//
|
|
||||||
// static {
|
static {
|
||||||
// LOGIN_PRIV_KEY = Ed25519Util.generatePrivateKeyFromString("test-ed25519-login-11" + TEST_LOGIN);
|
LOGIN_PRIV_KEY = Ed25519Util.generatePrivateKeyFromString("test-ed25519-login-11" + TEST_LOGIN);
|
||||||
// LOGIN_PUB_KEY = Ed25519Util.derivePublicKey(LOGIN_PRIV_KEY);
|
LOGIN_PUB_KEY = Ed25519Util.derivePublicKey(LOGIN_PRIV_KEY);
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// private static final byte[] ZERO32 = new byte[32];
|
private static final byte[] ZERO32 = new byte[32];
|
||||||
// private static final String ZERO64 = "0".repeat(64);
|
private static final String ZERO64 = "0".repeat(64);
|
||||||
//
|
|
||||||
// public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
// CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
// HttpClient client = HttpClient.newHttpClient();
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
//
|
|
||||||
// client.newWebSocketBuilder()
|
client.newWebSocketBuilder()
|
||||||
// .buildAsync(URI.create(WS_URI), new WebSocket.Listener() {
|
.buildAsync(URI.create(WS_URI), new WebSocket.Listener() {
|
||||||
//
|
|
||||||
// private int step = 0;
|
private int step = 0;
|
||||||
//
|
|
||||||
// // Эти значения обновим ПО ОТВЕТУ сервера на header
|
// Эти значения обновим ПО ОТВЕТУ сервера на header
|
||||||
// private String lastGlobalHashHex = ZERO64;
|
private String lastGlobalHashHex = ZERO64;
|
||||||
// private String lastLineHashHex = ZERO64;
|
private String lastLineHashHex = ZERO64;
|
||||||
//
|
|
||||||
// @Override
|
@Override
|
||||||
// public void onOpen(WebSocket ws) {
|
public void onOpen(WebSocket ws) {
|
||||||
// System.out.println("✅ WS connected: " + WS_URI);
|
System.out.println("✅ WS connected: " + WS_URI);
|
||||||
// ws.request(1);
|
ws.request(1);
|
||||||
//
|
|
||||||
// // 1) HEADER (global=0, line=0, lineNumber=0)
|
// 1) HEADER (global=0, line=0, lineNumber=0)
|
||||||
// byte[] headerFull = buildHeaderBlockFullBytes(
|
byte[] headerFull = buildHeaderBlockFullBytes(
|
||||||
// /*global*/0,
|
/*global*/0,
|
||||||
// /*lineIndex*/(short)0,
|
/*lineIndex*/(short)0,
|
||||||
// /*lineBlock*/0,
|
/*lineBlock*/0,
|
||||||
// /*prevGlobal*/ZERO32,
|
/*prevGlobal*/ZERO32,
|
||||||
// /*prevLine*/ZERO32
|
/*prevLine*/ZERO32
|
||||||
// );
|
);
|
||||||
//
|
|
||||||
// String json = buildAddBlockJson(
|
String json = buildAddBlockJson(
|
||||||
// "test-add-header",
|
"test-add-header",
|
||||||
// TEST_BCH_NAME,
|
TEST_BCH_NAME,
|
||||||
// 0,
|
0,
|
||||||
// ZERO64, // prevGlobalHash для первого блока — нули
|
ZERO64, // prevGlobalHash для первого блока — нули
|
||||||
// base64(headerFull)
|
base64(headerFull)
|
||||||
// );
|
);
|
||||||
//
|
|
||||||
// System.out.println("\n📤 SEND #1 (HEADER):\n" + json);
|
System.out.println("\n📤 SEND #1 (HEADER):\n" + json);
|
||||||
// ws.sendText(json, true);
|
ws.sendText(json, true);
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// @Override
|
@Override
|
||||||
// public CompletionStage<?> onText(WebSocket ws, CharSequence data, boolean last) {
|
public CompletionStage<?> onText(WebSocket ws, CharSequence data, boolean last) {
|
||||||
// String msg = data.toString();
|
String msg = data.toString();
|
||||||
// System.out.println("\n📥 RECV:\n" + msg);
|
System.out.println("\n📥 RECV:\n" + msg);
|
||||||
// System.out.println("-----------------------------------------------------");
|
System.out.println("-----------------------------------------------------");
|
||||||
//
|
|
||||||
// try {
|
try {
|
||||||
// int status = extractStatus(msg);
|
int status = extractStatus(msg);
|
||||||
//
|
|
||||||
// if (step == 0) {
|
if (step == 0) {
|
||||||
// if (status != 200) {
|
if (status != 200) {
|
||||||
// System.out.println("❌ HEADER rejected, status=" + status);
|
System.out.println("❌ HEADER rejected, status=" + status);
|
||||||
// ws.sendClose(WebSocket.NORMAL_CLOSURE, "fail");
|
ws.sendClose(WebSocket.NORMAL_CLOSURE, "fail");
|
||||||
// return CompletableFuture.completedFuture(null);
|
return CompletableFuture.completedFuture(null);
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// // Берём ИМЕННО ТОТ хэш, который сервер сохранил в state
|
// Берём ИМЕННО ТОТ хэш, который сервер сохранил в state
|
||||||
// String serverLastGlobalHash = extractPayloadString(msg, "serverLastGlobalHash");
|
String serverLastGlobalHash = extractPayloadString(msg, "serverLastGlobalHash");
|
||||||
// String serverLastLineHash = extractPayloadString(msg, "serverLastLineHash");
|
String serverLastLineHash = extractPayloadString(msg, "serverLastLineHash");
|
||||||
//
|
|
||||||
// if (serverLastGlobalHash == null || serverLastGlobalHash.isBlank()) {
|
if (serverLastGlobalHash == null || serverLastGlobalHash.isBlank()) {
|
||||||
// System.out.println("❌ No serverLastGlobalHash in response");
|
System.out.println("❌ No serverLastGlobalHash in response");
|
||||||
// ws.sendClose(WebSocket.NORMAL_CLOSURE, "bad-response");
|
ws.sendClose(WebSocket.NORMAL_CLOSURE, "bad-response");
|
||||||
// return CompletableFuture.completedFuture(null);
|
return CompletableFuture.completedFuture(null);
|
||||||
// }
|
}
|
||||||
// if (serverLastLineHash == null || serverLastLineHash.isBlank()) {
|
if (serverLastLineHash == null || serverLastLineHash.isBlank()) {
|
||||||
// // fallback: пусть будет как global (если сервер так хранит)
|
// fallback: пусть будет как global (если сервер так хранит)
|
||||||
// serverLastLineHash = serverLastGlobalHash;
|
serverLastLineHash = serverLastGlobalHash;
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// lastGlobalHashHex = serverLastGlobalHash;
|
lastGlobalHashHex = serverLastGlobalHash;
|
||||||
// lastLineHashHex = serverLastLineHash;
|
lastLineHashHex = serverLastLineHash;
|
||||||
//
|
|
||||||
// byte[] prevGlobal32 = hexToBytes32(lastGlobalHashHex);
|
byte[] prevGlobal32 = hexToBytes32(lastGlobalHashHex);
|
||||||
// byte[] prevLine32 = hexToBytes32(lastLineHashHex);
|
byte[] prevLine32 = hexToBytes32(lastLineHashHex);
|
||||||
//
|
|
||||||
// // 2) TEXT (global=1, line=0, lineNumber=1)
|
// 2) TEXT (global=1, line=0, lineNumber=1)
|
||||||
// byte[] textFull = buildTextBlockFullBytes(
|
byte[] textFull = buildTextBlockFullBytes(
|
||||||
// /*global*/1,
|
/*global*/1,
|
||||||
// /*lineIndex*/(short)0,
|
/*lineIndex*/(short)0,
|
||||||
// /*lineBlock*/1,
|
/*lineBlock*/1,
|
||||||
// prevGlobal32,
|
prevGlobal32,
|
||||||
// prevLine32,
|
prevLine32,
|
||||||
// "Hello from test client"
|
"Hello from test client"
|
||||||
// );
|
);
|
||||||
//
|
|
||||||
// String json2 = buildAddBlockJson(
|
String json2 = buildAddBlockJson(
|
||||||
// "test-add-text",
|
"test-add-text",
|
||||||
// TEST_BCH_NAME,
|
TEST_BCH_NAME,
|
||||||
// 1,
|
1,
|
||||||
// lastGlobalHashHex, // prevGlobalHash = хэш header'а из ответа сервера
|
lastGlobalHashHex, // prevGlobalHash = хэш header'а из ответа сервера
|
||||||
// base64(textFull)
|
base64(textFull)
|
||||||
// );
|
);
|
||||||
//
|
|
||||||
// System.out.println("\n📤 SEND #2 (TEXT):\n" + json2);
|
System.out.println("\n📤 SEND #2 (TEXT):\n" + json2);
|
||||||
// step = 1;
|
step = 1;
|
||||||
// ws.sendText(json2, true);
|
ws.sendText(json2, true);
|
||||||
//
|
|
||||||
// } else if (step == 1) {
|
} else if (step == 1) {
|
||||||
// if (status != 200) {
|
if (status != 200) {
|
||||||
// System.out.println("❌ TEXT rejected, status=" + status);
|
System.out.println("❌ TEXT rejected, status=" + status);
|
||||||
// } else {
|
} else {
|
||||||
// System.out.println("✅ Done. Closing.");
|
System.out.println("✅ Done. Closing.");
|
||||||
// }
|
}
|
||||||
// ws.sendClose(WebSocket.NORMAL_CLOSURE, "ok");
|
ws.sendClose(WebSocket.NORMAL_CLOSURE, "ok");
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// } catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// e.printStackTrace(System.out);
|
e.printStackTrace(System.out);
|
||||||
// ws.sendClose(WebSocket.NORMAL_CLOSURE, "exception");
|
ws.sendClose(WebSocket.NORMAL_CLOSURE, "exception");
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// ws.request(1);
|
ws.request(1);
|
||||||
// return CompletableFuture.completedFuture(null);
|
return CompletableFuture.completedFuture(null);
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// @Override
|
@Override
|
||||||
// public void onError(WebSocket ws, Throwable error) {
|
public void onError(WebSocket ws, Throwable error) {
|
||||||
// System.out.println("❌ WS error: " + error.getMessage());
|
System.out.println("❌ WS error: " + error.getMessage());
|
||||||
// error.printStackTrace(System.out);
|
error.printStackTrace(System.out);
|
||||||
// latch.countDown();
|
latch.countDown();
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// @Override
|
@Override
|
||||||
// public CompletionStage<?> onClose(WebSocket ws, int statusCode, String reason) {
|
public CompletionStage<?> onClose(WebSocket ws, int statusCode, String reason) {
|
||||||
// System.out.println("🔚 WS closed. code=" + statusCode + " reason=" + reason);
|
System.out.println("🔚 WS closed. code=" + statusCode + " reason=" + reason);
|
||||||
// latch.countDown();
|
latch.countDown();
|
||||||
// return CompletableFuture.completedFuture(null);
|
return CompletableFuture.completedFuture(null);
|
||||||
// }
|
}
|
||||||
// }).join();
|
}).join();
|
||||||
//
|
|
||||||
// latch.await();
|
latch.await();
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// // =================================================================================
|
// =================================================================================
|
||||||
// // BUILD BLOCKS
|
// BUILD BLOCKS
|
||||||
// // =================================================================================
|
// =================================================================================
|
||||||
//
|
|
||||||
// private static byte[] buildHeaderBlockFullBytes(int globalNumber,
|
private static byte[] buildHeaderBlockFullBytes(int globalNumber,
|
||||||
// short lineIndex,
|
short lineIndex,
|
||||||
// int lineBlockNumber,
|
int lineBlockNumber,
|
||||||
// byte[] prevGlobalHash32,
|
byte[] prevGlobalHash32,
|
||||||
// byte[] prevLineHash32) {
|
byte[] prevLineHash32) {
|
||||||
//
|
|
||||||
// HeaderBody body = new HeaderBody(
|
HeaderBody body = new HeaderBody(
|
||||||
// TEST_BCH_NAME, // было TEST_BCH_ID (long), теперь имя блокчейна (String)
|
TEST_LOGIN
|
||||||
// TEST_LOGIN,
|
);
|
||||||
// 0, 0,
|
byte[] bodyBytes = body.toBytes();
|
||||||
// (short) 1,
|
|
||||||
// 0L,
|
return buildSignedBlockFullBytes(globalNumber, lineIndex, lineBlockNumber, bodyBytes, prevGlobalHash32, prevLineHash32);
|
||||||
// LOGIN_PUB_KEY
|
}
|
||||||
// );
|
|
||||||
// byte[] bodyBytes = body.toBytes();
|
private static byte[] buildTextBlockFullBytes(int globalNumber,
|
||||||
//
|
short lineIndex,
|
||||||
// return buildSignedBlockFullBytes(globalNumber, lineIndex, lineBlockNumber, bodyBytes, prevGlobalHash32, prevLineHash32);
|
int lineBlockNumber,
|
||||||
// }
|
byte[] prevGlobalHash32,
|
||||||
//
|
byte[] prevLineHash32,
|
||||||
// private static byte[] buildTextBlockFullBytes(int globalNumber,
|
String text) {
|
||||||
// short lineIndex,
|
|
||||||
// int lineBlockNumber,
|
TextBody body = new TextBody(text);
|
||||||
// byte[] prevGlobalHash32,
|
byte[] bodyBytes = body.toBytes();
|
||||||
// byte[] prevLineHash32,
|
|
||||||
// String text) {
|
return buildSignedBlockFullBytes(globalNumber, lineIndex, lineBlockNumber, bodyBytes, prevGlobalHash32, prevLineHash32);
|
||||||
//
|
}
|
||||||
// TextBody body = new TextBody(text);
|
|
||||||
// byte[] bodyBytes = body.toBytes();
|
private static byte[] buildSignedBlockFullBytes(int globalNumber,
|
||||||
//
|
short lineIndex,
|
||||||
// return buildSignedBlockFullBytes(globalNumber, lineIndex, lineBlockNumber, bodyBytes, prevGlobalHash32, prevLineHash32);
|
int lineBlockNumber,
|
||||||
// }
|
byte[] bodyBytes,
|
||||||
//
|
byte[] prevGlobalHash32,
|
||||||
// private static byte[] buildSignedBlockFullBytes(int globalNumber,
|
byte[] prevLineHash32) {
|
||||||
// short lineIndex,
|
|
||||||
// int lineBlockNumber,
|
long ts = System.currentTimeMillis() / 1000L;
|
||||||
// byte[] bodyBytes,
|
|
||||||
// byte[] prevGlobalHash32,
|
int recordSize =
|
||||||
// byte[] prevLineHash32) {
|
BchBlockEntry.RAW_HEADER_SIZE +
|
||||||
//
|
bodyBytes.length +
|
||||||
// long ts = System.currentTimeMillis() / 1000L;
|
BchBlockEntry.SIGNATURE_LEN +
|
||||||
//
|
BchBlockEntry.HASH_LEN;
|
||||||
// int recordSize =
|
|
||||||
// BchBlockEntry_new.RAW_HEADER_SIZE +
|
byte[] rawBytes = ByteBuffer.allocate(BchBlockEntry.RAW_HEADER_SIZE + bodyBytes.length)
|
||||||
// bodyBytes.length +
|
.order(ByteOrder.BIG_ENDIAN)
|
||||||
// BchBlockEntry_new.SIGNATURE_LEN +
|
.putInt(recordSize)
|
||||||
// BchBlockEntry_new.HASH_LEN;
|
.putInt(globalNumber)
|
||||||
//
|
.putLong(ts)
|
||||||
// byte[] rawBytes = ByteBuffer.allocate(BchBlockEntry_new.RAW_HEADER_SIZE + bodyBytes.length)
|
.putShort(lineIndex)
|
||||||
// .order(ByteOrder.BIG_ENDIAN)
|
.putInt(lineBlockNumber)
|
||||||
// .putInt(recordSize)
|
.put(bodyBytes)
|
||||||
// .putInt(globalNumber)
|
.array();
|
||||||
// .putLong(ts)
|
|
||||||
// .putShort(lineIndex)
|
byte[] preimage = BchCryptoVerifier.buildPreimage(
|
||||||
// .putInt(lineBlockNumber)
|
TEST_LOGIN,
|
||||||
// .put(bodyBytes)
|
prevGlobalHash32,
|
||||||
// .array();
|
prevLineHash32,
|
||||||
//
|
rawBytes
|
||||||
// byte[] preimage = BchCryptoVerifier_new.buildPreimage(
|
);
|
||||||
// TEST_LOGIN,
|
|
||||||
// prevGlobalHash32,
|
byte[] hash32 = BchCryptoVerifier.sha256(preimage);
|
||||||
// prevLineHash32,
|
|
||||||
// rawBytes
|
// если у тебя подпись должна быть по preimage — меняй тут
|
||||||
// );
|
byte[] signature64 = Ed25519Util.sign(hash32, LOGIN_PRIV_KEY);
|
||||||
//
|
|
||||||
// byte[] hash32 = BchCryptoVerifier_new.sha256(preimage);
|
return new BchBlockEntry(
|
||||||
//
|
globalNumber,
|
||||||
// // если у тебя подпись должна быть по preimage — меняй тут
|
ts,
|
||||||
// byte[] signature64 = Ed25519Util.sign(hash32, LOGIN_PRIV_KEY);
|
lineIndex,
|
||||||
//
|
lineBlockNumber,
|
||||||
// return new BchBlockEntry_new(
|
bodyBytes,
|
||||||
// globalNumber,
|
signature64,
|
||||||
// ts,
|
hash32
|
||||||
// lineIndex,
|
).toBytes();
|
||||||
// lineBlockNumber,
|
}
|
||||||
// bodyBytes,
|
|
||||||
// signature64,
|
// =================================================================================
|
||||||
// hash32
|
// JSON BUILD
|
||||||
// ).toBytes();
|
// =================================================================================
|
||||||
// }
|
|
||||||
//
|
private static String buildAddBlockJson(String requestId,
|
||||||
// // =================================================================================
|
String blockchainName,
|
||||||
// // JSON BUILD
|
int globalNumber,
|
||||||
// // =================================================================================
|
String prevGlobalHashHex,
|
||||||
//
|
String blockBytesB64) {
|
||||||
// private static String buildAddBlockJson(String requestId,
|
return """
|
||||||
// String blockchainName,
|
{
|
||||||
// int globalNumber,
|
"op": "AddBlock",
|
||||||
// String prevGlobalHashHex,
|
"requestId": "%s",
|
||||||
// String blockBytesB64) {
|
"payload": {
|
||||||
// return """
|
"login": "%s",
|
||||||
// {
|
"blockchainName": "%s",
|
||||||
// "op": "AddBlock",
|
"globalNumber": %d,
|
||||||
// "requestId": "%s",
|
"prevGlobalHash": "%s",
|
||||||
// "payload": {
|
"blockBytesB64": "%s"
|
||||||
// "login": "%s",
|
}
|
||||||
// "blockchainName": "%s",
|
}
|
||||||
// "globalNumber": %d,
|
""".formatted(requestId, TEST_LOGIN, blockchainName, globalNumber, prevGlobalHashHex, blockBytesB64);
|
||||||
// "prevGlobalHash": "%s",
|
}
|
||||||
// "blockBytesB64": "%s"
|
|
||||||
// }
|
// =================================================================================
|
||||||
// }
|
// HELPERS
|
||||||
// """.formatted(requestId, TEST_LOGIN, blockchainName, globalNumber, prevGlobalHashHex, blockBytesB64);
|
// =================================================================================
|
||||||
// }
|
|
||||||
//
|
private static int extractStatus(String json) {
|
||||||
// // =================================================================================
|
try {
|
||||||
// // HELPERS
|
JsonNode root = JSON.readTree(json);
|
||||||
// // =================================================================================
|
if (root.has("status")) return root.get("status").asInt();
|
||||||
//
|
} catch (Exception ignore) {}
|
||||||
// private static int extractStatus(String json) {
|
return -1;
|
||||||
// try {
|
}
|
||||||
// JsonNode root = JSON.readTree(json);
|
|
||||||
// if (root.has("status")) return root.get("status").asInt();
|
private static String extractPayloadString(String json, String field) {
|
||||||
// } catch (Exception ignore) {}
|
try {
|
||||||
// return -1;
|
JsonNode root = JSON.readTree(json);
|
||||||
// }
|
JsonNode payload = root.get("payload");
|
||||||
//
|
if (payload != null && payload.has(field)) {
|
||||||
// private static String extractPayloadString(String json, String field) {
|
return payload.get(field).asText();
|
||||||
// try {
|
}
|
||||||
// JsonNode root = JSON.readTree(json);
|
} catch (Exception ignore) {}
|
||||||
// JsonNode payload = root.get("payload");
|
return null;
|
||||||
// if (payload != null && payload.has(field)) {
|
}
|
||||||
// return payload.get(field).asText();
|
|
||||||
// }
|
private static String base64(byte[] bytes) {
|
||||||
// } catch (Exception ignore) {}
|
return Base64.getEncoder().encodeToString(bytes);
|
||||||
// return null;
|
}
|
||||||
// }
|
|
||||||
//
|
private static byte[] hexToBytes32(String hex) {
|
||||||
// private static String base64(byte[] bytes) {
|
if (hex == null) throw new IllegalArgumentException("hex is null");
|
||||||
// return Base64.getEncoder().encodeToString(bytes);
|
String s = hex.trim();
|
||||||
// }
|
if (s.length() != 64) throw new IllegalArgumentException("hex must be 64 chars, got " + s.length());
|
||||||
//
|
byte[] out = new byte[32];
|
||||||
// private static byte[] hexToBytes32(String hex) {
|
for (int i = 0; i < 32; i++) {
|
||||||
// if (hex == null) throw new IllegalArgumentException("hex is null");
|
int hi = Character.digit(s.charAt(i * 2), 16);
|
||||||
// String s = hex.trim();
|
int lo = Character.digit(s.charAt(i * 2 + 1), 16);
|
||||||
// if (s.length() != 64) throw new IllegalArgumentException("hex must be 64 chars, got " + s.length());
|
if (hi < 0 || lo < 0) throw new IllegalArgumentException("bad hex at pos " + (i * 2));
|
||||||
// byte[] out = new byte[32];
|
out[i] = (byte) ((hi << 4) | lo);
|
||||||
// for (int i = 0; i < 32; i++) {
|
}
|
||||||
// int hi = Character.digit(s.charAt(i * 2), 16);
|
return out;
|
||||||
// int lo = Character.digit(s.charAt(i * 2 + 1), 16);
|
}
|
||||||
// if (hi < 0 || lo < 0) throw new IllegalArgumentException("bad hex at pos " + (i * 2));
|
}
|
||||||
// out[i] = (byte) ((hi << 4) | lo);
|
|
||||||
// }
|
|
||||||
// return out;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ public final class TestConfig {
|
|||||||
|
|
||||||
public static final String WS_URI = "ws://localhost:7070/ws";
|
public static final String WS_URI = "ws://localhost:7070/ws";
|
||||||
public static final String TEST_LOGIN = "anya24";
|
public static final String TEST_LOGIN = "anya24";
|
||||||
public static final String TEST_BCH_NAME = TEST_LOGIN + "0001";
|
public static final String TEST_BCH_NAME = TEST_LOGIN + "001";
|
||||||
public static final int TEST_BCH_LIMIT = 1_000_000;
|
public static final int TEST_BCH_LIMIT = 1_000_000;
|
||||||
public static final String TEST_CLIENT_INFO = "JavaTestClient/1.0";
|
public static final String TEST_CLIENT_INFO = "JavaTestClient/1.0";
|
||||||
|
|
||||||
|
|||||||
301
src/test/java/test/it/ws/Test_AddBlock_new_NoAuth.java
Normal file
301
src/test/java/test/it/ws/Test_AddBlock_new_NoAuth.java
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
package test.it.ws;
|
||||||
|
|
||||||
|
import blockchain.BchBlockEntry;
|
||||||
|
import blockchain.BchCryptoVerifier;
|
||||||
|
import blockchain.body.HeaderBody;
|
||||||
|
import blockchain.body.TextBody;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import test.it.utils.TestConfig;
|
||||||
|
import utils.crypto.Ed25519Util;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.WebSocket;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionStage;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
public class Test_AddBlock_new_NoAuth {
|
||||||
|
|
||||||
|
private static final ObjectMapper JSON = new ObjectMapper();
|
||||||
|
|
||||||
|
private static final byte[] ZERO32 = new byte[32];
|
||||||
|
private static final String ZERO64 = "0".repeat(64);
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
client.newWebSocketBuilder()
|
||||||
|
.buildAsync(URI.create(TestConfig.WS_URI), new WebSocket.Listener() {
|
||||||
|
|
||||||
|
private int step = 0;
|
||||||
|
|
||||||
|
private String lastGlobalHashHex = ZERO64;
|
||||||
|
private String lastLineHashHex = ZERO64;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen(WebSocket ws) {
|
||||||
|
System.out.println("✅ WS connected: " + TestConfig.WS_URI);
|
||||||
|
ws.request(1);
|
||||||
|
|
||||||
|
// 1) HEADER block: global=0, line=0, lineNumber=0
|
||||||
|
byte[] headerFull = buildHeaderBlockFullBytes(
|
||||||
|
0,
|
||||||
|
(short) 0,
|
||||||
|
0,
|
||||||
|
ZERO32,
|
||||||
|
ZERO32
|
||||||
|
);
|
||||||
|
|
||||||
|
String json = buildAddBlockJson(
|
||||||
|
"test-add-header",
|
||||||
|
TestConfig.TEST_LOGIN,
|
||||||
|
TestConfig.TEST_BCH_NAME,
|
||||||
|
0,
|
||||||
|
ZERO64,
|
||||||
|
base64(headerFull)
|
||||||
|
);
|
||||||
|
|
||||||
|
System.out.println("\n📤 SEND #1 (HEADER):\n" + json);
|
||||||
|
ws.sendText(json, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletionStage<?> onText(WebSocket ws, CharSequence data, boolean last) {
|
||||||
|
String msg = data.toString();
|
||||||
|
System.out.println("\n📥 RECV:\n" + msg);
|
||||||
|
System.out.println("-----------------------------------------------------");
|
||||||
|
|
||||||
|
try {
|
||||||
|
int status = extractStatus(msg);
|
||||||
|
|
||||||
|
if (step == 0) {
|
||||||
|
if (status != 200) {
|
||||||
|
System.out.println("❌ HEADER rejected, status=" + status);
|
||||||
|
ws.sendClose(WebSocket.NORMAL_CLOSURE, "fail");
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
String serverLastGlobalHash = extractPayloadString(msg, "serverLastGlobalHash");
|
||||||
|
String serverLastLineHash = extractPayloadString(msg, "serverLastLineHash");
|
||||||
|
|
||||||
|
if (serverLastGlobalHash == null || serverLastGlobalHash.isBlank()) {
|
||||||
|
System.out.println("❌ No serverLastGlobalHash in response");
|
||||||
|
ws.sendClose(WebSocket.NORMAL_CLOSURE, "bad-response");
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
}
|
||||||
|
if (serverLastLineHash == null || serverLastLineHash.isBlank()) {
|
||||||
|
serverLastLineHash = serverLastGlobalHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastGlobalHashHex = serverLastGlobalHash;
|
||||||
|
lastLineHashHex = serverLastLineHash;
|
||||||
|
|
||||||
|
byte[] prevGlobal32 = hexToBytes32(lastGlobalHashHex);
|
||||||
|
byte[] prevLine32 = hexToBytes32(lastLineHashHex);
|
||||||
|
|
||||||
|
// 2) TEXT block: global=1, line=0, lineNumber=1
|
||||||
|
byte[] textFull = buildTextBlockFullBytes(
|
||||||
|
1,
|
||||||
|
(short) 0,
|
||||||
|
1,
|
||||||
|
prevGlobal32,
|
||||||
|
prevLine32,
|
||||||
|
"Hello from test client"
|
||||||
|
);
|
||||||
|
|
||||||
|
String json2 = buildAddBlockJson(
|
||||||
|
"test-add-text",
|
||||||
|
TestConfig.TEST_LOGIN,
|
||||||
|
TestConfig.TEST_BCH_NAME,
|
||||||
|
1,
|
||||||
|
lastGlobalHashHex,
|
||||||
|
base64(textFull)
|
||||||
|
);
|
||||||
|
|
||||||
|
System.out.println("\n📤 SEND #2 (TEXT):\n" + json2);
|
||||||
|
step = 1;
|
||||||
|
ws.sendText(json2, true);
|
||||||
|
|
||||||
|
} else if (step == 1) {
|
||||||
|
if (status != 200) {
|
||||||
|
System.out.println("❌ TEXT rejected, status=" + status);
|
||||||
|
} else {
|
||||||
|
System.out.println("✅ Done. Closing.");
|
||||||
|
}
|
||||||
|
ws.sendClose(WebSocket.NORMAL_CLOSURE, "ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace(System.out);
|
||||||
|
ws.sendClose(WebSocket.NORMAL_CLOSURE, "exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.request(1);
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(WebSocket ws, Throwable error) {
|
||||||
|
System.out.println("❌ WS error: " + error.getMessage());
|
||||||
|
error.printStackTrace(System.out);
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletionStage<?> onClose(WebSocket ws, int statusCode, String reason) {
|
||||||
|
System.out.println("🔚 WS closed. code=" + statusCode + " reason=" + reason);
|
||||||
|
latch.countDown();
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}).join();
|
||||||
|
|
||||||
|
latch.await();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================================================
|
||||||
|
// BUILD BLOCKS
|
||||||
|
// =================================================================================
|
||||||
|
|
||||||
|
private static byte[] buildHeaderBlockFullBytes(int globalNumber,
|
||||||
|
short lineIndex,
|
||||||
|
int lineBlockNumber,
|
||||||
|
byte[] prevGlobalHash32,
|
||||||
|
byte[] prevLineHash32) {
|
||||||
|
|
||||||
|
// В твоём текущем коде HeaderBody формата type=0 ver=1:
|
||||||
|
// [type][ver][tag "SHiNE001"][loginLen][login]
|
||||||
|
HeaderBody body = new HeaderBody(TestConfig.TEST_LOGIN);
|
||||||
|
byte[] bodyBytes = body.toBytes();
|
||||||
|
|
||||||
|
return buildSignedBlockFullBytes(globalNumber, lineIndex, lineBlockNumber, bodyBytes, prevGlobalHash32, prevLineHash32);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] buildTextBlockFullBytes(int globalNumber,
|
||||||
|
short lineIndex,
|
||||||
|
int lineBlockNumber,
|
||||||
|
byte[] prevGlobalHash32,
|
||||||
|
byte[] prevLineHash32,
|
||||||
|
String text) {
|
||||||
|
|
||||||
|
TextBody body = new TextBody(text);
|
||||||
|
byte[] bodyBytes = body.toBytes();
|
||||||
|
|
||||||
|
return buildSignedBlockFullBytes(globalNumber, lineIndex, lineBlockNumber, bodyBytes, prevGlobalHash32, prevLineHash32);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] buildSignedBlockFullBytes(int globalNumber,
|
||||||
|
short lineIndex,
|
||||||
|
int lineBlockNumber,
|
||||||
|
byte[] bodyBytes,
|
||||||
|
byte[] prevGlobalHash32,
|
||||||
|
byte[] prevLineHash32) {
|
||||||
|
|
||||||
|
long ts = System.currentTimeMillis() / 1000L;
|
||||||
|
|
||||||
|
// recordSize = только RAW = header + body
|
||||||
|
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(lineBlockNumber)
|
||||||
|
.put(bodyBytes)
|
||||||
|
.array();
|
||||||
|
|
||||||
|
byte[] preimage = BchCryptoVerifier.buildPreimage(
|
||||||
|
TestConfig.TEST_LOGIN,
|
||||||
|
prevGlobalHash32,
|
||||||
|
prevLineHash32,
|
||||||
|
rawBytes
|
||||||
|
);
|
||||||
|
|
||||||
|
byte[] hash32 = BchCryptoVerifier.sha256(preimage);
|
||||||
|
byte[] signature64 = Ed25519Util.sign(hash32, TestConfig.LOGIN_PRIV_KEY);
|
||||||
|
|
||||||
|
return new BchBlockEntry(
|
||||||
|
globalNumber,
|
||||||
|
ts,
|
||||||
|
lineIndex,
|
||||||
|
lineBlockNumber,
|
||||||
|
bodyBytes,
|
||||||
|
signature64,
|
||||||
|
hash32
|
||||||
|
).toBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================================================
|
||||||
|
// JSON BUILD
|
||||||
|
// =================================================================================
|
||||||
|
|
||||||
|
private static String buildAddBlockJson(String requestId,
|
||||||
|
String login,
|
||||||
|
String blockchainName,
|
||||||
|
int globalNumber,
|
||||||
|
String prevGlobalHashHex,
|
||||||
|
String blockBytesB64) {
|
||||||
|
return """
|
||||||
|
{
|
||||||
|
"op": "AddBlock",
|
||||||
|
"requestId": "%s",
|
||||||
|
"payload": {
|
||||||
|
"login": "%s",
|
||||||
|
"blockchainName": "%s",
|
||||||
|
"globalNumber": %d,
|
||||||
|
"prevGlobalHash": "%s",
|
||||||
|
"blockBytesB64": "%s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".formatted(requestId, login, blockchainName, globalNumber, prevGlobalHashHex, blockBytesB64);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================================================
|
||||||
|
// HELPERS
|
||||||
|
// =================================================================================
|
||||||
|
|
||||||
|
private static int extractStatus(String json) {
|
||||||
|
try {
|
||||||
|
JsonNode root = JSON.readTree(json);
|
||||||
|
if (root.has("status")) return root.get("status").asInt();
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractPayloadString(String json, String field) {
|
||||||
|
try {
|
||||||
|
JsonNode root = JSON.readTree(json);
|
||||||
|
JsonNode payload = root.get("payload");
|
||||||
|
if (payload != null && payload.has(field)) {
|
||||||
|
return payload.get(field).asText();
|
||||||
|
}
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String base64(byte[] bytes) {
|
||||||
|
return Base64.getEncoder().encodeToString(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] hexToBytes32(String hex) {
|
||||||
|
if (hex == null) throw new IllegalArgumentException("hex is null");
|
||||||
|
String s = hex.trim();
|
||||||
|
if (s.length() != 64) throw new IllegalArgumentException("hex must be 64 chars, got " + s.length());
|
||||||
|
byte[] out = new byte[32];
|
||||||
|
for (int i = 0; i < 32; i++) {
|
||||||
|
int hi = Character.digit(s.charAt(i * 2), 16);
|
||||||
|
int lo = Character.digit(s.charAt(i * 2 + 1), 16);
|
||||||
|
if (hi < 0 || lo < 0) throw new IllegalArgumentException("bad hex at pos " + (i * 2));
|
||||||
|
out[i] = (byte) ((hi << 4) | lo);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user