@qubic.ts/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/package.json +37 -0
  2. package/scripts/verify-browser.mjs +15 -0
  3. package/scripts/verify-node.mjs +11 -0
  4. package/src/crypto/k12.ts +13 -0
  5. package/src/crypto/schnorrq.ts +3 -0
  6. package/src/crypto/seed.test.ts +11 -0
  7. package/src/crypto/seed.ts +63 -0
  8. package/src/index.browser.ts +85 -0
  9. package/src/index.node.ts +2 -0
  10. package/src/index.ts +85 -0
  11. package/src/primitives/identity.test.ts +41 -0
  12. package/src/primitives/identity.ts +108 -0
  13. package/src/primitives/number64.ts +58 -0
  14. package/src/protocol/assets.test.ts +86 -0
  15. package/src/protocol/assets.ts +276 -0
  16. package/src/protocol/broadcast-transaction.test.ts +34 -0
  17. package/src/protocol/broadcast-transaction.ts +17 -0
  18. package/src/protocol/contract-function.test.ts +37 -0
  19. package/src/protocol/contract-function.ts +53 -0
  20. package/src/protocol/entity.test.ts +58 -0
  21. package/src/protocol/entity.ts +82 -0
  22. package/src/protocol/message-types.ts +16 -0
  23. package/src/protocol/packet-framer.test.ts +101 -0
  24. package/src/protocol/packet-framer.ts +120 -0
  25. package/src/protocol/request-packet.ts +48 -0
  26. package/src/protocol/request-response-header.test.ts +69 -0
  27. package/src/protocol/request-response-header.ts +56 -0
  28. package/src/protocol/stream.test.ts +74 -0
  29. package/src/protocol/stream.ts +48 -0
  30. package/src/protocol/system-info.test.ts +76 -0
  31. package/src/protocol/system-info.ts +111 -0
  32. package/src/protocol/tick-data.test.ts +63 -0
  33. package/src/protocol/tick-data.ts +127 -0
  34. package/src/protocol/tick-info.test.ts +35 -0
  35. package/src/protocol/tick-info.ts +34 -0
  36. package/src/transactions/transaction.test.ts +80 -0
  37. package/src/transactions/transaction.ts +150 -0
  38. package/src/transport/async-queue.ts +60 -0
  39. package/src/transport/bridge.test.ts +114 -0
  40. package/src/transport/bridge.ts +178 -0
  41. package/src/transport/tcp.test.ts +99 -0
  42. package/src/transport/tcp.ts +134 -0
  43. package/src/transport/transport.ts +5 -0
@@ -0,0 +1,120 @@
1
+ import type { RequestResponseHeaderFields } from "./request-response-header.js";
2
+ import { decodeRequestResponseHeader } from "./request-response-header.js";
3
+
4
+ export type Packet = Readonly<{
5
+ header: RequestResponseHeaderFields;
6
+ payload: Uint8Array; // excludes the 8-byte header
7
+ }>;
8
+
9
+ export type PacketFramer = Readonly<{
10
+ push(chunk: Uint8Array): void;
11
+ read(): Packet[];
12
+ bufferedBytes(): number;
13
+ }>;
14
+
15
+ function concatInto(
16
+ target: Uint8Array,
17
+ targetOffset: number,
18
+ source: Uint8Array,
19
+ sourceOffset: number,
20
+ length: number,
21
+ ) {
22
+ target.set(source.subarray(sourceOffset, sourceOffset + length), targetOffset);
23
+ }
24
+
25
+ export function createPacketFramer(): PacketFramer {
26
+ const chunks: Uint8Array[] = [];
27
+ let buffered = 0;
28
+ let headOffset = 0;
29
+
30
+ const peek = (length: number): Uint8Array => {
31
+ const out = new Uint8Array(length);
32
+ let remaining = length;
33
+ let outOffset = 0;
34
+
35
+ for (let i = 0; i < chunks.length && remaining > 0; i++) {
36
+ const chunk = chunks[i];
37
+ if (!chunk) continue;
38
+ const start = i === 0 ? headOffset : 0;
39
+ const available = chunk.byteLength - start;
40
+ const take = Math.min(available, remaining);
41
+ concatInto(out, outOffset, chunk, start, take);
42
+ outOffset += take;
43
+ remaining -= take;
44
+ }
45
+
46
+ return out;
47
+ };
48
+
49
+ const consume = (length: number): Uint8Array => {
50
+ if (length > buffered) {
51
+ throw new RangeError("Cannot consume more bytes than buffered");
52
+ }
53
+
54
+ const out = new Uint8Array(length);
55
+ let remaining = length;
56
+ let outOffset = 0;
57
+
58
+ while (remaining > 0) {
59
+ const chunk = chunks[0];
60
+ if (!chunk) break;
61
+
62
+ const start = headOffset;
63
+ const available = chunk.byteLength - start;
64
+ const take = Math.min(available, remaining);
65
+ concatInto(out, outOffset, chunk, start, take);
66
+ outOffset += take;
67
+ remaining -= take;
68
+
69
+ headOffset += take;
70
+ buffered -= take;
71
+
72
+ if (headOffset >= chunk.byteLength) {
73
+ chunks.shift();
74
+ headOffset = 0;
75
+ }
76
+ }
77
+
78
+ return out;
79
+ };
80
+
81
+ const read = (): Packet[] => {
82
+ const packets: Packet[] = [];
83
+
84
+ while (true) {
85
+ if (buffered < 8) break;
86
+
87
+ const headerBytes = peek(8);
88
+ const header = decodeRequestResponseHeader(headerBytes);
89
+
90
+ if (header.size < 8) {
91
+ throw new RangeError(`Invalid packet header: size must be >= 8, got ${header.size}`);
92
+ }
93
+
94
+ if (buffered < header.size) break;
95
+
96
+ const packetBytes = consume(header.size);
97
+ packets.push({
98
+ header,
99
+ payload: packetBytes.subarray(8),
100
+ });
101
+ }
102
+
103
+ return packets;
104
+ };
105
+
106
+ const push = (chunk: Uint8Array) => {
107
+ if (!(chunk instanceof Uint8Array)) {
108
+ throw new TypeError("chunk must be a Uint8Array");
109
+ }
110
+ if (chunk.byteLength === 0) return;
111
+ chunks.push(chunk);
112
+ buffered += chunk.byteLength;
113
+ };
114
+
115
+ return {
116
+ push,
117
+ read,
118
+ bufferedBytes: () => buffered,
119
+ };
120
+ }
@@ -0,0 +1,48 @@
1
+ import { encodeRequestResponseHeader, MAX_PACKET_SIZE } from "./request-response-header.js";
2
+
3
+ export type EncodeRequestPacketOptions = Readonly<{
4
+ dejavu?: number;
5
+ }>;
6
+
7
+ function randomDejavu(): number {
8
+ // mimic go-node-connector: uint32(rand.Int31()) and ensure non-zero
9
+ let value = 0;
10
+
11
+ // Use WebCrypto if available
12
+ if (typeof globalThis.crypto?.getRandomValues === "function") {
13
+ const tmp = new Uint32Array(1);
14
+ globalThis.crypto.getRandomValues(tmp);
15
+ value = (tmp[0] ?? 0) & 0x7fffffff;
16
+ } else {
17
+ value = Math.floor(Math.random() * 0x80000000);
18
+ }
19
+
20
+ if (value === 0) value = 1;
21
+ return value >>> 0;
22
+ }
23
+
24
+ export function encodeRequestPacket(
25
+ type: number,
26
+ payload: Uint8Array = new Uint8Array(),
27
+ options: EncodeRequestPacketOptions = {},
28
+ ): Uint8Array {
29
+ if (!Number.isInteger(type) || type < 0 || type > 0xff) {
30
+ throw new RangeError("type must be a uint8");
31
+ }
32
+ if (!(payload instanceof Uint8Array)) {
33
+ throw new TypeError("payload must be a Uint8Array");
34
+ }
35
+
36
+ const size = 8 + payload.byteLength;
37
+ if (size > MAX_PACKET_SIZE) {
38
+ throw new RangeError(`packet size cannot exceed ${MAX_PACKET_SIZE}`);
39
+ }
40
+
41
+ const dejavu = options.dejavu ?? randomDejavu();
42
+ const header = encodeRequestResponseHeader({ size, type, dejavu });
43
+
44
+ const out = new Uint8Array(size);
45
+ out.set(header, 0);
46
+ out.set(payload, 8);
47
+ return out;
48
+ }
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import {
3
+ decodeRequestResponseHeader,
4
+ encodeRequestResponseHeader,
5
+ MAX_PACKET_SIZE,
6
+ } from "./request-response-header.js";
7
+
8
+ describe("RequestResponseHeader", () => {
9
+ it("encodes and decodes roundtrip", () => {
10
+ const encoded = encodeRequestResponseHeader({ size: 8, type: 24, dejavu: 0 });
11
+ expect(encoded).toBeInstanceOf(Uint8Array);
12
+ expect(encoded.length).toBe(8);
13
+
14
+ const decoded = decodeRequestResponseHeader(encoded);
15
+ expect(decoded).toEqual({ size: 8, type: 24, dejavu: 0 });
16
+ });
17
+
18
+ it("encodes max size", () => {
19
+ const encoded = encodeRequestResponseHeader({
20
+ size: MAX_PACKET_SIZE,
21
+ type: 255,
22
+ dejavu: 0xffffffff,
23
+ });
24
+ const decoded = decodeRequestResponseHeader(encoded);
25
+ expect(decoded.size).toBe(MAX_PACKET_SIZE);
26
+ expect(decoded.type).toBe(255);
27
+ expect(decoded.dejavu).toBe(0xffffffff);
28
+ });
29
+
30
+ it("rejects invalid size", () => {
31
+ expect(() => encodeRequestResponseHeader({ size: 0, type: 0, dejavu: 0 })).toThrow();
32
+ expect(() =>
33
+ encodeRequestResponseHeader({ size: MAX_PACKET_SIZE + 1, type: 0, dejavu: 0 }),
34
+ ).toThrow();
35
+ });
36
+
37
+ it("rejects invalid type", () => {
38
+ expect(() => encodeRequestResponseHeader({ size: 8, type: -1, dejavu: 0 })).toThrow();
39
+ expect(() => encodeRequestResponseHeader({ size: 8, type: 256, dejavu: 0 })).toThrow();
40
+ });
41
+
42
+ it("rejects invalid dejavu", () => {
43
+ expect(() => encodeRequestResponseHeader({ size: 8, type: 0, dejavu: -1 })).toThrow();
44
+ expect(() =>
45
+ encodeRequestResponseHeader({ size: 8, type: 0, dejavu: 0x1_0000_0000 }),
46
+ ).toThrow();
47
+ });
48
+
49
+ it("rejects decode of short buffer", () => {
50
+ expect(() => decodeRequestResponseHeader(new Uint8Array(7))).toThrow();
51
+ });
52
+
53
+ it("rejects decode when size is 0", () => {
54
+ const b = new Uint8Array(8);
55
+ b[3] = 1;
56
+ expect(() => decodeRequestResponseHeader(b)).toThrow();
57
+ });
58
+
59
+ it("property-ish: random roundtrips", () => {
60
+ for (let i = 0; i < 500; i++) {
61
+ const size = 1 + Math.floor(Math.random() * MAX_PACKET_SIZE);
62
+ const type = Math.floor(Math.random() * 256);
63
+ const dejavu = Math.floor(Math.random() * 0x1_0000_0000);
64
+ const encoded = encodeRequestResponseHeader({ size, type, dejavu });
65
+ const decoded = decodeRequestResponseHeader(encoded);
66
+ expect(decoded).toEqual({ size, type, dejavu: dejavu >>> 0 });
67
+ }
68
+ });
69
+ });
@@ -0,0 +1,56 @@
1
+ export const MAX_PACKET_SIZE = 0xffffff; // 16,777,215 (3 bytes)
2
+
3
+ export type RequestResponseHeaderFields = Readonly<{
4
+ size: number; // total packet size including this header
5
+ type: number; // uint8
6
+ dejavu: number; // uint32
7
+ }>;
8
+
9
+ function assertIntegerInRange(value: number, min: number, max: number, name: string) {
10
+ if (!Number.isInteger(value)) {
11
+ throw new TypeError(`${name} must be an integer`);
12
+ }
13
+ if (value < min || value > max) {
14
+ throw new RangeError(`${name} must be between ${min} and ${max}`);
15
+ }
16
+ }
17
+
18
+ export function encodeRequestResponseHeader(fields: RequestResponseHeaderFields): Uint8Array {
19
+ assertIntegerInRange(fields.size, 1, MAX_PACKET_SIZE, "size");
20
+ assertIntegerInRange(fields.type, 0, 0xff, "type");
21
+ assertIntegerInRange(fields.dejavu, 0, 0xffffffff, "dejavu");
22
+
23
+ const bytes = new Uint8Array(8);
24
+ bytes[0] = fields.size & 0xff;
25
+ bytes[1] = (fields.size >> 8) & 0xff;
26
+ bytes[2] = (fields.size >> 16) & 0xff;
27
+ bytes[3] = fields.type & 0xff;
28
+
29
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
30
+ view.setUint32(4, fields.dejavu >>> 0, true);
31
+ return bytes;
32
+ }
33
+
34
+ export function decodeRequestResponseHeader(bytes: Uint8Array): RequestResponseHeaderFields {
35
+ if (!(bytes instanceof Uint8Array)) {
36
+ throw new TypeError("bytes must be a Uint8Array");
37
+ }
38
+ if (bytes.byteLength < 8) {
39
+ throw new RangeError("bytes must be at least 8 bytes");
40
+ }
41
+
42
+ const size = ((bytes[0] ?? 0) | ((bytes[1] ?? 0) << 8) | ((bytes[2] ?? 0) << 16)) >>> 0;
43
+ const type = bytes[3] ?? 0;
44
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
45
+ const dejavu = view.getUint32(4, true);
46
+
47
+ // Core treats size=0 as broken; enforce invariant here to fail fast.
48
+ if (size === 0) {
49
+ throw new RangeError("Invalid header: size cannot be 0");
50
+ }
51
+ if (size > MAX_PACKET_SIZE) {
52
+ throw new RangeError(`Invalid header: size cannot exceed ${MAX_PACKET_SIZE}`);
53
+ }
54
+
55
+ return { size, type, dejavu };
56
+ }
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import type { Packet } from "./packet-framer.js";
3
+ import { encodeRequestResponseHeader } from "./request-response-header.js";
4
+ import { END_RESPONSE_TYPE, readUntilEndResponse } from "./stream.js";
5
+
6
+ function makePacket(type: number, dejavu: number, payload: Uint8Array): Packet {
7
+ const headerBytes = encodeRequestResponseHeader({
8
+ size: 8 + payload.byteLength,
9
+ type,
10
+ dejavu,
11
+ });
12
+ const header = {
13
+ size: (headerBytes[0] ?? 0) | ((headerBytes[1] ?? 0) << 8) | ((headerBytes[2] ?? 0) << 16),
14
+ type: headerBytes[3] ?? 0,
15
+ dejavu: new DataView(
16
+ headerBytes.buffer,
17
+ headerBytes.byteOffset,
18
+ headerBytes.byteLength,
19
+ ).getUint32(4, true),
20
+ };
21
+ return { header, payload };
22
+ }
23
+
24
+ async function collect<T>(iter: AsyncIterable<T>): Promise<T[]> {
25
+ const out: T[] = [];
26
+ for await (const item of iter) out.push(item);
27
+ return out;
28
+ }
29
+
30
+ describe("readUntilEndResponse", () => {
31
+ it("yields packets until END_RESPONSE then stops (excluding end by default)", async () => {
32
+ async function* source() {
33
+ yield makePacket(24, 1, Uint8Array.from([1]));
34
+ yield makePacket(24, 2, Uint8Array.from([2]));
35
+ yield makePacket(END_RESPONSE_TYPE, 3, new Uint8Array(0));
36
+ yield makePacket(24, 4, Uint8Array.from([9])); // should never be seen
37
+ }
38
+
39
+ const packets = await collect(readUntilEndResponse(source()));
40
+ expect(packets.map((p) => p.header.type)).toEqual([24, 24]);
41
+ const packet0 = packets[0];
42
+ const packet1 = packets[1];
43
+ if (!packet0 || !packet1) throw new Error("Expected two packets");
44
+ expect([...packet0.payload]).toEqual([1]);
45
+ expect([...packet1.payload]).toEqual([2]);
46
+ });
47
+
48
+ it("can include END_RESPONSE when requested", async () => {
49
+ async function* source() {
50
+ yield makePacket(24, 1, Uint8Array.from([1]));
51
+ yield makePacket(END_RESPONSE_TYPE, 3, new Uint8Array(0));
52
+ }
53
+
54
+ const packets = await collect(readUntilEndResponse(source(), { includeEnd: true }));
55
+ expect(packets.map((p) => p.header.type)).toEqual([24, END_RESPONSE_TYPE]);
56
+ });
57
+
58
+ it("throws if EOF occurs before END_RESPONSE (default)", async () => {
59
+ async function* source() {
60
+ yield makePacket(24, 1, Uint8Array.from([1]));
61
+ }
62
+
63
+ await expect(collect(readUntilEndResponse(source()))).rejects.toBeTruthy();
64
+ });
65
+
66
+ it("allows EOF before END_RESPONSE when allowEof is true", async () => {
67
+ async function* source() {
68
+ yield makePacket(24, 1, Uint8Array.from([1]));
69
+ }
70
+
71
+ const packets = await collect(readUntilEndResponse(source(), { allowEof: true }));
72
+ expect(packets.map((p) => p.header.type)).toEqual([24]);
73
+ });
74
+ });
@@ -0,0 +1,48 @@
1
+ import type { Packet } from "./packet-framer.js";
2
+
3
+ export const END_RESPONSE_TYPE = 35;
4
+
5
+ export type ReadUntilEndResponseOptions = Readonly<{
6
+ endType?: number;
7
+ includeEnd?: boolean;
8
+ allowEof?: boolean;
9
+ signal?: AbortSignal;
10
+ }>;
11
+
12
+ function toAbortError(reason: unknown): Error {
13
+ if (reason instanceof Error) return reason;
14
+ const error = new Error("Aborted");
15
+ error.name = "AbortError";
16
+ return error;
17
+ }
18
+
19
+ export async function* readUntilEndResponse(
20
+ packets: AsyncIterable<Packet>,
21
+ options: ReadUntilEndResponseOptions = {},
22
+ ): AsyncGenerator<Packet> {
23
+ const endType = options.endType ?? END_RESPONSE_TYPE;
24
+ const includeEnd = options.includeEnd ?? false;
25
+ const allowEof = options.allowEof ?? false;
26
+ const signal = options.signal;
27
+
28
+ if (signal?.aborted) {
29
+ throw toAbortError(signal.reason);
30
+ }
31
+
32
+ for await (const packet of packets) {
33
+ if (signal?.aborted) {
34
+ throw toAbortError(signal.reason);
35
+ }
36
+
37
+ if (packet.header.type === endType) {
38
+ if (includeEnd) yield packet;
39
+ return;
40
+ }
41
+
42
+ yield packet;
43
+ }
44
+
45
+ if (!allowEof) {
46
+ throw new Error(`Stream ended before terminator (type=${endType})`);
47
+ }
48
+ }
@@ -0,0 +1,76 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { NetworkMessageType } from "./message-types.js";
3
+ import { decodeRequestResponseHeader } from "./request-response-header.js";
4
+ import { decodeRespondSystemInfo, encodeRequestSystemInfo } from "./system-info.js";
5
+
6
+ describe("system-info", () => {
7
+ it("encodes request system info packet", () => {
8
+ const packet = encodeRequestSystemInfo();
9
+ expect(packet.byteLength).toBe(8);
10
+ const header = decodeRequestResponseHeader(packet);
11
+ expect(header.size).toBe(8);
12
+ expect(header.type).toBe(NetworkMessageType.REQUEST_SYSTEM_INFO);
13
+ expect(header.dejavu).not.toBe(0);
14
+ });
15
+
16
+ it("decodes respond system info payload", () => {
17
+ const payload = new Uint8Array(128);
18
+ const view = new DataView(payload.buffer);
19
+
20
+ view.setInt16(0, 123, true);
21
+ view.setUint16(2, 42, true);
22
+ view.setUint32(4, 1001, true);
23
+ view.setUint32(8, 500, true);
24
+ view.setUint32(12, 999, true);
25
+
26
+ view.setUint16(16, 250, true);
27
+ view.setUint8(18, 1);
28
+ view.setUint8(19, 2);
29
+ view.setUint8(20, 3);
30
+ view.setUint8(21, 4);
31
+ view.setUint8(22, 5);
32
+ view.setUint8(23, 6);
33
+
34
+ view.setUint32(24, 10, true);
35
+ view.setUint32(28, 20, true);
36
+
37
+ payload.set(new Uint8Array(32).fill(9), 32);
38
+ view.setInt32(64, -7, true);
39
+
40
+ view.setBigUint64(68, 1234n, true);
41
+ view.setBigUint64(76, 5678n, true);
42
+ view.setUint32(84, 0xdeadbeef, true);
43
+ view.setBigUint64(88, 9999n, true);
44
+
45
+ view.setBigUint64(96, 1n, true);
46
+ view.setBigUint64(104, 2n, true);
47
+ view.setBigUint64(112, 3n, true);
48
+ view.setBigUint64(120, 4n, true);
49
+
50
+ const decoded = decodeRespondSystemInfo(payload);
51
+ expect(decoded.version).toBe(123);
52
+ expect(decoded.epoch).toBe(42);
53
+ expect(decoded.tick).toBe(1001);
54
+ expect(decoded.initialTick).toBe(500);
55
+ expect(decoded.latestCreatedTick).toBe(999);
56
+ expect(decoded.initialMillisecond).toBe(250);
57
+ expect(decoded.initialSecond).toBe(1);
58
+ expect(decoded.initialMinute).toBe(2);
59
+ expect(decoded.initialHour).toBe(3);
60
+ expect(decoded.initialDay).toBe(4);
61
+ expect(decoded.initialMonth).toBe(5);
62
+ expect(decoded.initialYear).toBe(6);
63
+ expect(decoded.numberOfEntities).toBe(10);
64
+ expect(decoded.numberOfTransactions).toBe(20);
65
+ expect(decoded.randomMiningSeed32).toEqual(new Uint8Array(32).fill(9));
66
+ expect(decoded.solutionThreshold).toBe(-7);
67
+ expect(decoded.totalSpectrumAmount).toBe(1234n);
68
+ expect(decoded.currentEntityBalanceDustThreshold).toBe(5678n);
69
+ expect(decoded.targetTickVoteSignature).toBe(0xdeadbeef);
70
+ expect(decoded.computorPacketSignature).toBe(9999n);
71
+ expect(decoded.reserve1).toBe(1n);
72
+ expect(decoded.reserve2).toBe(2n);
73
+ expect(decoded.reserve3).toBe(3n);
74
+ expect(decoded.reserve4).toBe(4n);
75
+ });
76
+ });
@@ -0,0 +1,111 @@
1
+ import { readU64LE } from "../primitives/number64.js";
2
+ import { NetworkMessageType } from "./message-types.js";
3
+ import { encodeRequestPacket } from "./request-packet.js";
4
+
5
+ export const RESPOND_SYSTEM_INFO_PAYLOAD_SIZE = 128;
6
+
7
+ export type SystemInfo = Readonly<{
8
+ version: number; // int16
9
+ epoch: number; // uint16
10
+ tick: number; // uint32
11
+ initialTick: number; // uint32
12
+ latestCreatedTick: number; // uint32
13
+
14
+ initialMillisecond: number; // uint16
15
+ initialSecond: number; // uint8
16
+ initialMinute: number; // uint8
17
+ initialHour: number; // uint8
18
+ initialDay: number; // uint8
19
+ initialMonth: number; // uint8
20
+ initialYear: number; // uint8
21
+
22
+ numberOfEntities: number; // uint32
23
+ numberOfTransactions: number; // uint32
24
+
25
+ randomMiningSeed32: Uint8Array;
26
+ solutionThreshold: number; // int32
27
+
28
+ totalSpectrumAmount: bigint; // uint64
29
+ currentEntityBalanceDustThreshold: bigint; // uint64
30
+
31
+ targetTickVoteSignature: number; // uint32
32
+ computorPacketSignature: bigint; // uint64
33
+
34
+ reserve1: bigint;
35
+ reserve2: bigint;
36
+ reserve3: bigint;
37
+ reserve4: bigint;
38
+ }>;
39
+
40
+ export function encodeRequestSystemInfo(): Uint8Array {
41
+ return encodeRequestPacket(NetworkMessageType.REQUEST_SYSTEM_INFO);
42
+ }
43
+
44
+ export function decodeRespondSystemInfo(payload: Uint8Array): SystemInfo {
45
+ if (!(payload instanceof Uint8Array)) {
46
+ throw new TypeError("payload must be a Uint8Array");
47
+ }
48
+ if (payload.byteLength !== RESPOND_SYSTEM_INFO_PAYLOAD_SIZE) {
49
+ throw new RangeError(`payload must be ${RESPOND_SYSTEM_INFO_PAYLOAD_SIZE} bytes`);
50
+ }
51
+
52
+ const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
53
+
54
+ const version = view.getInt16(0, true);
55
+ const epoch = view.getUint16(2, true);
56
+ const tick = view.getUint32(4, true);
57
+ const initialTick = view.getUint32(8, true);
58
+ const latestCreatedTick = view.getUint32(12, true);
59
+
60
+ const initialMillisecond = view.getUint16(16, true);
61
+ const initialSecond = view.getUint8(18);
62
+ const initialMinute = view.getUint8(19);
63
+ const initialHour = view.getUint8(20);
64
+ const initialDay = view.getUint8(21);
65
+ const initialMonth = view.getUint8(22);
66
+ const initialYear = view.getUint8(23);
67
+
68
+ const numberOfEntities = view.getUint32(24, true);
69
+ const numberOfTransactions = view.getUint32(28, true);
70
+
71
+ const randomMiningSeed32 = payload.slice(32, 64);
72
+ const solutionThreshold = view.getInt32(64, true);
73
+
74
+ const totalSpectrumAmount = readU64LE(payload, 68);
75
+ const currentEntityBalanceDustThreshold = readU64LE(payload, 76);
76
+
77
+ const targetTickVoteSignature = view.getUint32(84, true);
78
+ const computorPacketSignature = readU64LE(payload, 88);
79
+
80
+ const reserve1 = readU64LE(payload, 96);
81
+ const reserve2 = readU64LE(payload, 104);
82
+ const reserve3 = readU64LE(payload, 112);
83
+ const reserve4 = readU64LE(payload, 120);
84
+
85
+ return {
86
+ version,
87
+ epoch,
88
+ tick,
89
+ initialTick,
90
+ latestCreatedTick,
91
+ initialMillisecond,
92
+ initialSecond,
93
+ initialMinute,
94
+ initialHour,
95
+ initialDay,
96
+ initialMonth,
97
+ initialYear,
98
+ numberOfEntities,
99
+ numberOfTransactions,
100
+ randomMiningSeed32,
101
+ solutionThreshold,
102
+ totalSpectrumAmount,
103
+ currentEntityBalanceDustThreshold,
104
+ targetTickVoteSignature,
105
+ computorPacketSignature,
106
+ reserve1,
107
+ reserve2,
108
+ reserve3,
109
+ reserve4,
110
+ };
111
+ }
@@ -0,0 +1,63 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { NetworkMessageType } from "./message-types.js";
3
+ import { decodeRequestResponseHeader } from "./request-response-header.js";
4
+ import {
5
+ countNonZeroTransactionDigests,
6
+ decodeBroadcastFutureTickData,
7
+ encodeRequestTickData,
8
+ MAX_NUMBER_OF_CONTRACTS,
9
+ NUMBER_OF_TRANSACTIONS_PER_TICK,
10
+ TICK_DATA_PAYLOAD_SIZE,
11
+ } from "./tick-data.js";
12
+
13
+ describe("tick-data", () => {
14
+ it("encodes request tick data packet", () => {
15
+ const packet = encodeRequestTickData(1234);
16
+ expect(packet.byteLength).toBe(8 + 4);
17
+ const header = decodeRequestResponseHeader(packet.subarray(0, 8));
18
+ expect(header.type).toBe(NetworkMessageType.REQUEST_TICK_DATA);
19
+
20
+ const tick = new DataView(packet.buffer, packet.byteOffset + 8, 4).getUint32(0, true);
21
+ expect(tick).toBe(1234);
22
+ });
23
+
24
+ it("decodes broadcast future tick data payload with accessors", () => {
25
+ const payload = new Uint8Array(TICK_DATA_PAYLOAD_SIZE);
26
+ const view = new DataView(payload.buffer);
27
+ view.setUint16(0, 1, true);
28
+ view.setUint16(2, 2, true);
29
+ view.setUint32(4, 3, true);
30
+ view.setUint16(8, 4, true);
31
+ view.setUint8(10, 5);
32
+ view.setUint8(11, 6);
33
+ view.setUint8(12, 7);
34
+ view.setUint8(13, 8);
35
+ view.setUint8(14, 9);
36
+ view.setUint8(15, 10);
37
+
38
+ // timelock
39
+ payload.set(new Uint8Array(32).fill(11), 16);
40
+
41
+ // set one non-zero tx digest at index 0
42
+ payload[16 + 32 + 0] = 1;
43
+
44
+ // set one contract fee
45
+ const contractFeesOffset = 16 + 32 + NUMBER_OF_TRANSACTIONS_PER_TICK * 32;
46
+ view.setBigInt64(contractFeesOffset + 5 * 8, 123n, true);
47
+
48
+ const signatureOffset = contractFeesOffset + MAX_NUMBER_OF_CONTRACTS * 8;
49
+ payload.set(new Uint8Array(64).fill(12), signatureOffset);
50
+
51
+ const decoded = decodeBroadcastFutureTickData(payload);
52
+ expect(decoded.computorIndex).toBe(1);
53
+ expect(decoded.epoch).toBe(2);
54
+ expect(decoded.tick).toBe(3);
55
+ expect(decoded.millisecond).toBe(4);
56
+ expect(decoded.second).toBe(5);
57
+ expect(decoded.timelock32).toEqual(new Uint8Array(32).fill(11));
58
+ expect(decoded.signature64).toEqual(new Uint8Array(64).fill(12));
59
+ expect(decoded.getContractFee(5)).toBe(123n);
60
+
61
+ expect(countNonZeroTransactionDigests(decoded)).toBe(1);
62
+ });
63
+ });