25 12 25
Сделал три теста на общий формат
This commit is contained in:
parent
25aa57dc5e
commit
eeb8ee9069
@ -10,7 +10,7 @@ import java.time.Duration;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class AddUserIT {
|
||||
public class IT_01_AddUser {
|
||||
|
||||
// ANSI цвета (работает в большинстве терминалов)
|
||||
private static final String R = "\u001B[0m";
|
||||
@ -12,7 +12,7 @@ import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class SessionsIT {
|
||||
public class IT_02_Sessions {
|
||||
|
||||
// ANSI цвета
|
||||
private static final String R = "\u001B[0m";
|
||||
337
src/test/java/test/it/ws/IT_03_AddBlock_NoAuth.java
Normal file
337
src/test/java/test/it/ws/IT_03_AddBlock_NoAuth.java
Normal file
@ -0,0 +1,337 @@
|
||||
package test.it;
|
||||
|
||||
import blockchain.BchBlockEntry;
|
||||
import blockchain.BchCryptoVerifier;
|
||||
import blockchain.body.HeaderBody;
|
||||
import blockchain.body.TextBody;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import test.it.utils.JsonBuilders;
|
||||
import test.it.utils.JsonParsers;
|
||||
import test.it.utils.TestConfig;
|
||||
import test.it.utils.WsTestClient;
|
||||
import utils.crypto.Ed25519Util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* IT_03_AddBlock_NoAuth
|
||||
*
|
||||
* Интеграционный тест добавления блоков в персональный блокчейн без отдельной авторизации,
|
||||
* в формате твоих IT-тестов (ANSI, шаги, WsTestClient, JsonBuilders/JsonParsers).
|
||||
*
|
||||
* Сценарий:
|
||||
* 1) AddBlock: HEADER (global=0, prevGlobalHash=ZERO64) -> ожидаем 200
|
||||
* - забираем payload.serverLastGlobalHash
|
||||
* 2) AddBlock: TEXT (global=1, prevGlobalHash=serverLastGlobalHash) -> ожидаем 200
|
||||
*
|
||||
* Примечание:
|
||||
* - lastLineHash пока равен lastGlobalHash (как ты говорил).
|
||||
*/
|
||||
public class IT_03_AddBlock_NoAuth {
|
||||
|
||||
// ANSI цвета (работает в большинстве терминалов)
|
||||
private static final String R = "\u001B[0m";
|
||||
private static final String G = "\u001B[32m";
|
||||
private static final String Y = "\u001B[33m";
|
||||
private static final String RED = "\u001B[31m";
|
||||
private static final String C = "\u001B[36m";
|
||||
|
||||
private static final byte[] ZERO32 = new byte[32];
|
||||
private static final String ZERO64 = "0".repeat(64);
|
||||
|
||||
private static void line() {
|
||||
System.out.println(C + "------------------------------------------------------------" + R);
|
||||
}
|
||||
|
||||
private static void title(String s) {
|
||||
System.out.println(C + "\n============================================================" + R);
|
||||
System.out.println(C + s + R);
|
||||
System.out.println(C + "============================================================\n" + R);
|
||||
}
|
||||
|
||||
private static void stepTitle(String s) {
|
||||
System.out.println(C + "\n-------------------- " + s + " --------------------" + R);
|
||||
}
|
||||
|
||||
private static void ok(String s) {
|
||||
System.out.println(G + "✅ " + s + R);
|
||||
}
|
||||
|
||||
private static void warn(String s) {
|
||||
System.out.println(Y + "⚠️ " + s + R);
|
||||
}
|
||||
|
||||
private static void boom(String s) {
|
||||
System.out.println(RED + "****************************************************************" + R);
|
||||
System.out.println(RED + "❌ " + s + R);
|
||||
System.out.println(RED + "****************************************************************" + R);
|
||||
}
|
||||
|
||||
private static void send(String op, String json) {
|
||||
System.out.println("📤 [" + op + "] Request JSON:");
|
||||
System.out.println(json);
|
||||
line();
|
||||
}
|
||||
|
||||
private static void recv(String op, String json) {
|
||||
System.out.println("📥 [" + op + "] Response JSON:");
|
||||
System.out.println(json);
|
||||
line();
|
||||
}
|
||||
|
||||
private static void assert200(String op, String resp) {
|
||||
int st = JsonParsers.status(resp);
|
||||
try {
|
||||
assertEquals(200, st, op + ": expected status=200, but got=" + st + ", resp=" + resp);
|
||||
ok(op + ": status=200");
|
||||
} catch (AssertionError ae) {
|
||||
boom(op + ": ожидали 200, но получили " + st);
|
||||
throw ae;
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
static void ensureUserExists() {
|
||||
title("AddBlockIT (BeforeAll): предусловие — пользователь должен существовать (AddUser: 200 или 409)");
|
||||
|
||||
try (WsTestClient client = new WsTestClient(TestConfig.WS_URI)) {
|
||||
String reqId = "it03-adduser-beforeall";
|
||||
String reqJson = JsonBuilders.addUser(reqId);
|
||||
|
||||
send("AddUser(BeforeAll)", reqJson);
|
||||
String resp = client.request(reqId, reqJson, Duration.ofSeconds(5));
|
||||
recv("AddUser(BeforeAll)", resp);
|
||||
|
||||
int st = JsonParsers.status(resp);
|
||||
|
||||
if (st == 200) {
|
||||
ok("BeforeAll: пользователь создан/добавлен (status=200)");
|
||||
} else if (st == 409) {
|
||||
String code = JsonParsers.errorCode(resp);
|
||||
if ("USER_ALREADY_EXISTS".equals(code)) {
|
||||
ok("BeforeAll: пользователь уже есть (status=409, USER_ALREADY_EXISTS)");
|
||||
} else {
|
||||
boom("BeforeAll: status=409, но code неожиданный: " + code);
|
||||
fail("User precondition failed. status=409, code=" + code + ", resp=" + resp);
|
||||
}
|
||||
} else {
|
||||
boom("BeforeAll: предусловие не выполнено. status=" + st);
|
||||
fail("User precondition failed. status=" + st + ", resp=" + resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void addBlock_shouldAppendHeaderThenText() {
|
||||
title("AddBlockIT: добавить HEADER(0) и затем TEXT(1) без auth — с проверкой serverLastGlobalHash");
|
||||
System.out.println("Ожидание:");
|
||||
System.out.println(" 1) AddBlock HEADER (global=0, prev=ZERO64) -> 200");
|
||||
System.out.println(" - в ответе payload.serverLastGlobalHash (64 hex)");
|
||||
System.out.println(" 2) AddBlock TEXT (global=1, prev=serverLastGlobalHash) -> 200\n");
|
||||
|
||||
try (WsTestClient client = new WsTestClient(TestConfig.WS_URI)) {
|
||||
|
||||
// =================================================================================
|
||||
// ШАГ 1: HEADER (global=0)
|
||||
// =================================================================================
|
||||
stepTitle("ШАГ 1: AddBlock HEADER (global=0)");
|
||||
|
||||
byte[] headerFull = buildHeaderBlockFullBytes(
|
||||
0, // globalNumber
|
||||
(short) 0, // lineIndex
|
||||
0, // lineBlockNumber
|
||||
ZERO32, // prevGlobalHash32
|
||||
ZERO32 // prevLineHash32 (пока равно prevGlobal)
|
||||
);
|
||||
|
||||
String reqId1 = "it03-add-header";
|
||||
String reqJson1 = buildAddBlockJson(reqId1, TestConfig.TEST_BCH_NAME, 0, ZERO64, base64(headerFull));
|
||||
|
||||
send("AddBlock#HEADER", reqJson1);
|
||||
String resp1 = client.request(reqId1, reqJson1, Duration.ofSeconds(8));
|
||||
recv("AddBlock#HEADER", resp1);
|
||||
|
||||
assert200("AddBlock#HEADER", resp1);
|
||||
|
||||
String serverLastGlobalHash = extractPayloadString(resp1, "serverLastGlobalHash");
|
||||
assertNotNull(serverLastGlobalHash, "HEADER: payload.serverLastGlobalHash must not be null");
|
||||
assertFalse(serverLastGlobalHash.isBlank(), "HEADER: payload.serverLastGlobalHash must not be blank");
|
||||
assertEquals(64, serverLastGlobalHash.trim().length(), "HEADER: serverLastGlobalHash must be 64 hex chars");
|
||||
|
||||
ok("HEADER принят. serverLastGlobalHash=" + serverLastGlobalHash);
|
||||
|
||||
// =================================================================================
|
||||
// ШАГ 2: TEXT (global=1)
|
||||
// =================================================================================
|
||||
stepTitle("ШАГ 2: AddBlock TEXT (global=1)");
|
||||
|
||||
byte[] prevGlobal32 = hexToBytes32(serverLastGlobalHash);
|
||||
byte[] prevLine32 = prevGlobal32; // пока lineHash = globalHash
|
||||
|
||||
byte[] textFull = buildTextBlockFullBytes(
|
||||
1, // globalNumber
|
||||
(short) 0, // lineIndex
|
||||
1, // lineBlockNumber
|
||||
prevGlobal32,
|
||||
prevLine32,
|
||||
"Hello from IT_03 test"
|
||||
);
|
||||
|
||||
String reqId2 = "it03-add-text";
|
||||
String reqJson2 = buildAddBlockJson(reqId2, TestConfig.TEST_BCH_NAME, 1, serverLastGlobalHash, base64(textFull));
|
||||
|
||||
send("AddBlock#TEXT", reqJson2);
|
||||
String resp2 = client.request(reqId2, reqJson2, Duration.ofSeconds(8));
|
||||
recv("AddBlock#TEXT", resp2);
|
||||
|
||||
assert200("AddBlock#TEXT", resp2);
|
||||
|
||||
ok("ТЕСТ ПРОЙДЕН: AddBlock HEADER(0) + TEXT(1) успешно добавлены");
|
||||
|
||||
} catch (AssertionError | RuntimeException e) {
|
||||
boom("ТЕСТ УПАЛ: AddBlockIT. Причина: " + e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================================
|
||||
// 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 (без подписи/хэша — это внутренняя "raw"-часть записи)
|
||||
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 blockchainName,
|
||||
int globalNumber,
|
||||
String prevGlobalHashHex,
|
||||
String blockBytesB64) {
|
||||
return """
|
||||
{
|
||||
"op": "AddBlock",
|
||||
"requestId": "%s",
|
||||
"payload": {
|
||||
"blockchainName": "%s",
|
||||
"globalNumber": %d,
|
||||
"prevGlobalHash": "%s",
|
||||
"blockBytesB64": "%s"
|
||||
}
|
||||
}
|
||||
""".formatted(requestId, blockchainName, globalNumber, prevGlobalHashHex, blockBytesB64);
|
||||
}
|
||||
|
||||
// =================================================================================
|
||||
// HELPERS
|
||||
// =================================================================================
|
||||
|
||||
private static String extractPayloadString(String json, String field) {
|
||||
try {
|
||||
// JsonParsers у тебя уже есть, но тут проще и не ломать совместимость:
|
||||
// Если захочешь — можем добавить в JsonParsers отдельный метод payloadString(...)
|
||||
com.fasterxml.jackson.databind.JsonNode root =
|
||||
new com.fasterxml.jackson.databind.ObjectMapper().readTree(json);
|
||||
com.fasterxml.jackson.databind.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;
|
||||
}
|
||||
}
|
||||
@ -1,291 +0,0 @@
|
||||
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_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;
|
||||
|
||||
@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_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");
|
||||
|
||||
if (serverLastGlobalHash == null || serverLastGlobalHash.isBlank()) {
|
||||
System.out.println("❌ No serverLastGlobalHash in response");
|
||||
ws.sendClose(WebSocket.NORMAL_CLOSURE, "bad-response");
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
lastGlobalHashHex = serverLastGlobalHash;
|
||||
|
||||
byte[] prevGlobal32 = hexToBytes32(lastGlobalHashHex);
|
||||
byte[] prevLine32 = prevGlobal32;
|
||||
|
||||
// 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_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 blockchainName,
|
||||
int globalNumber,
|
||||
String prevGlobalHashHex,
|
||||
String blockBytesB64) {
|
||||
return """
|
||||
{
|
||||
"op": "AddBlock",
|
||||
"requestId": "%s",
|
||||
"payload": {
|
||||
"blockchainName": "%s",
|
||||
"globalNumber": %d,
|
||||
"prevGlobalHash": "%s",
|
||||
"blockBytesB64": "%s"
|
||||
}
|
||||
}
|
||||
""".formatted(requestId, 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