@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.
- package/package.json +1 -1
- package/packages/jam/config-node/node-config.d.ts +20 -2
- package/packages/jam/config-node/node-config.d.ts.map +1 -1
- package/packages/jam/config-node/node-config.js +25 -3
- package/packages/jam/config-node/node-config.test.js +8 -1
- package/packages/jam/database/serialized-states-db.d.ts +4 -1
- package/packages/jam/database/serialized-states-db.d.ts.map +1 -1
- package/packages/jam/database/serialized-states-db.js +17 -5
- package/packages/jam/database/serialized-states-db.test.d.ts +2 -0
- package/packages/jam/database/serialized-states-db.test.d.ts.map +1 -0
- package/packages/jam/database/serialized-states-db.test.js +109 -0
- package/packages/jam/database-fjall/blocks.d.ts +25 -0
- package/packages/jam/database-fjall/blocks.d.ts.map +1 -0
- package/packages/jam/database-fjall/blocks.js +86 -0
- package/packages/jam/database-fjall/blocks.test.d.ts +2 -0
- package/packages/jam/database-fjall/blocks.test.d.ts.map +1 -0
- package/packages/jam/database-fjall/blocks.test.js +62 -0
- package/packages/jam/database-fjall/hybrid-states.d.ts.map +1 -1
- package/packages/jam/database-fjall/hybrid-states.js +11 -2
- package/packages/jam/database-fjall/hybrid-states.test.js +94 -2
- package/packages/jam/database-fjall/index.d.ts +2 -0
- package/packages/jam/database-fjall/index.d.ts.map +1 -1
- package/packages/jam/database-fjall/index.js +2 -0
- package/packages/jam/database-fjall/root.d.ts +14 -8
- package/packages/jam/database-fjall/root.d.ts.map +1 -1
- package/packages/jam/database-fjall/root.js +25 -9
- package/packages/jam/database-fjall/states.d.ts +31 -0
- package/packages/jam/database-fjall/states.d.ts.map +1 -0
- package/packages/jam/database-fjall/states.js +99 -0
- package/packages/jam/database-fjall/states.test.d.ts +2 -0
- package/packages/jam/database-fjall/states.test.d.ts.map +1 -0
- package/packages/jam/database-fjall/states.test.js +150 -0
- package/packages/jam/database-lmdb/hybrid-states.test.js +94 -2
- package/packages/jam/node/export.d.ts.map +1 -1
- package/packages/jam/node/export.js +18 -9
- package/packages/jam/node/main-importer.d.ts +4 -2
- package/packages/jam/node/main-importer.d.ts.map +1 -1
- package/packages/jam/node/main-importer.js +26 -12
- package/packages/jam/node/main.d.ts.map +1 -1
- package/packages/jam/node/main.js +42 -15
- package/packages/jam/node/workers.d.ts +4 -4
- package/packages/jam/node/workers.d.ts.map +1 -1
- package/packages/workers/api/config.d.ts +2 -2
- package/packages/workers/api/config.d.ts.map +1 -1
- package/packages/workers/api/config.js +1 -1
- package/packages/workers/api/port.d.ts +5 -1
- package/packages/workers/api/port.d.ts.map +1 -1
- package/packages/workers/api/port.js +56 -13
- package/packages/workers/api/port.test.js +35 -0
- package/packages/workers/api-node/config.d.ts +45 -4
- package/packages/workers/api-node/config.d.ts.map +1 -1
- package/packages/workers/api-node/config.js +127 -9
- package/packages/workers/api-node/config.test.js +52 -3
- package/packages/workers/api-node/port.d.ts +3 -0
- package/packages/workers/api-node/port.d.ts.map +1 -1
- package/packages/workers/api-node/port.js +31 -2
- package/packages/workers/api-node/port.test.js +17 -0
- package/packages/workers/api-node/protocol.d.ts +3 -3
- package/packages/workers/api-node/protocol.d.ts.map +1 -1
- package/packages/workers/api-node/protocol.js +18 -12
- package/packages/workers/block-authorship/main.js +1 -1
- package/packages/workers/importer/finality.d.ts +4 -0
- package/packages/workers/importer/finality.d.ts.map +1 -1
- package/packages/workers/importer/finality.js +10 -1
- package/packages/workers/importer/finality.test.js +6 -0
- package/packages/workers/importer/importer.d.ts.map +1 -1
- package/packages/workers/importer/importer.js +3 -0
- package/packages/workers/importer/main.d.ts +1 -1
- package/packages/workers/importer/main.d.ts.map +1 -1
- package/packages/workers/importer/main.js +1 -1
- 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
|
-
/**
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
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;
|
|
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
|
-
|
|
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 {
|
|
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:
|
|
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:
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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;
|
|
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":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAiB,MAAM,kBAAkB,CAAC;AACvH,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACxF,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAiB,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC5F,OAAO,EAA0B,KAAK,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AAC3F,OAAO,EAA6B,MAAM,EAAkB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACvG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,EAAE,KAAK,sBAAsB,EAAiB,MAAM,YAAY,CAAC;AAExE,oBAAY,iBAAiB;IAC3B,QAAQ,IAAI;IACZ,GAAG,IAAI;IACP,MAAM,IAAI;CACX;AAED,MAAM,MAAM,aAAa,GACrB,WAAW,CAAC,iBAAiB,CAAC,QAAQ,EAAE,kBAAkB,CAAC,GAC3D,WAAW,CAAC,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,GAC5C,WAAW,CAAC,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;AAO5D,MAAM,MAAM,eAAe,GAAG;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,6CAA6C;AAC7C,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,GAAG,EAAE,UAAU,CAAC;IAChB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,QAAQ,CAAC;IACjB,MAAM,EAAE,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1C,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,MAAM,CAAC,EAAE,sBAAsB,CAAC;CACjC,CAAC;AAQF,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAU;IAG9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA0B;IAEhD,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2C;IAEnE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAEhD;;;;;OAKG;IACH,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ;IASzC,OAAO;IAyBP,6DAA6D;IAChD,mBAAmB;IAQnB,wBAAwB,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IAQzF,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,aAAa,CAAC,CAAC;YA2B9F,mBAAmB;
|
|
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;
|
|
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();
|