@typeberry/lib 0.10.0 → 0.11.0-411c828
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.
- package/package.json +3 -5
- package/packages/configs/index.d.ts +9 -0
- package/packages/configs/index.d.ts.map +1 -1
- package/packages/configs/typeberry-default.json +4 -1
- package/packages/configs/typeberry-dev-full.json +3 -0
- package/packages/configs/typeberry-dev-tiny.json +3 -0
- package/packages/jam/config-node/index.d.ts +1 -0
- package/packages/jam/config-node/index.d.ts.map +1 -1
- package/packages/jam/config-node/index.js +1 -0
- package/packages/jam/config-node/node-config.d.ts +5 -11
- package/packages/jam/config-node/node-config.d.ts.map +1 -1
- package/packages/jam/config-node/node-config.js +9 -25
- package/packages/jam/config-node/node-config.test.js +11 -6
- package/packages/jam/config-node/rpc.d.ts +11 -0
- package/packages/jam/config-node/rpc.d.ts.map +1 -0
- package/packages/jam/config-node/rpc.js +16 -0
- package/packages/jam/database/states.d.ts +1 -8
- package/packages/jam/database/states.d.ts.map +1 -1
- package/packages/jam/database/value-refs.d.ts +4 -5
- package/packages/jam/database/value-refs.d.ts.map +1 -1
- package/packages/jam/database-fjall/blocks.d.ts +1 -0
- package/packages/jam/database-fjall/blocks.d.ts.map +1 -1
- package/packages/jam/database-fjall/blocks.js +8 -3
- package/packages/jam/database-fjall/hybrid-states.d.ts +10 -4
- package/packages/jam/database-fjall/hybrid-states.d.ts.map +1 -1
- package/packages/jam/database-fjall/hybrid-states.js +22 -6
- package/packages/jam/database-fjall/root.d.ts +2 -0
- package/packages/jam/database-fjall/root.d.ts.map +1 -1
- package/packages/jam/database-fjall/root.js +8 -1
- package/packages/jam/database-fjall/root.test.d.ts +2 -0
- package/packages/jam/database-fjall/root.test.d.ts.map +1 -0
- package/packages/jam/database-fjall/root.test.js +23 -0
- package/packages/jam/database-fjall/states.d.ts +1 -0
- package/packages/jam/database-fjall/states.d.ts.map +1 -1
- package/packages/jam/database-fjall/states.js +6 -3
- package/packages/jam/node/export.d.ts.map +1 -1
- package/packages/jam/node/export.js +8 -17
- package/packages/jam/node/jam-config.d.ts +5 -1
- package/packages/jam/node/jam-config.d.ts.map +1 -1
- package/packages/jam/node/jam-config.js +6 -2
- package/packages/jam/node/main-fuzz.d.ts.map +1 -1
- package/packages/jam/node/main-fuzz.js +59 -39
- package/packages/jam/node/main-importer.d.ts +6 -11
- package/packages/jam/node/main-importer.d.ts.map +1 -1
- package/packages/jam/node/main-importer.js +15 -29
- package/packages/jam/node/main.d.ts.map +1 -1
- package/packages/jam/node/main.js +6 -16
- package/packages/jam/rpc/index.d.ts +3 -0
- package/packages/jam/rpc/index.d.ts.map +1 -0
- package/packages/jam/rpc/index.js +2 -0
- package/packages/jam/rpc/src/handlers/best-block.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/best-block.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/best-block.js +15 -0
- package/packages/jam/rpc/src/handlers/finalized-block.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/finalized-block.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/finalized-block.js +7 -0
- package/packages/jam/rpc/src/handlers/list-services.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/list-services.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/list-services.js +15 -0
- package/packages/jam/rpc/src/handlers/not-implemented.d.ts +4 -0
- package/packages/jam/rpc/src/handlers/not-implemented.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/not-implemented.js +4 -0
- package/packages/jam/rpc/src/handlers/parameters.d.ts +7 -0
- package/packages/jam/rpc/src/handlers/parameters.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/parameters.js +53 -0
- package/packages/jam/rpc/src/handlers/parent.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/parent.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/parent.js +25 -0
- package/packages/jam/rpc/src/handlers/service-data.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/service-data.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/service-data.js +21 -0
- package/packages/jam/rpc/src/handlers/service-preimage.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/service-preimage.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/service-preimage.js +23 -0
- package/packages/jam/rpc/src/handlers/service-request.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/service-request.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/service-request.js +23 -0
- package/packages/jam/rpc/src/handlers/service-value.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/service-value.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/service-value.js +23 -0
- package/packages/jam/rpc/src/handlers/state-root.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/state-root.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/state-root.js +14 -0
- package/packages/jam/rpc/src/handlers/statistics.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/statistics.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/statistics.js +16 -0
- package/packages/jam/rpc/src/handlers/subscribe-best-block.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/subscribe-best-block.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/subscribe-best-block.js +8 -0
- package/packages/jam/rpc/src/handlers/subscribe-finalized-block.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/subscribe-finalized-block.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/subscribe-finalized-block.js +8 -0
- package/packages/jam/rpc/src/handlers/subscribe-service-data.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/subscribe-service-data.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/subscribe-service-data.js +13 -0
- package/packages/jam/rpc/src/handlers/subscribe-service-preimage.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/subscribe-service-preimage.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/subscribe-service-preimage.js +13 -0
- package/packages/jam/rpc/src/handlers/subscribe-service-request.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/subscribe-service-request.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/subscribe-service-request.js +13 -0
- package/packages/jam/rpc/src/handlers/subscribe-service-value.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/subscribe-service-value.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/subscribe-service-value.js +13 -0
- package/packages/jam/rpc/src/handlers/subscribe-statistics.d.ts +6 -0
- package/packages/jam/rpc/src/handlers/subscribe-statistics.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/subscribe-statistics.js +13 -0
- package/packages/jam/rpc/src/handlers/typeberry/refine-work-package.d.ts +8 -0
- package/packages/jam/rpc/src/handlers/typeberry/refine-work-package.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/typeberry/refine-work-package.js +68 -0
- package/packages/jam/rpc/src/handlers/unsubscribe.d.ts +4 -0
- package/packages/jam/rpc/src/handlers/unsubscribe.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers/unsubscribe.js +3 -0
- package/packages/jam/rpc/src/handlers.d.ts +3 -0
- package/packages/jam/rpc/src/handlers.d.ts.map +1 -0
- package/packages/jam/rpc/src/handlers.js +60 -0
- package/packages/jam/rpc/src/server.d.ts +31 -0
- package/packages/jam/rpc/src/server.d.ts.map +1 -0
- package/packages/jam/rpc/src/server.js +179 -0
- package/packages/jam/rpc/src/subscription-manager.d.ts +20 -0
- package/packages/jam/rpc/src/subscription-manager.d.ts.map +1 -0
- package/packages/jam/rpc/src/subscription-manager.js +74 -0
- package/packages/jam/rpc/test/e2e-server.d.ts +3 -0
- package/packages/jam/rpc/test/e2e-server.d.ts.map +1 -0
- package/packages/jam/rpc/test/e2e-server.js +30 -0
- package/packages/jam/rpc/test/e2e-setup.d.ts +2 -0
- package/packages/jam/rpc/test/e2e-setup.d.ts.map +1 -0
- package/packages/jam/rpc/test/e2e-setup.js +126 -0
- package/packages/jam/rpc/test/e2e.d.ts +2 -0
- package/packages/jam/rpc/test/e2e.d.ts.map +1 -0
- package/packages/jam/rpc/test/e2e.js +326 -0
- package/packages/jam/state-merkleization/index.d.ts +1 -1
- package/packages/jam/state-merkleization/index.js +1 -1
- package/packages/workers/api-node/config.d.ts +14 -46
- package/packages/workers/api-node/config.d.ts.map +1 -1
- package/packages/workers/api-node/config.js +31 -96
- package/packages/workers/api-node/config.test.js +82 -63
- package/packages/workers/importer/main.d.ts +2 -0
- package/packages/workers/importer/main.d.ts.map +1 -1
- package/packages/workers/importer/main.js +1 -1
- package/packages/jam/database-lmdb/blocks.d.ts +0 -24
- package/packages/jam/database-lmdb/blocks.d.ts.map +0 -1
- package/packages/jam/database-lmdb/blocks.js +0 -82
- package/packages/jam/database-lmdb/hybrid-states.d.ts +0 -56
- package/packages/jam/database-lmdb/hybrid-states.d.ts.map +0 -1
- package/packages/jam/database-lmdb/hybrid-states.js +0 -149
- package/packages/jam/database-lmdb/hybrid-states.test.d.ts +0 -2
- package/packages/jam/database-lmdb/hybrid-states.test.d.ts.map +0 -1
- package/packages/jam/database-lmdb/hybrid-states.test.js +0 -180
- package/packages/jam/database-lmdb/index.d.ts +0 -5
- package/packages/jam/database-lmdb/index.d.ts.map +0 -1
- package/packages/jam/database-lmdb/index.js +0 -4
- package/packages/jam/database-lmdb/root.d.ts +0 -26
- package/packages/jam/database-lmdb/root.d.ts.map +0 -1
- package/packages/jam/database-lmdb/root.js +0 -53
- package/packages/jam/database-lmdb/states.d.ts +0 -76
- package/packages/jam/database-lmdb/states.d.ts.map +0 -1
- package/packages/jam/database-lmdb/states.js +0 -145
- package/packages/jam/database-lmdb/states.test.d.ts +0 -2
- package/packages/jam/database-lmdb/states.test.d.ts.map +0 -1
- package/packages/jam/database-lmdb/states.test.js +0 -202
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { after, before, describe, it } from "node:test";
|
|
3
|
+
import { RpcClient } from "#@typeberry/rpc-client";
|
|
4
|
+
import { JSON_RPC_VERSION, validation } from "#@typeberry/rpc-validation";
|
|
5
|
+
import { startTestRpcServer } from "./e2e-server.js";
|
|
6
|
+
function hexToUint8Array(hex) {
|
|
7
|
+
return new Uint8Array(Buffer.from(hex, "hex"));
|
|
8
|
+
}
|
|
9
|
+
describe("JSON RPC Client-Server E2E", { concurrency: false }, () => {
|
|
10
|
+
let client;
|
|
11
|
+
let server;
|
|
12
|
+
before(async () => {
|
|
13
|
+
server = await startTestRpcServer(`${import.meta.dirname}/e2e.config.json`);
|
|
14
|
+
client = RpcClient.new("ws://localhost:19800");
|
|
15
|
+
await client.waitForConnection();
|
|
16
|
+
});
|
|
17
|
+
after(async () => {
|
|
18
|
+
client.close();
|
|
19
|
+
await server.close();
|
|
20
|
+
});
|
|
21
|
+
it("gets best block", async () => {
|
|
22
|
+
const result = await client.call("bestBlock");
|
|
23
|
+
assert.deepStrictEqual(result, {
|
|
24
|
+
header_hash: hexToUint8Array("9eb96a960cb553d5b27a63e6df4bf422c2781d8a013d9dcfbe32a30ef25afd52"),
|
|
25
|
+
slot: 100,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
it("gets finalized block", async () => {
|
|
29
|
+
// todo [seko] we're temporarily returning the best instead of finalized block
|
|
30
|
+
const result = await client.call("finalizedBlock");
|
|
31
|
+
assert.deepStrictEqual(result, {
|
|
32
|
+
header_hash: hexToUint8Array("9eb96a960cb553d5b27a63e6df4bf422c2781d8a013d9dcfbe32a30ef25afd52"),
|
|
33
|
+
slot: 100,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
it("gets parent block", async () => {
|
|
37
|
+
const bestBlock = await client.call("bestBlock");
|
|
38
|
+
const result = await client.call("parent", [bestBlock.header_hash]);
|
|
39
|
+
assert.deepStrictEqual(result, {
|
|
40
|
+
header_hash: hexToUint8Array("958ff3540f9641de3b7e1d89272c5979c0d7071817d9f5cd691d4f7fdb97e4d2"),
|
|
41
|
+
slot: 99,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
it("throws an error when a non-existing block hash is provided", async () => {
|
|
45
|
+
await assert.rejects(async () => {
|
|
46
|
+
await client.call("parent", [
|
|
47
|
+
hexToUint8Array("1111111111111111111111111111111111111111111111111111111111111111"),
|
|
48
|
+
]);
|
|
49
|
+
}, {
|
|
50
|
+
code: 1,
|
|
51
|
+
message: "Block unavailable: 0x1111111111111111111111111111111111111111111111111111111111111111",
|
|
52
|
+
data: "ERERERERERERERERERERERERERERERERERERERERERE=",
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
it("gets state root", async () => {
|
|
56
|
+
const bestBlock = await client.call("bestBlock");
|
|
57
|
+
const result = await client.call("stateRoot", [bestBlock.header_hash]);
|
|
58
|
+
assert.deepStrictEqual(result, hexToUint8Array("a685a3a56825043f0de06709e023d8fd83a8a311f3fe20b47fdd0f93706d1323"));
|
|
59
|
+
});
|
|
60
|
+
it("gets statistics", async () => {
|
|
61
|
+
const bestBlock = await client.call("bestBlock");
|
|
62
|
+
const result = await client.call("statistics", [bestBlock.header_hash]);
|
|
63
|
+
assert.deepStrictEqual(result, hexToUint8Array("01000000000000000000000000000000020000000500000002000000000000000000000000000000020000000500000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000500000001000000000000000000000000000000000000000300000001000000000000000000000000000000020000000300000002000000000000000000000000000000040000000a00000002000000000000000000000000000000060000000b00000003000000000000000000000000000000060000000c00000003000000000000000000000000000000040000000c00000001000000000000000000000000000000040000000a00000001000000000000000000000000000000060000000a000000000300128519008be9c30c0500000000000000000100000000000004c30c0500128519000000"));
|
|
64
|
+
});
|
|
65
|
+
it("gets service data", async () => {
|
|
66
|
+
const bestBlock = await client.call("bestBlock");
|
|
67
|
+
const result = await client.call("serviceData", [bestBlock.header_hash, 0]);
|
|
68
|
+
assert.deepStrictEqual(result, hexToUint8Array("d1b097b4410b3a63446d7c57d093972a9744fcd2d74f4a5e2ec163610e6d6327ffffffffffffffff0a000000000000000a000000000000007c20020000000000ffffffffffffffff1a000000000000006200000000000000"));
|
|
69
|
+
});
|
|
70
|
+
it("gets service value", async () => {
|
|
71
|
+
const bestBlock = await client.call("bestBlock");
|
|
72
|
+
const result = await client.call("serviceValue", [
|
|
73
|
+
bestBlock.header_hash,
|
|
74
|
+
1,
|
|
75
|
+
hexToUint8Array("0242a295a93ac7f3ba564f0be83089a647a9bd3861798cf9fbffae0daa2ce1ff"),
|
|
76
|
+
]);
|
|
77
|
+
assert.deepStrictEqual(result, null);
|
|
78
|
+
});
|
|
79
|
+
// The preimage hash comes from the post state of block 100 (can be extracted using the state viewer tool)
|
|
80
|
+
const testPreimageHash = hexToUint8Array("d1b097b4410b3a63446d7c57d093972a9744fcd2d74f4a5e2ec163610e6d6327");
|
|
81
|
+
it("gets service preimage", async () => {
|
|
82
|
+
const bestBlock = await client.call("bestBlock");
|
|
83
|
+
const data = await client.call("servicePreimage", [bestBlock.header_hash, 0, testPreimageHash]);
|
|
84
|
+
assert.deepStrictEqual(data?.length, 137056);
|
|
85
|
+
});
|
|
86
|
+
it("gets service request", async () => {
|
|
87
|
+
const bestBlock = await client.call("bestBlock");
|
|
88
|
+
const result = await client.call("serviceRequest", [bestBlock.header_hash, 0, testPreimageHash, 137056]);
|
|
89
|
+
assert.deepStrictEqual(result, [0]);
|
|
90
|
+
});
|
|
91
|
+
it("lists services", async () => {
|
|
92
|
+
const bestBlock = await client.call("bestBlock");
|
|
93
|
+
const result = await client.call("listServices", [bestBlock.header_hash]);
|
|
94
|
+
// TODO [ToDr] We should probably do a little bit better in terms of
|
|
95
|
+
// tracking recently active services. Some options for the future:
|
|
96
|
+
// 1. Use InMemoryDb for RPC E2E tests.
|
|
97
|
+
// 2. Store additional service metadata in the database.
|
|
98
|
+
// 3. Cache the state object, so that accessed services would be returned here.
|
|
99
|
+
assert.deepStrictEqual(result, []);
|
|
100
|
+
});
|
|
101
|
+
it("subscribes and unsubscribes to/from service preimage", async () => {
|
|
102
|
+
const subscription = await client.subscribe("subscribeServicePreimage", [0, testPreimageHash, false]);
|
|
103
|
+
try {
|
|
104
|
+
const data = await new Promise((resolve) => subscription.once("data", (result) => resolve(result)));
|
|
105
|
+
assert.deepStrictEqual(data.length, 182744);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
await subscription.unsubscribe();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
it("client handles errors when subscription is being requested", async () => {
|
|
112
|
+
await assert.rejects(async () => client.subscribe("subscribeServicePreimage", [
|
|
113
|
+
0,
|
|
114
|
+
hexToUint8Array("c16326432b5b3213dfd1609495e13c6b276cb474d679645337e5c2c09f19b53c3d"), // invalid preimage hash
|
|
115
|
+
false,
|
|
116
|
+
]));
|
|
117
|
+
});
|
|
118
|
+
it("client handles errors produced by the subscription", async (test) => {
|
|
119
|
+
const handlers = server.getHandlers();
|
|
120
|
+
const originalHandler = handlers.subscribeBestBlock;
|
|
121
|
+
handlers.subscribeBestBlock = async (params, { subscription }) => {
|
|
122
|
+
return subscription.subscribe("subscribeBestBlock", () => {
|
|
123
|
+
throw new Error("Forced error for bestBlock");
|
|
124
|
+
}, validation.schemas.bestBlock.output, params);
|
|
125
|
+
};
|
|
126
|
+
test.after(() => {
|
|
127
|
+
handlers.subscribeBestBlock = originalHandler;
|
|
128
|
+
});
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
client.subscribe("subscribeBestBlock", []).then((subscription) => {
|
|
131
|
+
subscription.on("data", async () => {
|
|
132
|
+
await subscription.unsubscribe();
|
|
133
|
+
reject(new Error("Subscription callback should not be called."));
|
|
134
|
+
});
|
|
135
|
+
subscription.on("error", async (error) => {
|
|
136
|
+
assert.strictEqual(error, "Error: Forced error for bestBlock");
|
|
137
|
+
await subscription.unsubscribe();
|
|
138
|
+
resolve();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
it("server raises an error for unknown method", async () => {
|
|
144
|
+
const socket = client.getSocket();
|
|
145
|
+
socket.send(JSON.stringify({
|
|
146
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
147
|
+
method: "unknownMethod",
|
|
148
|
+
params: [],
|
|
149
|
+
id: 1,
|
|
150
|
+
}));
|
|
151
|
+
const message = await new Promise((resolve) => {
|
|
152
|
+
socket.on("message", (data) => {
|
|
153
|
+
resolve(data.toString());
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
assert.deepStrictEqual(JSON.parse(message), {
|
|
157
|
+
jsonrpc: "2.0",
|
|
158
|
+
error: { code: -32601, message: "Method not found: unknownMethod" },
|
|
159
|
+
id: 1,
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
it("server raises an error when more than necessary parameters are supplied", async () => {
|
|
163
|
+
const bestBlock = await client.call("bestBlock");
|
|
164
|
+
const socket = client.getSocket();
|
|
165
|
+
socket.send(JSON.stringify({
|
|
166
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
167
|
+
method: "stateRoot",
|
|
168
|
+
params: [Buffer.from(bestBlock.header_hash).toString("base64"), 0],
|
|
169
|
+
id: 1,
|
|
170
|
+
}));
|
|
171
|
+
const message = await new Promise((resolve) => {
|
|
172
|
+
socket.on("message", (data) => {
|
|
173
|
+
resolve(data.toString());
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
assert.deepStrictEqual(JSON.parse(message), {
|
|
177
|
+
jsonrpc: "2.0",
|
|
178
|
+
error: { code: -32602, message: "Invalid params:\n[] Too big: expected array to have <1 items" },
|
|
179
|
+
id: 1,
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
it("server raises an error when a parameter of the wrong type is supplied", async () => {
|
|
183
|
+
const bestBlock = await client.call("bestBlock");
|
|
184
|
+
const socket = client.getSocket();
|
|
185
|
+
socket.send(JSON.stringify({
|
|
186
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
187
|
+
method: "serviceData",
|
|
188
|
+
params: [Buffer.from(bestBlock.header_hash).toString("base64"), "wrong argument"],
|
|
189
|
+
id: 1,
|
|
190
|
+
}));
|
|
191
|
+
const message = await new Promise((resolve) => {
|
|
192
|
+
socket.on("message", (data) => {
|
|
193
|
+
resolve(data.toString());
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
assert.deepStrictEqual(JSON.parse(message), {
|
|
197
|
+
jsonrpc: "2.0",
|
|
198
|
+
error: { code: -32602, message: "Invalid params:\n[1] Invalid input: expected number, received string" },
|
|
199
|
+
id: 1,
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
it("server raises an error when no parameters are supplied (even though necessary)", async () => {
|
|
203
|
+
const socket = client.getSocket();
|
|
204
|
+
socket.send(JSON.stringify({
|
|
205
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
206
|
+
method: "stateRoot",
|
|
207
|
+
id: 1,
|
|
208
|
+
}));
|
|
209
|
+
const message = await new Promise((resolve) => {
|
|
210
|
+
socket.on("message", (data) => {
|
|
211
|
+
resolve(data.toString());
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
assert.deepStrictEqual(JSON.parse(message), {
|
|
215
|
+
jsonrpc: "2.0",
|
|
216
|
+
error: { code: -32602, message: "Invalid params:\n[0] Invalid input: expected string, received undefined" },
|
|
217
|
+
id: 1,
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
it("server raises an error when some (not all) parameters are missing", async () => {
|
|
221
|
+
const bestBlock = await client.call("bestBlock");
|
|
222
|
+
const socket = client.getSocket();
|
|
223
|
+
socket.send(JSON.stringify({
|
|
224
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
225
|
+
method: "serviceData",
|
|
226
|
+
params: [Buffer.from(bestBlock.header_hash).toString("base64")],
|
|
227
|
+
id: 1,
|
|
228
|
+
}));
|
|
229
|
+
const message = await new Promise((resolve) => {
|
|
230
|
+
socket.on("message", (data) => {
|
|
231
|
+
resolve(data.toString());
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
assert.deepStrictEqual(JSON.parse(message), {
|
|
235
|
+
jsonrpc: "2.0",
|
|
236
|
+
error: { code: -32602, message: "Invalid params:\n[1] Invalid input: expected number, received undefined" },
|
|
237
|
+
id: 1,
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
it("server raises an error when an unimplemented method is called", async () => {
|
|
241
|
+
const socket = client.getSocket();
|
|
242
|
+
socket.send(JSON.stringify({
|
|
243
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
244
|
+
method: "submitPreimage",
|
|
245
|
+
params: [],
|
|
246
|
+
id: 1,
|
|
247
|
+
}));
|
|
248
|
+
const message = await new Promise((resolve) => {
|
|
249
|
+
socket.on("message", (data) => {
|
|
250
|
+
resolve(data.toString());
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
assert.deepStrictEqual(JSON.parse(message), {
|
|
254
|
+
jsonrpc: "2.0",
|
|
255
|
+
error: { code: 0, message: "Method not implemented" },
|
|
256
|
+
id: 1,
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
it("server raises an error when a non-base64 string is provided as hash", async () => {
|
|
260
|
+
const socket = client.getSocket();
|
|
261
|
+
socket.send(JSON.stringify({
|
|
262
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
263
|
+
method: "parent",
|
|
264
|
+
params: ["ThisIsNotBase64!!"],
|
|
265
|
+
id: 1,
|
|
266
|
+
}));
|
|
267
|
+
const message = await new Promise((resolve) => {
|
|
268
|
+
socket.on("message", (data) => {
|
|
269
|
+
resolve(data.toString());
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
assert.deepStrictEqual(JSON.parse(message), {
|
|
273
|
+
jsonrpc: "2.0",
|
|
274
|
+
error: { code: -32602, message: "Invalid params:\n[0] Invalid base64-encoded string" },
|
|
275
|
+
id: 1,
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
it("server gracefully handles malformed requests", async () => {
|
|
279
|
+
const socket = client.getSocket();
|
|
280
|
+
socket.send(JSON.stringify({ foo: "bar" }));
|
|
281
|
+
const message = await new Promise((resolve) => {
|
|
282
|
+
socket.on("message", (data) => {
|
|
283
|
+
resolve(data.toString());
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
assert.deepStrictEqual(JSON.parse(message), {
|
|
287
|
+
jsonrpc: "2.0",
|
|
288
|
+
error: { code: -32600, message: 'Invalid request: {"foo":"bar"}' },
|
|
289
|
+
id: null,
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
it("server handles batch requests", async () => {
|
|
293
|
+
const socket = client.getSocket();
|
|
294
|
+
const bestBlock = await client.call("bestBlock");
|
|
295
|
+
socket.send(JSON.stringify([
|
|
296
|
+
{ foo: "bar" }, // malformed request (error expected)
|
|
297
|
+
{
|
|
298
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
299
|
+
method: "notificationTest",
|
|
300
|
+
params: [],
|
|
301
|
+
}, // notification (no response expected, even if causes error)
|
|
302
|
+
{
|
|
303
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
304
|
+
method: "bestBlock",
|
|
305
|
+
params: [],
|
|
306
|
+
id: 1,
|
|
307
|
+
}, // correct request
|
|
308
|
+
]));
|
|
309
|
+
const message = await new Promise((resolve) => {
|
|
310
|
+
socket.on("message", (data) => {
|
|
311
|
+
resolve(data.toString());
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
assert.deepStrictEqual(JSON.parse(message), [
|
|
315
|
+
{ jsonrpc: "2.0", error: { code: -32600, message: 'Invalid request: {"foo":"bar"}' }, id: null },
|
|
316
|
+
{
|
|
317
|
+
jsonrpc: "2.0",
|
|
318
|
+
result: {
|
|
319
|
+
...bestBlock,
|
|
320
|
+
header_hash: Buffer.from(bestBlock.header_hash).toString("base64"),
|
|
321
|
+
},
|
|
322
|
+
id: 1,
|
|
323
|
+
},
|
|
324
|
+
]);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* (incomplete in-memory view).
|
|
20
20
|
* - `SerializedState<LeafDb>`: Disk-backed trie storage-leaf nodes live on
|
|
21
21
|
* disk and load on demand; cheap to update (no data duplication) and re-compute
|
|
22
|
-
* the `stateRoot`.
|
|
22
|
+
* the `stateRoot`.
|
|
23
23
|
* - `SerializedState<StateEntries>`: serialized state represented as a simple in-memory
|
|
24
24
|
* hashmap of `key -> value` entries.
|
|
25
25
|
*/
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* (incomplete in-memory view).
|
|
20
20
|
* - `SerializedState<LeafDb>`: Disk-backed trie storage-leaf nodes live on
|
|
21
21
|
* disk and load on demand; cheap to update (no data duplication) and re-compute
|
|
22
|
-
* the `stateRoot`.
|
|
22
|
+
* the `stateRoot`.
|
|
23
23
|
* - `SerializedState<StateEntries>`: serialized state represented as a simple in-memory
|
|
24
24
|
* hashmap of `key -> value` entries.
|
|
25
25
|
*/
|
|
@@ -2,46 +2,15 @@ import type { MessagePort } from "node:worker_threads";
|
|
|
2
2
|
import { type Decode, type Encode } from "#@typeberry/codec";
|
|
3
3
|
import { ChainSpec } from "#@typeberry/config";
|
|
4
4
|
import { type BlocksDb, type RootDb, type SerializedStatesDb } from "#@typeberry/database";
|
|
5
|
-
import { FjallValuesSession } from "#@typeberry/database-fjall";
|
|
5
|
+
import { FjallRoot, FjallValuesSession } from "#@typeberry/database-fjall";
|
|
6
6
|
import { Blake2b } from "#@typeberry/hash";
|
|
7
7
|
import type { WorkerConfig } from "#@typeberry/workers-api";
|
|
8
8
|
import { ThreadPort, type TransferablePort } from "./port.js";
|
|
9
|
-
export { FjallValuesSession };
|
|
9
|
+
export { FjallRoot, FjallValuesSession };
|
|
10
10
|
/** Persistent regular-node backend. */
|
|
11
|
-
export type PersistentBackend = "
|
|
11
|
+
export type PersistentBackend = "fjall";
|
|
12
12
|
/** Transferable worker config for persistent regular-node workers. */
|
|
13
|
-
export type PersistentWorkerConfig<T> =
|
|
14
|
-
/**
|
|
15
|
-
* Worker config for node.js, backed by the LMDB database.
|
|
16
|
-
*
|
|
17
|
-
* @deprecated lmdb is retained as an explicit fallback. Use `FjallWorkerConfig` for regular nodes.
|
|
18
|
-
*/
|
|
19
|
-
export declare class LmdbWorkerConfig<T = void> implements WorkerConfig<T, BlocksDb, SerializedStatesDb> {
|
|
20
|
-
readonly nodeName: string;
|
|
21
|
-
readonly chainSpec: ChainSpec;
|
|
22
|
-
readonly workerParams: T;
|
|
23
|
-
readonly dbPath: string;
|
|
24
|
-
readonly blake2b: Blake2b;
|
|
25
|
-
readonly ports: Map<string, ThreadPort>;
|
|
26
|
-
readonly ephemeral: boolean;
|
|
27
|
-
static new<T>({ nodeName, chainSpec, workerParams, dbPath, blake2b, ports, ephemeral, }: {
|
|
28
|
-
nodeName: string;
|
|
29
|
-
chainSpec: ChainSpec;
|
|
30
|
-
workerParams: T;
|
|
31
|
-
dbPath: string;
|
|
32
|
-
blake2b: Blake2b;
|
|
33
|
-
ports?: Map<string, ThreadPort>;
|
|
34
|
-
ephemeral?: boolean;
|
|
35
|
-
}): LmdbWorkerConfig<T>;
|
|
36
|
-
/** Restore node config from a transferable config object. */
|
|
37
|
-
static fromTransferable<T>(decodeParams: Decode<T>, config: TransferableConfig): Promise<LmdbWorkerConfig<T>>;
|
|
38
|
-
private constructor();
|
|
39
|
-
openDatabase(options?: {
|
|
40
|
-
readonly: boolean;
|
|
41
|
-
}): Promise<RootDb<BlocksDb, SerializedStatesDb>>;
|
|
42
|
-
/** Convert this config into a thread-transferable object. */
|
|
43
|
-
intoTransferable(paramsCodec: Encode<T>): TransferableConfig;
|
|
44
|
-
}
|
|
13
|
+
export type PersistentWorkerConfig<T> = FjallWorkerConfig<T>;
|
|
45
14
|
/** Worker config for node.js, backed by a shared fjall engine. */
|
|
46
15
|
export declare class FjallWorkerConfig<T = void> implements WorkerConfig<T, BlocksDb, SerializedStatesDb> {
|
|
47
16
|
readonly nodeName: string;
|
|
@@ -52,7 +21,8 @@ export declare class FjallWorkerConfig<T = void> implements WorkerConfig<T, Bloc
|
|
|
52
21
|
readonly ports: Map<string, ThreadPort>;
|
|
53
22
|
readonly ephemeral: boolean;
|
|
54
23
|
readonly cacheSizeBytes: number | undefined;
|
|
55
|
-
|
|
24
|
+
private readonly sharedFjallKeyspace;
|
|
25
|
+
static new<T>({ nodeName, chainSpec, workerParams, dbPath, blake2b, ports, ephemeral, cacheSizeBytes, sharedFjallKeyspace, }: {
|
|
56
26
|
nodeName: string;
|
|
57
27
|
chainSpec: ChainSpec;
|
|
58
28
|
workerParams: T;
|
|
@@ -61,6 +31,7 @@ export declare class FjallWorkerConfig<T = void> implements WorkerConfig<T, Bloc
|
|
|
61
31
|
ports?: Map<string, ThreadPort>;
|
|
62
32
|
ephemeral?: boolean;
|
|
63
33
|
cacheSizeBytes?: number;
|
|
34
|
+
sharedFjallKeyspace?: FjallRoot;
|
|
64
35
|
}): FjallWorkerConfig<T>;
|
|
65
36
|
/** Restore node config from a transferable config object. */
|
|
66
37
|
static fromTransferable<T>(decodeParams: Decode<T>, config: TransferableConfig): Promise<FjallWorkerConfig<T>>;
|
|
@@ -115,11 +86,10 @@ export declare class InMemWorkerConfig<T = undefined> implements WorkerConfig<T,
|
|
|
115
86
|
}): Promise<RootDb<BlocksDb, SerializedStatesDb>>;
|
|
116
87
|
}
|
|
117
88
|
/** Persistent values store backing the hybrid config. */
|
|
118
|
-
export type HybridBackend = "
|
|
89
|
+
export type HybridBackend = "fjall";
|
|
119
90
|
/**
|
|
120
91
|
* Hybrid worker config for the fuzz target: in-memory blocks and leaf sets,
|
|
121
|
-
* but large values persisted to disk
|
|
122
|
-
* (lmdb or fjall).
|
|
92
|
+
* but large values persisted to disk via fjall.
|
|
123
93
|
*
|
|
124
94
|
* fjall opens its keyspace asynchronously, that is why `new` here is async.
|
|
125
95
|
*
|
|
@@ -141,7 +111,7 @@ export declare class HybridWorkerConfig<T = undefined> implements WorkerConfig<T
|
|
|
141
111
|
readonly ephemeral: boolean;
|
|
142
112
|
readonly compression: boolean;
|
|
143
113
|
private readonly states;
|
|
144
|
-
static new<T>({ nodeName, chainSpec, workerParams, blake2b, dbPath, ephemeral, compression,
|
|
114
|
+
static new<T>({ nodeName, chainSpec, workerParams, blake2b, dbPath, ephemeral, compression, sharedFjallKeyspace, }: {
|
|
145
115
|
nodeName: string;
|
|
146
116
|
chainSpec: ChainSpec;
|
|
147
117
|
workerParams: T;
|
|
@@ -149,14 +119,12 @@ export declare class HybridWorkerConfig<T = undefined> implements WorkerConfig<T
|
|
|
149
119
|
dbPath: string;
|
|
150
120
|
ephemeral?: boolean;
|
|
151
121
|
compression?: boolean;
|
|
152
|
-
backend?: HybridBackend;
|
|
153
122
|
/**
|
|
154
|
-
* Reuse an already-open fjall
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* unless `backend === "fjall"`.
|
|
123
|
+
* Reuse an already-open fjall keyspace instead of opening a fresh keyspace.
|
|
124
|
+
* The fuzz target opens one per run and passes it on every reset, so only
|
|
125
|
+
* the in-memory blocks/leaf sets are rebuilt per vector.
|
|
158
126
|
*/
|
|
159
|
-
|
|
127
|
+
sharedFjallKeyspace?: FjallRoot;
|
|
160
128
|
}): Promise<HybridWorkerConfig<T>>;
|
|
161
129
|
private readonly blocks;
|
|
162
130
|
private constructor();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/api-node/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,KAAK,MAAM,EAAW,KAAK,MAAM,EAAW,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EACL,KAAK,QAAQ,EAGb,KAAK,MAAM,EACX,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/api-node/config.ts"],"names":[],"mappings":"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"}
|