@typeberry/lib 0.9.0-6850f84 → 0.9.0-a723180

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 (71) hide show
  1. package/package.json +1 -1
  2. package/packages/jam/config-node/node-config.d.ts +20 -2
  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 +8 -1
  6. package/packages/jam/database/serialized-states-db.d.ts +4 -1
  7. package/packages/jam/database/serialized-states-db.d.ts.map +1 -1
  8. package/packages/jam/database/serialized-states-db.js +17 -5
  9. package/packages/jam/database/serialized-states-db.test.d.ts +2 -0
  10. package/packages/jam/database/serialized-states-db.test.d.ts.map +1 -0
  11. package/packages/jam/database/serialized-states-db.test.js +109 -0
  12. package/packages/jam/database-fjall/blocks.d.ts +25 -0
  13. package/packages/jam/database-fjall/blocks.d.ts.map +1 -0
  14. package/packages/jam/database-fjall/blocks.js +86 -0
  15. package/packages/jam/database-fjall/blocks.test.d.ts +2 -0
  16. package/packages/jam/database-fjall/blocks.test.d.ts.map +1 -0
  17. package/packages/jam/database-fjall/blocks.test.js +62 -0
  18. package/packages/jam/database-fjall/hybrid-states.d.ts.map +1 -1
  19. package/packages/jam/database-fjall/hybrid-states.js +11 -2
  20. package/packages/jam/database-fjall/hybrid-states.test.js +94 -2
  21. package/packages/jam/database-fjall/index.d.ts +2 -0
  22. package/packages/jam/database-fjall/index.d.ts.map +1 -1
  23. package/packages/jam/database-fjall/index.js +2 -0
  24. package/packages/jam/database-fjall/root.d.ts +14 -8
  25. package/packages/jam/database-fjall/root.d.ts.map +1 -1
  26. package/packages/jam/database-fjall/root.js +25 -9
  27. package/packages/jam/database-fjall/states.d.ts +31 -0
  28. package/packages/jam/database-fjall/states.d.ts.map +1 -0
  29. package/packages/jam/database-fjall/states.js +99 -0
  30. package/packages/jam/database-fjall/states.test.d.ts +2 -0
  31. package/packages/jam/database-fjall/states.test.d.ts.map +1 -0
  32. package/packages/jam/database-fjall/states.test.js +150 -0
  33. package/packages/jam/database-lmdb/hybrid-states.test.js +94 -2
  34. package/packages/jam/node/export.d.ts.map +1 -1
  35. package/packages/jam/node/export.js +18 -9
  36. package/packages/jam/node/main-importer.d.ts +4 -2
  37. package/packages/jam/node/main-importer.d.ts.map +1 -1
  38. package/packages/jam/node/main-importer.js +26 -12
  39. package/packages/jam/node/main.d.ts.map +1 -1
  40. package/packages/jam/node/main.js +42 -15
  41. package/packages/jam/node/workers.d.ts +4 -4
  42. package/packages/jam/node/workers.d.ts.map +1 -1
  43. package/packages/workers/api/config.d.ts +2 -2
  44. package/packages/workers/api/config.d.ts.map +1 -1
  45. package/packages/workers/api/config.js +1 -1
  46. package/packages/workers/api/port.d.ts +5 -1
  47. package/packages/workers/api/port.d.ts.map +1 -1
  48. package/packages/workers/api/port.js +56 -13
  49. package/packages/workers/api/port.test.js +35 -0
  50. package/packages/workers/api-node/config.d.ts +45 -4
  51. package/packages/workers/api-node/config.d.ts.map +1 -1
  52. package/packages/workers/api-node/config.js +127 -9
  53. package/packages/workers/api-node/config.test.js +52 -3
  54. package/packages/workers/api-node/port.d.ts +3 -0
  55. package/packages/workers/api-node/port.d.ts.map +1 -1
  56. package/packages/workers/api-node/port.js +31 -2
  57. package/packages/workers/api-node/port.test.js +17 -0
  58. package/packages/workers/api-node/protocol.d.ts +3 -3
  59. package/packages/workers/api-node/protocol.d.ts.map +1 -1
  60. package/packages/workers/api-node/protocol.js +18 -12
  61. package/packages/workers/block-authorship/main.js +1 -1
  62. package/packages/workers/importer/finality.d.ts +4 -0
  63. package/packages/workers/importer/finality.d.ts.map +1 -1
  64. package/packages/workers/importer/finality.js +10 -1
  65. package/packages/workers/importer/finality.test.js +6 -0
  66. package/packages/workers/importer/importer.d.ts.map +1 -1
  67. package/packages/workers/importer/importer.js +3 -0
  68. package/packages/workers/importer/main.d.ts +1 -1
  69. package/packages/workers/importer/main.d.ts.map +1 -1
  70. package/packages/workers/importer/main.js +1 -1
  71. package/packages/workers/jam-network/main.js +1 -1
@@ -1,14 +1,18 @@
1
1
  import { Decoder, Encoder } from "#@typeberry/codec";
2
2
  import { ChainSpec } from "#@typeberry/config";
3
3
  import { InMemoryBlocks, InMemorySerializedStates, } from "#@typeberry/database";
4
- import { HybridSerializedStates as FjallHybridSerializedStates, FjallValuesSession } from "#@typeberry/database-fjall";
4
+ import { FjallBlocks, HybridSerializedStates as FjallHybridSerializedStates, FjallRoot, FjallStates, FjallValuesSession, } from "#@typeberry/database-fjall";
5
5
  import { LmdbBlocks, HybridSerializedStates as LmdbHybridSerializedStates, LmdbRoot, LmdbStates, } from "#@typeberry/database-lmdb";
6
6
  import { Blake2b } from "#@typeberry/hash";
7
7
  import { ThreadPort } from "./port.js";
8
8
  // Re-exported so the fuzz target can open one values session per run and reuse
9
9
  // it across resets (see `HybridWorkerConfig` / `mainFuzz`).
10
10
  export { FjallValuesSession };
11
- /** Worker config for node.js, backed by the LMDB database. */
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
+ */
12
16
  export class LmdbWorkerConfig {
13
17
  nodeName;
14
18
  chainSpec;
@@ -22,10 +26,10 @@ export class LmdbWorkerConfig {
22
26
  }
23
27
  /** Restore node config from a transferable config object. */
24
28
  static async fromTransferable(decodeParams, config) {
25
- const blake2b = await Blake2b.createHasher();
26
- const chainSpec = ChainSpec.new(config.chainSpec);
27
- const workerParams = Decoder.decodeObject(decodeParams, config.workerParams, chainSpec);
28
- const ports = new Map(config.workerPorts.map(([name, port]) => [name, ThreadPort.fromTransferable(chainSpec, port)]));
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);
29
33
  return LmdbWorkerConfig.new({
30
34
  nodeName: config.nodeName,
31
35
  chainSpec,
@@ -48,7 +52,7 @@ export class LmdbWorkerConfig {
48
52
  this.ports = ports;
49
53
  this.ephemeral = ephemeral;
50
54
  }
51
- openDatabase(options = { readonly: true }) {
55
+ async openDatabase(options = { readonly: true }) {
52
56
  const lmdb = LmdbRoot.new(this.dbPath, {
53
57
  readOnly: options.readonly,
54
58
  ephemeral: this.ephemeral,
@@ -62,14 +66,128 @@ export class LmdbWorkerConfig {
62
66
  /** Convert this config into a thread-transferable object. */
63
67
  intoTransferable(paramsCodec) {
64
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
+ }
78
+ /** Worker config for node.js, backed by a shared fjall engine. */
79
+ export class FjallWorkerConfig {
80
+ nodeName;
81
+ chainSpec;
82
+ workerParams;
83
+ dbPath;
84
+ blake2b;
85
+ ports;
86
+ ephemeral;
87
+ cacheSizeBytes;
88
+ static new({ nodeName, chainSpec, workerParams, dbPath, blake2b, ports = new Map(), ephemeral = false, cacheSizeBytes, }) {
89
+ return new FjallWorkerConfig(nodeName, chainSpec, workerParams, dbPath, blake2b, ports, ephemeral, cacheSizeBytes);
90
+ }
91
+ /** Restore node config from a transferable config object. */
92
+ static async fromTransferable(decodeParams, config) {
93
+ if (config.databaseBackend !== "fjall") {
94
+ throw new Error(`Expected fjall worker config, got ${config.databaseBackend}.`);
95
+ }
96
+ const { blake2b, chainSpec, workerParams, ports } = await decodeTransferableConfig(decodeParams, config);
97
+ return FjallWorkerConfig.new({
98
+ nodeName: config.nodeName,
99
+ chainSpec,
100
+ workerParams,
101
+ dbPath: config.dbPath,
102
+ blake2b,
103
+ ports,
104
+ cacheSizeBytes: config.cacheSizeBytes,
105
+ });
106
+ }
107
+ constructor(nodeName, chainSpec, workerParams, dbPath, blake2b, ports,
108
+ // Kept for the fuzz/importer path. When set, persist() is skipped.
109
+ ephemeral = false, cacheSizeBytes = undefined) {
110
+ this.nodeName = nodeName;
111
+ this.chainSpec = chainSpec;
112
+ this.workerParams = workerParams;
113
+ this.dbPath = dbPath;
114
+ this.blake2b = blake2b;
115
+ this.ports = ports;
116
+ this.ephemeral = ephemeral;
117
+ this.cacheSizeBytes = cacheSizeBytes;
118
+ }
119
+ async openDatabase(options = { readonly: true }) {
120
+ const fjall = await FjallRoot.open(this.dbPath, {
121
+ readOnly: options.readonly,
122
+ ephemeral: this.ephemeral,
123
+ cacheSizeBytes: this.cacheSizeBytes,
124
+ });
125
+ let blocks = null;
126
+ let states = null;
127
+ try {
128
+ [blocks, states] = await Promise.all([
129
+ FjallBlocks.open(this.chainSpec, fjall),
130
+ FjallStates.open(this.chainSpec, this.blake2b, fjall),
131
+ ]);
132
+ }
133
+ catch (e) {
134
+ await fjall.close();
135
+ throw e;
136
+ }
137
+ return {
138
+ getBlocksDb: () => {
139
+ if (blocks === null) {
140
+ throw new Error("Fjall database is closed.");
141
+ }
142
+ return blocks;
143
+ },
144
+ getStatesDb: () => {
145
+ if (states === null) {
146
+ throw new Error("Fjall database is closed.");
147
+ }
148
+ return states;
149
+ },
150
+ close: async () => {
151
+ blocks = null;
152
+ states = null;
153
+ await fjall.close();
154
+ },
155
+ };
156
+ }
157
+ /** Convert this config into a thread-transferable object. */
158
+ intoTransferable(paramsCodec) {
159
+ return {
160
+ databaseBackend: "fjall",
65
161
  nodeName: this.nodeName,
66
162
  chainSpec: this.chainSpec,
67
163
  workerParams: Encoder.encodeObject(paramsCodec, this.workerParams, this.chainSpec).raw,
68
164
  dbPath: this.dbPath,
69
165
  workerPorts: Array.from(this.ports.entries()).map(([name, port]) => [name, port.intoTransferable()]),
166
+ cacheSizeBytes: this.cacheSizeBytes,
70
167
  };
71
168
  }
72
169
  }
170
+ async function decodeTransferableConfig(decodeParams, config) {
171
+ const blake2b = await Blake2b.createHasher();
172
+ const chainSpec = ChainSpec.new(config.chainSpec);
173
+ const workerParams = Decoder.decodeObject(decodeParams, config.workerParams, chainSpec);
174
+ const ports = new Map(config.workerPorts.map(([name, port]) => [name, ThreadPort.fromTransferable(chainSpec, port)]));
175
+ return {
176
+ blake2b,
177
+ chainSpec,
178
+ workerParams,
179
+ ports,
180
+ };
181
+ }
182
+ /** Restore a persistent worker config from its transferable form. */
183
+ export async function persistentConfigFromTransferable(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
+ }
190
+ }
73
191
  /**
74
192
  * Collect the transferable objects (communication ports) embedded in a config.
75
193
  *
@@ -103,7 +221,7 @@ export class InMemWorkerConfig {
103
221
  this.blocks = InMemoryBlocks.new();
104
222
  this.states = InMemorySerializedStates.withHasher({ chainSpec, blake2b });
105
223
  }
106
- openDatabase(_options = { readonly: true }) {
224
+ async openDatabase(_options = { readonly: true }) {
107
225
  // opening/closing db doesn't do anything, we persist the state.
108
226
  return {
109
227
  getBlocksDb: () => this.blocks,
@@ -159,7 +277,7 @@ export class HybridWorkerConfig {
159
277
  this.states = states;
160
278
  this.blocks = InMemoryBlocks.new();
161
279
  }
162
- openDatabase(_options = { readonly: true }) {
280
+ async openDatabase(_options = { readonly: true }) {
163
281
  return {
164
282
  getBlocksDb: () => this.blocks,
165
283
  getStatesDb: () => this.states,
@@ -2,11 +2,12 @@ import assert from "node:assert";
2
2
  import * as fs from "node:fs";
3
3
  import { describe, it } from "node:test";
4
4
  import { MessageChannel } from "node:worker_threads";
5
+ import { Bytes } from "#@typeberry/bytes";
5
6
  import { codec } from "#@typeberry/codec";
6
7
  import { tinyChainSpec } from "#@typeberry/config";
7
- import { Blake2b } from "#@typeberry/hash";
8
+ import { Blake2b, HASH_SIZE } from "#@typeberry/hash";
8
9
  import { tryAsU32 } from "#@typeberry/numbers";
9
- import { configTransferList, HybridWorkerConfig, LmdbWorkerConfig } from "./config.js";
10
+ import { configTransferList, FjallWorkerConfig, HybridWorkerConfig, LmdbWorkerConfig } from "./config.js";
10
11
  import { ThreadPort } from "./port.js";
11
12
  const spec = tinyChainSpec;
12
13
  describe("LmdbWorkerConfig transfer list", () => {
@@ -40,6 +41,54 @@ describe("LmdbWorkerConfig transfer list", () => {
40
41
  }
41
42
  });
42
43
  });
44
+ describe("FjallWorkerConfig transfer list", () => {
45
+ it("surfaces embedded worker ports and marks the backend", async () => {
46
+ const blake2b = await Blake2b.createHasher();
47
+ const [portA, portB] = ThreadPort.pair(spec);
48
+ const config = FjallWorkerConfig.new({
49
+ nodeName: "node",
50
+ chainSpec: spec,
51
+ workerParams: tryAsU32(7),
52
+ dbPath: "db",
53
+ blake2b,
54
+ ports: new Map([["authorship-network", portA]]),
55
+ });
56
+ try {
57
+ const transferable = config.intoTransferable(codec.varU32);
58
+ assert.strictEqual(transferable.databaseBackend, "fjall");
59
+ assert.strictEqual(configTransferList(transferable).length, 1);
60
+ }
61
+ finally {
62
+ portA.close();
63
+ portB.close();
64
+ }
65
+ });
66
+ it("opens writable and read-only handles over one shared fjall path", async () => {
67
+ const blake2b = await Blake2b.createHasher();
68
+ const dbPath = fs.mkdtempSync("typeberry-fjall-worker-");
69
+ const config = FjallWorkerConfig.new({
70
+ nodeName: "node",
71
+ chainSpec: spec,
72
+ workerParams: undefined,
73
+ dbPath,
74
+ blake2b,
75
+ });
76
+ let writer = null;
77
+ let reader = null;
78
+ try {
79
+ writer = await config.openDatabase({ readonly: false });
80
+ reader = await config.openDatabase({ readonly: true });
81
+ const best = Bytes.fill(HASH_SIZE, 9).asOpaque();
82
+ await writer.getBlocksDb().setBestHeaderHash(best);
83
+ assert.strictEqual(reader.getBlocksDb().getBestHeaderHash().toString(), best.toString());
84
+ }
85
+ finally {
86
+ await reader?.close();
87
+ await writer?.close();
88
+ fs.rmSync(dbPath, { recursive: true, force: true });
89
+ }
90
+ });
91
+ });
43
92
  describe("HybridWorkerConfig", () => {
44
93
  // Both persistent backends must construct asynchronously and hand out a
45
94
  // working db. fjall is the experimental backend we want to benchmark.
@@ -57,7 +106,7 @@ describe("HybridWorkerConfig", () => {
57
106
  ephemeral: true,
58
107
  backend,
59
108
  });
60
- const db = config.openDatabase({ readonly: false });
109
+ const db = await config.openDatabase({ readonly: false });
61
110
  const states = db.getStatesDb();
62
111
  try {
63
112
  assert.notStrictEqual(db.getBlocksDb(), undefined);
@@ -18,6 +18,7 @@ export declare class ThreadPort implements Port {
18
18
  static pair(spec: ChainSpec): [ThreadPort, ThreadPort];
19
19
  readonly threadId: number;
20
20
  private readonly events;
21
+ private readonly pendingMessages;
21
22
  static new(spec: ChainSpec, port: MessagePort): ThreadPort;
22
23
  private constructor();
23
24
  static fromTransferable(spec: ChainSpec, { port }: TransferablePort): ThreadPort;
@@ -28,5 +29,7 @@ export declare class ThreadPort implements Port {
28
29
  on<T>(event: string, codec: Codec<T>, callback: (msg: Envelope<T>) => void): () => void;
29
30
  once<T>(event: string, codec: Codec<T>, callback: (msg: Envelope<T>) => void): () => void;
30
31
  postMessage<T>(event: string, codec: Codec<T>, msg: Envelope<T>): void;
32
+ private queuePending;
33
+ private flushPending;
31
34
  }
32
35
  //# sourceMappingURL=port.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/api-node/port.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,WAAW,EAA+B,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAE,KAAK,KAAK,EAAoB,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AAErE,MAAM,MAAM,OAAO,GAAG;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAIF,qDAAqD;AACrD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AACF,qBAAa,UAAW,YAAW,IAAI;IAenC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAfvB,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC;IAMtD,SAAgB,QAAQ,SAAY;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAE7C,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW;IAI7C,OAAO;IAgBP,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,EAAE,gBAAgB;IAInE,gBAAgB,IAAI,gBAAgB;IAIpC,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,cAAc;IAkBtB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;IAI3C,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IASvF,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IASzF,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI;CAWvE"}
1
+ {"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/api-node/port.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,WAAW,EAA+B,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAE,KAAK,KAAK,EAAoB,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AAErE,MAAM,MAAM,OAAO,GAAG;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAIF,qDAAqD;AACrD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AACF,qBAAa,UAAW,YAAW,IAAI;IAgBnC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAhBvB,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC;IAMtD,SAAgB,QAAQ,SAAY;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAgC;IAEhE,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW;IAI7C,OAAO;IAmBP,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,EAAE,gBAAgB;IAInE,gBAAgB,IAAI,gBAAgB;IAIpC,KAAK,IAAI,IAAI;IAKb,OAAO,CAAC,cAAc;IAkBtB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;IAI3C,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IAUvF,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IAoBzF,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI;IAYtE,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,YAAY;CAUrB"}
@@ -2,7 +2,6 @@ import EventEmitter from "node:events";
2
2
  import { threadId } from "node:worker_threads";
3
3
  import { Decoder, Encoder } from "#@typeberry/codec";
4
4
  import { Logger } from "#@typeberry/logger";
5
- import { check } from "#@typeberry/utils";
6
5
  const MESSAGE_KEYS = ["eventName", "responseId", "data"];
7
6
  const logger = Logger.new(import.meta.filename, "workers/api");
8
7
  export class ThreadPort {
@@ -14,6 +13,7 @@ export class ThreadPort {
14
13
  }
15
14
  threadId = threadId;
16
15
  events = new EventEmitter();
16
+ pendingMessages = new Map();
17
17
  static new(spec, port) {
18
18
  return new ThreadPort(spec, port);
19
19
  }
@@ -26,7 +26,10 @@ export class ThreadPort {
26
26
  return;
27
27
  }
28
28
  const { eventName, responseId, data } = input;
29
- check `${this.events.listeners(eventName).length > 0} No listeners for received event ${eventName}!`;
29
+ if (this.events.listeners(eventName).length === 0) {
30
+ this.queuePending(input);
31
+ return;
32
+ }
30
33
  this.events.emit(eventName, responseId, data);
31
34
  });
32
35
  }
@@ -38,6 +41,7 @@ export class ThreadPort {
38
41
  }
39
42
  close() {
40
43
  this.port.close();
44
+ this.pendingMessages.clear();
41
45
  }
42
46
  createListener(codec, callback) {
43
47
  return (responseId, data) => {
@@ -64,12 +68,22 @@ export class ThreadPort {
64
68
  on(event, codec, callback) {
65
69
  const listener = this.createListener(codec, callback);
66
70
  this.events.on(event, listener);
71
+ this.flushPending(event, listener);
67
72
  return () => {
68
73
  this.events.off(event, listener);
69
74
  };
70
75
  }
71
76
  once(event, codec, callback) {
72
77
  const listener = this.createListener(codec, callback);
78
+ const pending = this.pendingMessages.get(event);
79
+ const pendingMessage = pending?.shift();
80
+ if (pending?.length === 0) {
81
+ this.pendingMessages.delete(event);
82
+ }
83
+ if (pendingMessage !== undefined) {
84
+ listener(pendingMessage.responseId, pendingMessage.data);
85
+ return () => { };
86
+ }
73
87
  this.events.once(event, listener);
74
88
  return () => {
75
89
  this.events.off(event, listener);
@@ -86,6 +100,21 @@ export class ThreadPort {
86
100
  // always returns owned uint8arrays.
87
101
  this.port.postMessage(message, [encoded.raw.buffer]);
88
102
  }
103
+ queuePending(msg) {
104
+ const pending = this.pendingMessages.get(msg.eventName) ?? [];
105
+ pending.push(msg);
106
+ this.pendingMessages.set(msg.eventName, pending);
107
+ }
108
+ flushPending(event, listener) {
109
+ const pending = this.pendingMessages.get(event);
110
+ if (pending === undefined) {
111
+ return;
112
+ }
113
+ this.pendingMessages.delete(event);
114
+ for (const msg of pending) {
115
+ listener(msg.responseId, msg.data);
116
+ }
117
+ }
89
118
  }
90
119
  function isMessage(data) {
91
120
  const isObject = data !== null && typeof data === "object";
@@ -36,4 +36,21 @@ describe("ThreadPort", () => {
36
36
  data: tryAsU32(42),
37
37
  });
38
38
  });
39
+ it("should deliver messages sent before the listener is attached", async () => {
40
+ const tx = ThreadPort.new(spec, channel.port1);
41
+ const rx = ThreadPort.new(spec, channel.port2);
42
+ tx.postMessage("hello", codec.varU32, {
43
+ responseId: "10",
44
+ data: tryAsU32(42),
45
+ });
46
+ await setTimeout(10);
47
+ let received = null;
48
+ rx.on("hello", codec.varU32, (msg) => {
49
+ received = msg;
50
+ });
51
+ assert.deepStrictEqual(received, {
52
+ responseId: "10",
53
+ data: tryAsU32(42),
54
+ });
55
+ });
39
56
  });
@@ -2,7 +2,7 @@ import { type MessagePort, Worker } from "node:worker_threads";
2
2
  import type { Decode, Encode } from "#@typeberry/codec";
3
3
  import { Listener } from "#@typeberry/listener";
4
4
  import type { Api, Internal, LousyProtocol } from "#@typeberry/workers-api/types.js";
5
- import { LmdbWorkerConfig, type TransferableConfig } from "./config.js";
5
+ import { type PersistentWorkerConfig, type TransferableConfig } from "./config.js";
6
6
  /** Type of the control plane message. */
7
7
  export declare enum WorkerControlPlaneMsg {
8
8
  /** Transfers a communication port to some other thread. */
@@ -31,7 +31,7 @@ export type WorkerControlPlane = ({
31
31
  /**
32
32
  * Invoked by the main thread, to spawn a worker thread and initiate communication channel.
33
33
  */
34
- export declare function spawnWorker<To, From, Params>(protocol: LousyProtocol<To, From>, bootstrapPath: URL, config: LmdbWorkerConfig<Params>, paramsEncoder: Encode<Params>): {
34
+ export declare function spawnWorker<To, From, Params>(protocol: LousyProtocol<To, From>, bootstrapPath: URL, config: PersistentWorkerConfig<Params>, paramsEncoder: Encode<Params>): {
35
35
  api: Api<typeof protocol>;
36
36
  worker: Worker;
37
37
  workerFinished: Promise<void>;
@@ -40,7 +40,7 @@ export declare function spawnWorker<To, From, Params>(protocol: LousyProtocol<To
40
40
  * Initialize worker thread by awaiting the config message.
41
41
  */
42
42
  export declare function initWorker<To, From, Params>(protocol: LousyProtocol<To, From>, paramsDecoder: Decode<Params>): Promise<{
43
- config: LmdbWorkerConfig<Params>;
43
+ config: PersistentWorkerConfig<Params>;
44
44
  comms: Internal<typeof protocol>;
45
45
  threadComms: Listener<ThreadComms>;
46
46
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/api-node/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,WAAW,EAAc,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC3F,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAI/C,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,EAAsB,gBAAgB,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAM5F,yCAAyC;AACzC,oBAAY,qBAAqB;IAC/B,2DAA2D;IAC3D,iBAAiB,IAAI;IACrB,sCAAsC;IACtC,MAAM,IAAI;CACX;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,0BAA0B;IAC1B,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AACF,yFAAyF;AACzF,MAAM,MAAM,kBAAkB,GAC1B,CAAC;IACC,wDAAwD;IACxD,IAAI,EAAE,qBAAqB,CAAC,iBAAiB,CAAC;CAC/C,GAAG,WAAW,CAAC,GAChB;IACE,8EAA8E;IAC9E,IAAI,EAAE,qBAAqB,CAAC,MAAM,CAAC;IACnC,sCAAsC;IACtC,UAAU,EAAE,WAAW,CAAC;IACxB,4BAA4B;IAC5B,MAAM,EAAE,kBAAkB,CAAC;CAC5B,CAAC;AAeN;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAC1C,QAAQ,EAAE,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,EACjC,aAAa,EAAE,GAAG,EAClB,MAAM,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAChC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,GAC5B;IACD,GAAG,EAAE,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B,CAoCA;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAC/C,QAAQ,EAAE,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,EACjC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,GAC5B,OAAO,CAAC;IACT,MAAM,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACjC,KAAK,EAAE,QAAQ,CAAC,OAAO,QAAQ,CAAC,CAAC;IACjC,WAAW,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CACpC,CAAC,CAmDD"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/api-node/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,WAAW,EAAc,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC3F,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAI/C,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,EAEL,KAAK,sBAAsB,EAE3B,KAAK,kBAAkB,EACxB,MAAM,aAAa,CAAC;AAMrB,yCAAyC;AACzC,oBAAY,qBAAqB;IAC/B,2DAA2D;IAC3D,iBAAiB,IAAI;IACrB,sCAAsC;IACtC,MAAM,IAAI;CACX;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,0BAA0B;IAC1B,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AACF,yFAAyF;AACzF,MAAM,MAAM,kBAAkB,GAC1B,CAAC;IACC,wDAAwD;IACxD,IAAI,EAAE,qBAAqB,CAAC,iBAAiB,CAAC;CAC/C,GAAG,WAAW,CAAC,GAChB;IACE,8EAA8E;IAC9E,IAAI,EAAE,qBAAqB,CAAC,MAAM,CAAC;IACnC,sCAAsC;IACtC,UAAU,EAAE,WAAW,CAAC;IACxB,4BAA4B;IAC5B,MAAM,EAAE,kBAAkB,CAAC;CAC5B,CAAC;AAeN;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAC1C,QAAQ,EAAE,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,EACjC,aAAa,EAAE,GAAG,EAClB,MAAM,EAAE,sBAAsB,CAAC,MAAM,CAAC,EACtC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,GAC5B;IACD,GAAG,EAAE,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B,CAoCA;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAC/C,QAAQ,EAAE,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,EACjC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,GAC5B,OAAO,CAAC;IACT,MAAM,EAAE,sBAAsB,CAAC,MAAM,CAAC,CAAC;IACvC,KAAK,EAAE,QAAQ,CAAC,OAAO,QAAQ,CAAC,CAAC;IACjC,WAAW,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CACpC,CAAC,CAwDD"}
@@ -3,7 +3,7 @@ import { Listener } from "#@typeberry/listener";
3
3
  import { Level, Logger } from "#@typeberry/logger";
4
4
  import { assertNever } from "#@typeberry/utils";
5
5
  import { Channel } from "#@typeberry/workers-api";
6
- import { configTransferList, LmdbWorkerConfig } from "./config.js";
6
+ import { configTransferList, persistentConfigFromTransferable, } from "./config.js";
7
7
  import { logHeapLimit, workerResourceLimits } from "./host-environment.js";
8
8
  import { ThreadPort } from "./port.js";
9
9
  const logger = Logger.new(import.meta.filename, "workers");
@@ -70,13 +70,14 @@ export async function initWorker(protocol, paramsDecoder) {
70
70
  logger.trace `Worker ${protocol.name} starting.`;
71
71
  logHeapLimit(logger, protocol.name);
72
72
  return new Promise((resolve, reject) => {
73
- if (parentPort === null) {
73
+ const workerParentPort = parentPort;
74
+ if (workerParentPort === null) {
74
75
  throw new Error(`Unable to start ${protocol.name} worker. Not running in a worker thread!`);
75
76
  }
76
- parentPort.once("close", () => reject(new Error(`(${protocol.name}) parent port closed too early`)));
77
+ workerParentPort.once("close", () => reject(new Error(`(${protocol.name}) parent port closed too early`)));
77
78
  let isResolved = false;
78
79
  const threadComms = new Listener();
79
- parentPort.on("message", async (msg) => {
80
+ workerParentPort.on("message", async (msg) => {
80
81
  if (!isControlPlane(msg)) {
81
82
  logger.error `--> (${protocol.name}) received unexpected message: ${msg}`;
82
83
  return;
@@ -93,14 +94,19 @@ export async function initWorker(protocol, paramsDecoder) {
93
94
  }
94
95
  logger.trace `--> (${protocol.name}) received configuration.`;
95
96
  isResolved = true;
96
- const config = await LmdbWorkerConfig.fromTransferable(paramsDecoder, msg.config);
97
- const rxPort = ThreadPort.new(config.chainSpec, msg.parentPort);
98
- const comms = Channel.rx(protocol, rxPort);
99
- resolve({
100
- config,
101
- comms,
102
- threadComms,
103
- });
97
+ try {
98
+ const config = await persistentConfigFromTransferable(paramsDecoder, msg.config);
99
+ const rxPort = ThreadPort.new(config.chainSpec, msg.parentPort);
100
+ const comms = Channel.rx(protocol, rxPort);
101
+ resolve({
102
+ config,
103
+ comms,
104
+ threadComms,
105
+ });
106
+ }
107
+ catch (e) {
108
+ reject(e);
109
+ }
104
110
  return;
105
111
  }
106
112
  assertNever(msg.kind);
@@ -18,7 +18,7 @@ export async function main(config, comms, networkingComms) {
18
18
  await initWasm();
19
19
  logger.info `🎁 Block Authorship running`;
20
20
  const chainSpec = config.chainSpec;
21
- const db = config.openDatabase();
21
+ const db = await config.openDatabase();
22
22
  const blocks = db.getBlocksDb();
23
23
  const states = db.getStatesDb();
24
24
  const getBestState = () => {
@@ -4,6 +4,10 @@ import type { BlocksDb } from "#@typeberry/database";
4
4
  export interface FinalityResult {
5
5
  /** The newly finalized block hash. */
6
6
  finalizedHash: HeaderHash;
7
+ /** All newly finalized block hashes, ancestor-first, ending at `finalizedHash`. */
8
+ finalizedChain: HeaderHash[];
9
+ /** Blocks that became finalized in this round, ancestor-first. */
10
+ newlyFinalizedHeaders: HeaderHash[];
7
11
  /** Block hashes whose states are no longer needed and can be pruned. */
8
12
  prunableStateHashes: HeaderHash[];
9
13
  }
@@ -1 +1 @@
1
- {"version":3,"file":"finality.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/importer/finality.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAKpD,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,sCAAsC;IACtC,aAAa,EAAE,UAAU,CAAC;IAC1B,wEAAwE;IACxE,mBAAmB,EAAE,UAAU,EAAE,CAAC;CACnC;AAED,8DAA8D;AAC9D,MAAM,WAAW,SAAS;IACxB,8FAA8F;IAC9F,eAAe,CAAC,UAAU,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAAC;CAChE;AAKD;;;;;;;GAOG;AACH,qBAAa,cAAe,YAAW,SAAS;IAS5C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IATxB,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,WAAW,CAAe;IAElC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc;IAI9D,OAAO;IAQP,eAAe,CAAC,UAAU,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI;CAyF/D"}
1
+ {"version":3,"file":"finality.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/importer/finality.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAKpD,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,sCAAsC;IACtC,aAAa,EAAE,UAAU,CAAC;IAC1B,mFAAmF;IACnF,cAAc,EAAE,UAAU,EAAE,CAAC;IAC7B,kEAAkE;IAClE,qBAAqB,EAAE,UAAU,EAAE,CAAC;IACpC,wEAAwE;IACxE,mBAAmB,EAAE,UAAU,EAAE,CAAC;CACnC;AAED,8DAA8D;AAC9D,MAAM,WAAW,SAAS;IACxB,8FAA8F;IAC9F,eAAe,CAAC,UAAU,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAAC;CAChE;AAKD;;;;;;;GAOG;AACH,qBAAa,cAAe,YAAW,SAAS;IAS5C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IATxB,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,WAAW,CAAe;IAElC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc;IAI9D,OAAO;IAQP,eAAe,CAAC,UAAU,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI;CAkG/D"}
@@ -71,15 +71,24 @@ export class DummyFinalizer {
71
71
  // The newly finalized block sits at index (length - 1 - depth).
72
72
  const finalizedIdx = extendedChain.length - 1 - this.depth;
73
73
  const finalizedHash = extendedChain[finalizedIdx];
74
+ // Everything up to (and including) that index becomes finalized now.
75
+ const finalizedChain = extendedChain.slice(0, finalizedIdx + 1);
74
76
  // Collect prunable hashes and rebuild the unfinalized set.
75
77
  // The previously finalized block's state is no longer needed.
76
78
  const prunable = [this.lastFinalizedHash];
79
+ const newlyFinalized = [];
77
80
  const newUnfinalized = [];
78
81
  for (const chain of this.unfinalized) {
79
82
  // Find the finalized block in this chain.
80
83
  const finIdx = chain.findIndex((h) => h.isEqualTo(finalizedHash));
81
84
  if (finIdx !== -1) {
82
85
  // Chain contains the finalized block — it's still alive.
86
+ // Collect newly finalized blocks only once (forks share prefixes).
87
+ if (newlyFinalized.length === 0) {
88
+ for (let i = 0; i <= finIdx; i++) {
89
+ newlyFinalized.push(chain[i]);
90
+ }
91
+ }
83
92
  // Prune states for blocks before the finalized block.
84
93
  for (let i = 0; i < finIdx; i++) {
85
94
  prunable.push(chain[i]);
@@ -100,6 +109,6 @@ export class DummyFinalizer {
100
109
  }
101
110
  this.lastFinalizedHash = finalizedHash;
102
111
  this.unfinalized = newUnfinalized;
103
- return { finalizedHash, prunableStateHashes: prunable };
112
+ return { finalizedHash, finalizedChain, newlyFinalizedHeaders: newlyFinalized, prunableStateHashes: prunable };
104
113
  }
105
114
  }
@@ -77,6 +77,8 @@ describe("DummyFinalizer", () => {
77
77
  const result = finalizer.onBlockImported(chain[6]);
78
78
  assertExists(result);
79
79
  assert.strictEqual(result.finalizedHash.isEqualTo(chain[3]), true);
80
+ // The whole prefix becomes finalized, ancestor-first.
81
+ assert.deepStrictEqual(result.finalizedChain.map((h) => h.toString()), chain.slice(0, 4).map((h) => h.toString()));
80
82
  });
81
83
  it("should prune prev finalized and depth predecessors on first finality", async () => {
82
84
  const db = InMemoryBlocks.new();
@@ -111,6 +113,7 @@ describe("DummyFinalizer", () => {
111
113
  const r1 = finalizer.onBlockImported(chain[4]);
112
114
  assertExists(r1);
113
115
  assert.strictEqual(r1.finalizedHash.isEqualTo(chain[2]), true);
116
+ assert.deepStrictEqual(r1.finalizedChain.map((h) => h.toString()), chain.slice(0, 3).map((h) => h.toString()));
114
117
  assert.strictEqual(r1.prunableStateHashes.length, 3);
115
118
  assert.ok(r1.prunableStateHashes[0].isEqualTo(genesis));
116
119
  assert.ok(r1.prunableStateHashes[1].isEqualTo(chain[0]));
@@ -122,6 +125,8 @@ describe("DummyFinalizer", () => {
122
125
  const r2 = finalizer.onBlockImported(chain[7]);
123
126
  assertExists(r2);
124
127
  assert.strictEqual(r2.finalizedHash.isEqualTo(chain[5]), true);
128
+ // Only the blocks finalized in this round, not the ones from r1.
129
+ assert.deepStrictEqual(r2.finalizedChain.map((h) => h.toString()), chain.slice(3, 6).map((h) => h.toString()));
125
130
  assert.strictEqual(r2.prunableStateHashes.length, 3);
126
131
  assert.ok(r2.prunableStateHashes[0].isEqualTo(chain[2]));
127
132
  assert.ok(r2.prunableStateHashes[1].isEqualTo(chain[3]));
@@ -204,6 +209,7 @@ describe("DummyFinalizer", () => {
204
209
  const result = finalizer.onBlockImported(a5);
205
210
  assertExists(result);
206
211
  assert.strictEqual(result.finalizedHash.isEqualTo(a3), true);
212
+ assert.deepStrictEqual(result.finalizedChain.map((h) => h.toString()), [a1, a2, a3].map((h) => h.toString()));
207
213
  // Fork B [B1, B2] is dead (doesn't contain A3). B1 and B2 should be pruned.
208
214
  // Also: genesis (prev finalized) and A1, A2 pruned (before A3 in chain A).
209
215
  const prunedStrings = result.prunableStateHashes.map((h) => h.toString());
@@ -1 +1 @@
1
- {"version":3,"file":"importer.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/importer/importer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"importer.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/importer/importer.ts"],"names":[],"mappings":"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"}
@@ -177,6 +177,9 @@ export class Importer {
177
177
  const pruneBlocks = this.options.pruneBlocks ?? false;
178
178
  this.logger
179
179
  .info `🦭 Finalized: ${finality.finalizedHash.toStringTruncated()} (${finality.prunableStateHashes.length} to prune, blocks: ${pruneBlocks})`;
180
+ // Commit the finalized blocks BEFORE pruning: `markUnused` treats states
181
+ // with a pending value delta as dead forks and releases their values.
182
+ this.states.commitFinalized(finality.newlyFinalizedHeaders);
180
183
  for (const hash of finality.prunableStateHashes) {
181
184
  this.states.markUnused(hash);
182
185
  if (pruneBlocks) {
@@ -9,7 +9,7 @@ export type CreateImporterOptions = {
9
9
  };
10
10
  export declare function createImporter(config: Config, options?: CreateImporterOptions): Promise<{
11
11
  importer: Importer;
12
- db: ReturnType<Config["openDatabase"]>;
12
+ db: Awaited<ReturnType<Config["openDatabase"]>>;
13
13
  }>;
14
14
  /**
15
15
  * The `BlockImporter` listens to `block` signals, where it expects
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/importer/main.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAGtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAMtE,MAAM,MAAM,MAAM,GAAG,YAAY,CAAC,cAAc,EAAE,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAE/F,MAAM,MAAM,qBAAqB,GAAG;IAClC,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC,CAAC;AAEF,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC;IACT,QAAQ,EAAE,QAAQ,CAAC;IACnB,EAAE,EAAE,UAAU,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;CACxC,CAAC,CA8BD;AAED;;;;;GAKG;AACH,wBAAsB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,iBAwCjE"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/importer/main.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAGtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAMtE,MAAM,MAAM,MAAM,GAAG,YAAY,CAAC,cAAc,EAAE,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAE/F,MAAM,MAAM,qBAAqB,GAAG;IAClC,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC,CAAC;AAEF,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC;IACT,QAAQ,EAAE,QAAQ,CAAC;IACnB,EAAE,EAAE,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;CACjD,CAAC,CA8BD;AAED;;;;;GAKG;AACH,wBAAsB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,iBAwCjE"}
@@ -10,7 +10,7 @@ const keccakHasher = keccak.KeccakHasher.create();
10
10
  const blake2b = Blake2b.createHasher();
11
11
  export async function createImporter(config, options = {}) {
12
12
  const chainSpec = config.chainSpec;
13
- const db = config.openDatabase({ readonly: false });
13
+ const db = await config.openDatabase({ readonly: false });
14
14
  const pvm = config.workerParams.pvm;
15
15
  const blocks = db.getBlocksDb();
16
16
  const states = db.getStatesDb();