@typeberry/lib 0.11.0-829b7ea → 0.11.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 (48) hide show
  1. package/package.json +3 -1
  2. package/packages/jam/config-node/node-config.d.ts +11 -1
  3. package/packages/jam/config-node/node-config.d.ts.map +1 -1
  4. package/packages/jam/config-node/node-config.js +25 -3
  5. package/packages/jam/config-node/node-config.test.js +7 -3
  6. package/packages/jam/database/states.d.ts +8 -1
  7. package/packages/jam/database/states.d.ts.map +1 -1
  8. package/packages/jam/database/value-refs.d.ts +5 -4
  9. package/packages/jam/database/value-refs.d.ts.map +1 -1
  10. package/packages/jam/database-lmdb/blocks.d.ts +24 -0
  11. package/packages/jam/database-lmdb/blocks.d.ts.map +1 -0
  12. package/packages/jam/database-lmdb/blocks.js +82 -0
  13. package/packages/jam/database-lmdb/hybrid-states.d.ts +56 -0
  14. package/packages/jam/database-lmdb/hybrid-states.d.ts.map +1 -0
  15. package/packages/jam/database-lmdb/hybrid-states.js +149 -0
  16. package/packages/jam/database-lmdb/hybrid-states.test.d.ts +2 -0
  17. package/packages/jam/database-lmdb/hybrid-states.test.d.ts.map +1 -0
  18. package/packages/jam/database-lmdb/hybrid-states.test.js +180 -0
  19. package/packages/jam/database-lmdb/index.d.ts +5 -0
  20. package/packages/jam/database-lmdb/index.d.ts.map +1 -0
  21. package/packages/jam/database-lmdb/index.js +4 -0
  22. package/packages/jam/database-lmdb/root.d.ts +26 -0
  23. package/packages/jam/database-lmdb/root.d.ts.map +1 -0
  24. package/packages/jam/database-lmdb/root.js +53 -0
  25. package/packages/jam/database-lmdb/states.d.ts +76 -0
  26. package/packages/jam/database-lmdb/states.d.ts.map +1 -0
  27. package/packages/jam/database-lmdb/states.js +145 -0
  28. package/packages/jam/database-lmdb/states.test.d.ts +2 -0
  29. package/packages/jam/database-lmdb/states.test.d.ts.map +1 -0
  30. package/packages/jam/database-lmdb/states.test.js +202 -0
  31. package/packages/jam/node/export.d.ts.map +1 -1
  32. package/packages/jam/node/export.js +17 -8
  33. package/packages/jam/node/main-fuzz.d.ts.map +1 -1
  34. package/packages/jam/node/main-fuzz.js +5 -3
  35. package/packages/jam/node/main-importer.d.ts +6 -2
  36. package/packages/jam/node/main-importer.d.ts.map +1 -1
  37. package/packages/jam/node/main-importer.js +27 -12
  38. package/packages/jam/node/main.d.ts.map +1 -1
  39. package/packages/jam/node/main.js +16 -6
  40. package/packages/jam/rpc/test/e2e-server.d.ts.map +1 -1
  41. package/packages/jam/rpc/test/e2e-server.js +5 -3
  42. package/packages/jam/rpc/test/e2e.js +1 -1
  43. package/packages/jam/state-merkleization/index.d.ts +1 -1
  44. package/packages/jam/state-merkleization/index.js +1 -1
  45. package/packages/workers/api-node/config.d.ts +40 -6
  46. package/packages/workers/api-node/config.d.ts.map +1 -1
  47. package/packages/workers/api-node/config.js +82 -6
  48. package/packages/workers/api-node/config.test.js +63 -26
@@ -2,11 +2,79 @@ import { Decoder, Encoder } from "#@typeberry/codec";
2
2
  import { ChainSpec } from "#@typeberry/config";
3
3
  import { InMemoryBlocks, InMemorySerializedStates, } from "#@typeberry/database";
4
4
  import { FjallBlocks, HybridSerializedStates as FjallHybridSerializedStates, FjallRoot, FjallStates, FjallValuesSession, } from "#@typeberry/database-fjall";
5
+ import { LmdbBlocks, HybridSerializedStates as LmdbHybridSerializedStates, LmdbRoot, LmdbStates, } from "#@typeberry/database-lmdb";
5
6
  import { Blake2b } from "#@typeberry/hash";
6
7
  import { ThreadPort } from "./port.js";
7
8
  // Re-exported so the fuzz target can open one values session per run and reuse
8
9
  // it across resets (see `HybridWorkerConfig` / `mainFuzz`).
9
10
  export { FjallValuesSession };
11
+ /**
12
+ * Worker config for node.js, backed by the LMDB database.
13
+ *
14
+ * @deprecated lmdb is retained as an explicit fallback. Use `FjallWorkerConfig` for regular nodes.
15
+ */
16
+ export class LmdbWorkerConfig {
17
+ nodeName;
18
+ chainSpec;
19
+ workerParams;
20
+ dbPath;
21
+ blake2b;
22
+ ports;
23
+ ephemeral;
24
+ static new({ nodeName, chainSpec, workerParams, dbPath, blake2b, ports = new Map(), ephemeral = false, }) {
25
+ return new LmdbWorkerConfig(nodeName, chainSpec, workerParams, dbPath, blake2b, ports, ephemeral);
26
+ }
27
+ /** Restore node config from a transferable config object. */
28
+ static async fromTransferable(decodeParams, config) {
29
+ if (config.databaseBackend !== "lmdb") {
30
+ throw new Error(`Expected lmdb worker config, got ${config.databaseBackend}.`);
31
+ }
32
+ const { blake2b, chainSpec, workerParams, ports } = await decodeTransferableConfig(decodeParams, config);
33
+ return LmdbWorkerConfig.new({
34
+ nodeName: config.nodeName,
35
+ chainSpec,
36
+ workerParams,
37
+ dbPath: config.dbPath,
38
+ blake2b,
39
+ ports,
40
+ });
41
+ }
42
+ constructor(nodeName, chainSpec, workerParams, dbPath, blake2b, ports,
43
+ // When set, the underlying database skips fsync. Only safe for throwaway
44
+ // databases (the fuzz target wipes on reset). Not transferred to worker
45
+ // threads, so the durable main node path always gets the default.
46
+ ephemeral = false) {
47
+ this.nodeName = nodeName;
48
+ this.chainSpec = chainSpec;
49
+ this.workerParams = workerParams;
50
+ this.dbPath = dbPath;
51
+ this.blake2b = blake2b;
52
+ this.ports = ports;
53
+ this.ephemeral = ephemeral;
54
+ }
55
+ async openDatabase(options = { readonly: true }) {
56
+ const lmdb = LmdbRoot.new(this.dbPath, {
57
+ readOnly: options.readonly,
58
+ ephemeral: this.ephemeral,
59
+ });
60
+ return {
61
+ getBlocksDb: () => LmdbBlocks.new(this.chainSpec, lmdb),
62
+ getStatesDb: () => LmdbStates.new(this.chainSpec, this.blake2b, lmdb),
63
+ close: async () => await lmdb.close(),
64
+ };
65
+ }
66
+ /** Convert this config into a thread-transferable object. */
67
+ intoTransferable(paramsCodec) {
68
+ return {
69
+ databaseBackend: "lmdb",
70
+ nodeName: this.nodeName,
71
+ chainSpec: this.chainSpec,
72
+ workerParams: Encoder.encodeObject(paramsCodec, this.workerParams, this.chainSpec).raw,
73
+ dbPath: this.dbPath,
74
+ workerPorts: Array.from(this.ports.entries()).map(([name, port]) => [name, port.intoTransferable()]),
75
+ };
76
+ }
77
+ }
10
78
  /** Worker config for node.js, backed by a shared fjall engine. */
11
79
  export class FjallWorkerConfig {
12
80
  nodeName;
@@ -113,7 +181,12 @@ async function decodeTransferableConfig(decodeParams, config) {
113
181
  }
114
182
  /** Restore a persistent worker config from its transferable form. */
115
183
  export async function persistentConfigFromTransferable(decodeParams, config) {
116
- return FjallWorkerConfig.fromTransferable(decodeParams, config);
184
+ switch (config.databaseBackend) {
185
+ case "lmdb":
186
+ return LmdbWorkerConfig.fromTransferable(decodeParams, config);
187
+ case "fjall":
188
+ return FjallWorkerConfig.fromTransferable(decodeParams, config);
189
+ }
117
190
  }
118
191
  /**
119
192
  * Collect the transferable objects (communication ports) embedded in a config.
@@ -159,7 +232,8 @@ export class InMemWorkerConfig {
159
232
  }
160
233
  /**
161
234
  * Hybrid worker config for the fuzz target: in-memory blocks and leaf sets,
162
- * but large values persisted to disk via fjall.
235
+ * but large values persisted to disk. The `backend` picks where the values go
236
+ * (lmdb or fjall).
163
237
  *
164
238
  * fjall opens its keyspace asynchronously, that is why `new` here is async.
165
239
  *
@@ -181,12 +255,14 @@ export class HybridWorkerConfig {
181
255
  ephemeral;
182
256
  compression;
183
257
  states;
184
- static async new({ nodeName, chainSpec, workerParams, blake2b, dbPath, ephemeral = false, compression = true, sharedFjallSession, }) {
258
+ static async new({ nodeName, chainSpec, workerParams, blake2b, dbPath, ephemeral = false, compression = true, backend = "lmdb", sharedFjallSession, }) {
185
259
  // The values store is created once here and shared across reopen. When a
186
260
  // session is given (fuzz reset reuse) we wrap it instead of opening a new one.
187
- const states = sharedFjallSession !== undefined
188
- ? FjallHybridSerializedStates.fromSession(chainSpec, blake2b, sharedFjallSession)
189
- : await FjallHybridSerializedStates.new({ spec: chainSpec, blake2b, dbPath, ephemeral });
261
+ const states = backend === "fjall"
262
+ ? sharedFjallSession !== undefined
263
+ ? FjallHybridSerializedStates.fromSession(chainSpec, blake2b, sharedFjallSession)
264
+ : await FjallHybridSerializedStates.new({ spec: chainSpec, blake2b, dbPath, ephemeral })
265
+ : LmdbHybridSerializedStates.new({ spec: chainSpec, blake2b, dbPath, ephemeral, compression, readOnly: false });
190
266
  return new HybridWorkerConfig(nodeName, chainSpec, workerParams, blake2b, dbPath, ephemeral, compression, states);
191
267
  }
192
268
  blocks;
@@ -1,14 +1,46 @@
1
1
  import assert from "node:assert";
2
2
  import * as fs from "node:fs";
3
3
  import { describe, it } from "node:test";
4
+ import { MessageChannel } from "node:worker_threads";
4
5
  import { Bytes } from "#@typeberry/bytes";
5
6
  import { codec } from "#@typeberry/codec";
6
7
  import { tinyChainSpec } from "#@typeberry/config";
7
8
  import { Blake2b, HASH_SIZE } from "#@typeberry/hash";
8
9
  import { tryAsU32 } from "#@typeberry/numbers";
9
- import { configTransferList, FjallWorkerConfig, HybridWorkerConfig } from "./config.js";
10
+ import { configTransferList, FjallWorkerConfig, HybridWorkerConfig, LmdbWorkerConfig } from "./config.js";
10
11
  import { ThreadPort } from "./port.js";
11
12
  const spec = tinyChainSpec;
13
+ describe("LmdbWorkerConfig transfer list", () => {
14
+ it("surfaces embedded worker ports so they can be transferred", async () => {
15
+ const blake2b = await Blake2b.createHasher();
16
+ const [portA, portB] = ThreadPort.pair(spec);
17
+ const config = LmdbWorkerConfig.new({
18
+ nodeName: "node",
19
+ chainSpec: spec,
20
+ workerParams: tryAsU32(7),
21
+ dbPath: "db",
22
+ blake2b,
23
+ ports: new Map([["authorship-network", portA]]),
24
+ });
25
+ const transferable = config.intoTransferable(codec.varU32);
26
+ const transferList = configTransferList(transferable);
27
+ // the single embedded comms port must be reported for transfer
28
+ assert.strictEqual(transferList.length, 1);
29
+ const sink = new MessageChannel();
30
+ try {
31
+ // reproduces the bug: a config carrying a port cannot be cloned without
32
+ // listing that port in the transfer list.
33
+ assert.throws(() => sink.port1.postMessage(transferable, []), /transfer/i);
34
+ // with the ports surfaced, posting succeeds.
35
+ assert.doesNotThrow(() => sink.port1.postMessage(transferable, transferList));
36
+ }
37
+ finally {
38
+ sink.port1.close();
39
+ sink.port2.close();
40
+ portB.close();
41
+ }
42
+ });
43
+ });
12
44
  describe("FjallWorkerConfig transfer list", () => {
13
45
  it("surfaces embedded worker ports and marks the backend", async () => {
14
46
  const blake2b = await Blake2b.createHasher();
@@ -58,33 +90,38 @@ describe("FjallWorkerConfig transfer list", () => {
58
90
  });
59
91
  });
60
92
  describe("HybridWorkerConfig", () => {
61
- it("constructs and opens a fjall-backed hybrid db", async () => {
62
- const blake2b = await Blake2b.createHasher();
63
- const dbPath = fs.mkdtempSync("typeberry-hybrid-fjall-");
64
- try {
65
- const config = await HybridWorkerConfig.new({
66
- nodeName: "node",
67
- chainSpec: spec,
68
- workerParams: undefined,
69
- blake2b,
70
- dbPath,
71
- ephemeral: true,
72
- });
73
- const db = await config.openDatabase({ readonly: false });
74
- const states = db.getStatesDb();
93
+ // Both persistent backends must construct asynchronously and hand out a
94
+ // working db. fjall is the experimental backend we want to benchmark.
95
+ for (const backend of ["lmdb", "fjall"]) {
96
+ it(`constructs and opens a ${backend}-backed hybrid db`, async () => {
97
+ const blake2b = await Blake2b.createHasher();
98
+ const dbPath = fs.mkdtempSync(`typeberry-hybrid-${backend}-`);
75
99
  try {
76
- assert.notStrictEqual(db.getBlocksDb(), undefined);
77
- assert.notStrictEqual(states, undefined);
100
+ const config = await HybridWorkerConfig.new({
101
+ nodeName: "node",
102
+ chainSpec: spec,
103
+ workerParams: undefined,
104
+ blake2b,
105
+ dbPath,
106
+ ephemeral: true,
107
+ backend,
108
+ });
109
+ const db = await config.openDatabase({ readonly: false });
110
+ const states = db.getStatesDb();
111
+ try {
112
+ assert.notStrictEqual(db.getBlocksDb(), undefined);
113
+ assert.notStrictEqual(states, undefined);
114
+ }
115
+ finally {
116
+ // The values store owns the on-disk resources (the no-op db.close()
117
+ // does not), so close it explicitly to release the fjall keyspace.
118
+ await states.close();
119
+ await db.close();
120
+ }
78
121
  }
79
122
  finally {
80
- // The values store owns the on-disk resources (the no-op db.close()
81
- // does not), so close it explicitly to release the fjall keyspace.
82
- await states.close();
83
- await db.close();
123
+ fs.rmSync(dbPath, { recursive: true, force: true });
84
124
  }
85
- }
86
- finally {
87
- fs.rmSync(dbPath, { recursive: true, force: true });
88
- }
89
- });
125
+ });
126
+ }
90
127
  });