@typeberry/lib 0.9.0-959889e → 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 (113) hide show
  1. package/package.json +1 -1
  2. package/packages/core/collections/sorted-array.d.ts +1 -1
  3. package/packages/core/collections/sorted-array.d.ts.map +1 -1
  4. package/packages/core/collections/sorted-array.js +4 -2
  5. package/packages/core/collections/sorted-array.test.js +10 -0
  6. package/packages/core/collections/sorted-set.d.ts +1 -1
  7. package/packages/core/collections/sorted-set.d.ts.map +1 -1
  8. package/packages/core/collections/sorted-set.js +6 -2
  9. package/packages/core/collections/sorted-set.test.js +24 -0
  10. package/packages/core/utils/shutdown.d.ts +3 -3
  11. package/packages/core/utils/shutdown.d.ts.map +1 -1
  12. package/packages/core/utils/shutdown.js +2 -79
  13. package/packages/core/utils/shutdown.node.d.ts +5 -0
  14. package/packages/core/utils/shutdown.node.d.ts.map +1 -0
  15. package/packages/core/utils/shutdown.node.js +76 -0
  16. package/packages/core/utils/shutdown.test.js +32 -23
  17. package/packages/jam/config-node/node-config.d.ts +20 -2
  18. package/packages/jam/config-node/node-config.d.ts.map +1 -1
  19. package/packages/jam/config-node/node-config.js +25 -3
  20. package/packages/jam/config-node/node-config.test.js +8 -1
  21. package/packages/jam/database/index.d.ts +1 -0
  22. package/packages/jam/database/index.d.ts.map +1 -1
  23. package/packages/jam/database/index.js +1 -0
  24. package/packages/jam/database/leaf-db-update.d.ts +1 -0
  25. package/packages/jam/database/leaf-db-update.d.ts.map +1 -1
  26. package/packages/jam/database/leaf-db-update.js +22 -5
  27. package/packages/jam/database/leaf-db-update.test.d.ts +2 -0
  28. package/packages/jam/database/leaf-db-update.test.d.ts.map +1 -0
  29. package/packages/jam/database/leaf-db-update.test.js +81 -0
  30. package/packages/jam/database/serialized-states-db.d.ts +4 -0
  31. package/packages/jam/database/serialized-states-db.d.ts.map +1 -1
  32. package/packages/jam/database/serialized-states-db.js +18 -2
  33. package/packages/jam/database/serialized-states-db.test.d.ts +2 -0
  34. package/packages/jam/database/serialized-states-db.test.d.ts.map +1 -0
  35. package/packages/jam/database/serialized-states-db.test.js +109 -0
  36. package/packages/jam/database/states.d.ts +11 -0
  37. package/packages/jam/database/states.d.ts.map +1 -1
  38. package/packages/jam/database/states.js +3 -0
  39. package/packages/jam/database/value-refs.d.ts +117 -0
  40. package/packages/jam/database/value-refs.d.ts.map +1 -0
  41. package/packages/jam/database/value-refs.js +206 -0
  42. package/packages/jam/database/value-refs.test.d.ts +2 -0
  43. package/packages/jam/database/value-refs.test.d.ts.map +1 -0
  44. package/packages/jam/database/value-refs.test.js +165 -0
  45. package/packages/jam/database-fjall/blocks.d.ts +25 -0
  46. package/packages/jam/database-fjall/blocks.d.ts.map +1 -0
  47. package/packages/jam/database-fjall/blocks.js +86 -0
  48. package/packages/jam/database-fjall/blocks.test.d.ts +2 -0
  49. package/packages/jam/database-fjall/blocks.test.d.ts.map +1 -0
  50. package/packages/jam/database-fjall/blocks.test.js +62 -0
  51. package/packages/jam/database-fjall/hybrid-states.d.ts +14 -0
  52. package/packages/jam/database-fjall/hybrid-states.d.ts.map +1 -1
  53. package/packages/jam/database-fjall/hybrid-states.js +55 -6
  54. package/packages/jam/database-fjall/hybrid-states.test.js +94 -2
  55. package/packages/jam/database-fjall/index.d.ts +2 -0
  56. package/packages/jam/database-fjall/index.d.ts.map +1 -1
  57. package/packages/jam/database-fjall/index.js +2 -0
  58. package/packages/jam/database-fjall/root.d.ts +14 -8
  59. package/packages/jam/database-fjall/root.d.ts.map +1 -1
  60. package/packages/jam/database-fjall/root.js +25 -9
  61. package/packages/jam/database-fjall/states.d.ts +31 -0
  62. package/packages/jam/database-fjall/states.d.ts.map +1 -0
  63. package/packages/jam/database-fjall/states.js +99 -0
  64. package/packages/jam/database-fjall/states.test.d.ts +2 -0
  65. package/packages/jam/database-fjall/states.test.d.ts.map +1 -0
  66. package/packages/jam/database-fjall/states.test.js +150 -0
  67. package/packages/jam/database-lmdb/hybrid-states.d.ts +12 -0
  68. package/packages/jam/database-lmdb/hybrid-states.d.ts.map +1 -1
  69. package/packages/jam/database-lmdb/hybrid-states.js +45 -4
  70. package/packages/jam/database-lmdb/hybrid-states.test.js +94 -2
  71. package/packages/jam/database-lmdb/states.d.ts +1 -0
  72. package/packages/jam/database-lmdb/states.d.ts.map +1 -1
  73. package/packages/jam/database-lmdb/states.js +5 -0
  74. package/packages/jam/node/export.d.ts.map +1 -1
  75. package/packages/jam/node/export.js +18 -9
  76. package/packages/jam/node/main-fuzz.d.ts.map +1 -1
  77. package/packages/jam/node/main-fuzz.js +85 -71
  78. package/packages/jam/node/main-importer.d.ts +4 -2
  79. package/packages/jam/node/main-importer.d.ts.map +1 -1
  80. package/packages/jam/node/main-importer.js +26 -12
  81. package/packages/jam/node/main.d.ts.map +1 -1
  82. package/packages/jam/node/main.js +42 -15
  83. package/packages/jam/node/workers.d.ts +4 -4
  84. package/packages/jam/node/workers.d.ts.map +1 -1
  85. package/packages/workers/api/config.d.ts +2 -2
  86. package/packages/workers/api/config.d.ts.map +1 -1
  87. package/packages/workers/api/config.js +1 -1
  88. package/packages/workers/api/port.d.ts +5 -1
  89. package/packages/workers/api/port.d.ts.map +1 -1
  90. package/packages/workers/api/port.js +56 -13
  91. package/packages/workers/api/port.test.js +35 -0
  92. package/packages/workers/api-node/config.d.ts +45 -4
  93. package/packages/workers/api-node/config.d.ts.map +1 -1
  94. package/packages/workers/api-node/config.js +127 -9
  95. package/packages/workers/api-node/config.test.js +52 -3
  96. package/packages/workers/api-node/port.d.ts +3 -0
  97. package/packages/workers/api-node/port.d.ts.map +1 -1
  98. package/packages/workers/api-node/port.js +31 -2
  99. package/packages/workers/api-node/port.test.js +17 -0
  100. package/packages/workers/api-node/protocol.d.ts +3 -3
  101. package/packages/workers/api-node/protocol.d.ts.map +1 -1
  102. package/packages/workers/api-node/protocol.js +18 -12
  103. package/packages/workers/block-authorship/main.js +1 -1
  104. package/packages/workers/importer/finality.d.ts +4 -0
  105. package/packages/workers/importer/finality.d.ts.map +1 -1
  106. package/packages/workers/importer/finality.js +10 -1
  107. package/packages/workers/importer/finality.test.js +6 -0
  108. package/packages/workers/importer/importer.d.ts.map +1 -1
  109. package/packages/workers/importer/importer.js +3 -0
  110. package/packages/workers/importer/main.d.ts +1 -1
  111. package/packages/workers/importer/main.d.ts.map +1 -1
  112. package/packages/workers/importer/main.js +1 -1
  113. package/packages/workers/jam-network/main.js +1 -1
@@ -2,12 +2,14 @@
2
2
  import assert from "node:assert";
3
3
  import * as fs from "node:fs";
4
4
  import { afterEach, before, beforeEach, describe, it } from "node:test";
5
+ import { tryAsServiceId } from "#@typeberry/block";
5
6
  import { Bytes, BytesBlob } from "#@typeberry/bytes";
6
7
  import { tinyChainSpec } from "#@typeberry/config";
7
8
  import { Blake2b, HASH_SIZE } from "#@typeberry/hash";
8
- import { InMemoryState } from "#@typeberry/state";
9
+ import { InMemoryState, StorageItem, UpdateStorage, } from "#@typeberry/state";
10
+ import { testState } from "#@typeberry/state/test.utils.js";
9
11
  import { StateEntries } from "#@typeberry/state-merkleization";
10
- import { deepEqual, OK, Result } from "#@typeberry/utils";
12
+ import { asOpaqueType, deepEqual, OK, Result } from "#@typeberry/utils";
11
13
  import { HybridSerializedStates } from "./hybrid-states.js";
12
14
  let blake2b;
13
15
  before(async () => {
@@ -86,3 +88,93 @@ describe("Hybrid serialized states", () => {
86
88
  }
87
89
  });
88
90
  });
91
+ function hh(n) {
92
+ return Bytes.fill(HASH_SIZE, n).asOpaque();
93
+ }
94
+ const storageKey = asOpaqueType(BytesBlob.blobFromString("test-key"));
95
+ /** A state update writing a single large (non-embedded) value under `storageKey`. */
96
+ function storageUpdate(value) {
97
+ const item = StorageItem.create({ key: storageKey, value: BytesBlob.blobFromString(value) });
98
+ return {
99
+ storage: new Map([[tryAsServiceId(0), [UpdateStorage.set({ storage: item })]]]),
100
+ };
101
+ }
102
+ // > 32 bytes => stored in the values db (not embedded in the leaf).
103
+ const BIG_1 = "a".repeat(100);
104
+ const BIG_2 = "b".repeat(100);
105
+ /** `true` if every value referenced by the state can still be resolved. */
106
+ function canReadFully(state) {
107
+ if (state === null) {
108
+ return false;
109
+ }
110
+ try {
111
+ state.backend.intoStateEntries();
112
+ return true;
113
+ }
114
+ catch {
115
+ return false;
116
+ }
117
+ }
118
+ /** Value removals are queued, so poll for the expected outcome. */
119
+ async function eventually(check, what, timeoutMs = 5_000) {
120
+ const start = Date.now();
121
+ while (!check()) {
122
+ if (Date.now() - start > timeoutMs) {
123
+ assert.fail(`Timed out waiting for: ${what}`);
124
+ }
125
+ await new Promise((resolve) => setTimeout(resolve, 10));
126
+ }
127
+ }
128
+ describe("Hybrid serialized states value refcounting", () => {
129
+ const spec = tinyChainSpec;
130
+ let dbPath = "";
131
+ beforeEach(() => {
132
+ dbPath = createTempDir();
133
+ });
134
+ afterEach(() => {
135
+ fs.rmSync(dbPath, { recursive: true });
136
+ });
137
+ it("removes a replaced value from disk once the replacement finalizes", async () => {
138
+ const states = HybridSerializedStates.new({ spec, blake2b, dbPath });
139
+ try {
140
+ await states.insertInitialState(hh(0), StateEntries.serializeInMemory(spec, blake2b, testState()));
141
+ const s0 = states.getState(hh(0));
142
+ assert.ok(s0 !== null);
143
+ await states.updateAndSetState(hh(1), s0, storageUpdate(BIG_1));
144
+ // a handle to the post-1 state, surviving the pruning below
145
+ const stale1 = states.getState(hh(1));
146
+ const s1 = states.getState(hh(1));
147
+ assert.ok(s1 !== null);
148
+ await states.updateAndSetState(hh(2), s1, storageUpdate(BIG_2));
149
+ states.commitFinalized([hh(1)]);
150
+ assert.ok(canReadFully(stale1), "still referenced by the finalized tip");
151
+ states.commitFinalized([hh(2)]);
152
+ await eventually(() => !canReadFully(stale1), "replaced value removed from lmdb");
153
+ assert.ok(canReadFully(states.getState(hh(2))), "the new finalized tip stays fully readable");
154
+ }
155
+ finally {
156
+ await states.close();
157
+ }
158
+ });
159
+ it("collects values of a pruned dead fork and keeps the surviving chain intact", async () => {
160
+ const states = HybridSerializedStates.new({ spec, blake2b, dbPath });
161
+ try {
162
+ await states.insertInitialState(hh(0), StateEntries.serializeInMemory(spec, blake2b, testState()));
163
+ const s0 = states.getState(hh(0));
164
+ assert.ok(s0 !== null);
165
+ await states.updateAndSetState(hh(1), s0, storageUpdate(BIG_1));
166
+ // a dead fork on top of genesis, inserting a different value
167
+ const fork = states.getState(hh(0));
168
+ assert.ok(fork !== null);
169
+ await states.updateAndSetState(hh(0xaa), fork, storageUpdate(BIG_2));
170
+ const staleFork = states.getState(hh(0xaa));
171
+ states.markUnused(hh(0xaa));
172
+ assert.strictEqual(states.getState(hh(0xaa)), null);
173
+ await eventually(() => !canReadFully(staleFork), "fork-only value removed from lmdb");
174
+ assert.ok(canReadFully(states.getState(hh(1))), "surviving chain is unaffected");
175
+ }
176
+ finally {
177
+ await states.close();
178
+ }
179
+ });
180
+ });
@@ -68,6 +68,7 @@ export declare class LmdbStates implements StatesDb<SerializedState<LeafDb>>, In
68
68
  updateAndSetState(headerHash: HeaderHash, state: SerializedState<LeafDb>, update: Partial<State & ServicesUpdate>): Promise<Result<OK, StateUpdateError>>;
69
69
  getStateRoot(state: SerializedState<LeafDb>): Promise<StateRootHash>;
70
70
  getState(root: HeaderHash): SerializedState<LeafDb> | null;
71
+ commitFinalized(_headers: HeaderHash[]): void;
71
72
  markUnused(header: HeaderHash): void;
72
73
  diskSizeInBytes(): number | null;
73
74
  close(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"states.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-lmdb/states.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGlE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,EAAE,KAAK,QAAQ,EAAE,gBAAgB,EAAe,MAAM,qBAAqB,CAAC;AAC9G,OAAO,KAAK,EAAE,OAAO,EAAiB,MAAM,iBAAiB,CAAC;AAE9D,OAAO,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,gCAAgC,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAgD,MAAM,gCAAgC,CAAC;AAG/G,OAAO,EAAE,EAAE,EAAE,MAAM,EAAkB,MAAM,kBAAkB,CAAC;AAC9D,OAAO,KAAK,EAAE,QAAQ,EAAS,MAAM,WAAW,CAAC;AAGjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,qBAAa,UAAW,YAAW,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC,YAAY,CAAC;IAS5F,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAVvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAE/B,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ;IAI5D,OAAO;IASD,kBAAkB,CACtB,UAAU,EAAE,UAAU,EACtB,eAAe,EAAE,YAAY,GAC5B,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;YAQ1B,eAAe;IA0BvB,iBAAiB,CACrB,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,eAAe,CAAC,MAAM,CAAC,EAC9B,MAAM,EAAE,OAAO,CAAC,KAAK,GAAG,cAAc,CAAC,GACtC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAWlC,YAAY,CAAC,KAAK,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;IAI1E,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,GAAG,IAAI;IAsB1D,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAIpC,eAAe,IAAI,MAAM,GAAG,IAAI;IAI1B,KAAK;CAGZ"}
1
+ {"version":3,"file":"states.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-lmdb/states.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGlE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,EAAE,KAAK,QAAQ,EAAE,gBAAgB,EAAe,MAAM,qBAAqB,CAAC;AAC9G,OAAO,KAAK,EAAE,OAAO,EAAiB,MAAM,iBAAiB,CAAC;AAE9D,OAAO,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,gCAAgC,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAgD,MAAM,gCAAgC,CAAC;AAG/G,OAAO,EAAE,EAAE,EAAE,MAAM,EAAkB,MAAM,kBAAkB,CAAC;AAC9D,OAAO,KAAK,EAAE,QAAQ,EAAS,MAAM,WAAW,CAAC;AAGjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,qBAAa,UAAW,YAAW,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC,YAAY,CAAC;IAS5F,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAVvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAE/B,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ;IAI5D,OAAO;IASD,kBAAkB,CACtB,UAAU,EAAE,UAAU,EACtB,eAAe,EAAE,YAAY,GAC5B,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;YAQ1B,eAAe;IA0BvB,iBAAiB,CACrB,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,eAAe,CAAC,MAAM,CAAC,EAC9B,MAAM,EAAE,OAAO,CAAC,KAAK,GAAG,cAAc,CAAC,GACtC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAWlC,YAAY,CAAC,KAAK,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;IAI1E,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,GAAG,IAAI;IAsB1D,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,IAAI;IAM7C,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAIpC,eAAe,IAAI,MAAM,GAAG,IAAI;IAI1B,KAAK;CAGZ"}
@@ -128,6 +128,11 @@ export class LmdbStates {
128
128
  }
129
129
  return SerializedState.new(this.spec, this.blake2b, leafDbResult.ok);
130
130
  }
131
+ commitFinalized(_headers) {
132
+ // Values are never pruned here. This db survives restarts, so refcounting
133
+ // would need counts persisted (and crash-consistent) alongside the values.
134
+ // See the TODO above - not implemented until actually needed.
135
+ }
131
136
  markUnused(header) {
132
137
  this.states.removeSync(header.raw);
133
138
  }
@@ -1 +1 @@
1
- {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/export.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,wBAAsB,YAAY,CAAC,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,iBA2F9G"}
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/export.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,wBAAsB,YAAY,CAAC,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,iBAoG9G"}
@@ -3,9 +3,10 @@ import path from "node:path";
3
3
  import { Block as BlockCodec } from "#@typeberry/block";
4
4
  import { Bytes } from "#@typeberry/bytes";
5
5
  import { Encoder } from "#@typeberry/codec";
6
+ import { RegularStateBackend } from "#@typeberry/config-node";
6
7
  import { Blake2b, HASH_SIZE } from "#@typeberry/hash";
7
8
  import { Logger } from "#@typeberry/logger";
8
- import { LmdbWorkerConfig } from "#@typeberry/workers-api-node";
9
+ import { FjallWorkerConfig, LmdbWorkerConfig } from "#@typeberry/workers-api-node";
9
10
  import { getChainSpec, getDatabasePath } from "./common.js";
10
11
  export async function exportBlocks(jamNodeConfig, output, withRelPath) {
11
12
  const logger = Logger.new(import.meta.filename, "export");
@@ -29,14 +30,22 @@ export async function exportBlocks(jamNodeConfig, output, withRelPath) {
29
30
  const blake2b = await Blake2b.createHasher();
30
31
  const chainSpec = getChainSpec(jamNodeConfig.node.flavor);
31
32
  const { dbPath } = getDatabasePath(blake2b, jamNodeConfig.nodeName, jamNodeConfig.node.chainSpec.genesisHeader, withRelPath(jamNodeConfig.node.databaseBasePath));
32
- const config = LmdbWorkerConfig.new({
33
- nodeName: jamNodeConfig.nodeName,
34
- chainSpec,
35
- blake2b,
36
- dbPath,
37
- workerParams: undefined,
38
- });
39
- const rootDb = config.openDatabase();
33
+ const config = jamNodeConfig.node.stateBackend === RegularStateBackend.Fjall
34
+ ? FjallWorkerConfig.new({
35
+ nodeName: jamNodeConfig.nodeName,
36
+ chainSpec,
37
+ blake2b,
38
+ dbPath,
39
+ workerParams: undefined,
40
+ })
41
+ : LmdbWorkerConfig.new({
42
+ nodeName: jamNodeConfig.nodeName,
43
+ chainSpec,
44
+ blake2b,
45
+ dbPath,
46
+ workerParams: undefined,
47
+ });
48
+ const rootDb = await config.openDatabase();
40
49
  const blocks = rootDb.getBlocksDb();
41
50
  logger.info `📖 Gathering blocks...`;
42
51
  const hashes = [];
@@ -1 +1 @@
1
- {"version":3,"file":"main-fuzz.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/main-fuzz.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,WAAW,EAAmB,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAIrD,OAAO,EAAE,KAAK,MAAM,EAAoC,MAAM,kBAAkB,CAAC;AAGjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,WAAW,CAAC;IACrB,aAAa,EAAE,SAAS,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uBAAuB,EAAE,OAAO,CAAC;CAClC,CAAC;AAoBF;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CASpF;AAED,iFAAiF;AACjF,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5D;AAED,wBAAgB,cAAc;;;;EAM7B;AAED,wBAAsB,QAAQ,CAAC,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAsKrH"}
1
+ {"version":3,"file":"main-fuzz.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/main-fuzz.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,WAAW,EAAmB,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAIrD,OAAO,EAAE,KAAK,MAAM,EAAoC,MAAM,kBAAkB,CAAC;AAGjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,WAAW,CAAC;IACrB,aAAa,EAAE,SAAS,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uBAAuB,EAAE,OAAO,CAAC;CAClC,CAAC;AAoBF;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CASpF;AAED,iFAAiF;AACjF,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5D;AAED,wBAAgB,cAAc;;;;EAM7B;AAED,wBAAsB,QAAQ,CAAC,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAgLrH"}
@@ -78,6 +78,7 @@ export async function mainFuzz(fuzzConfig, withRelPath) {
78
78
  // Set when close() starts. Guards resetState so a fuzz command arriving
79
79
  // mid-shutdown can't build a fresh node that close() then orphans.
80
80
  let isClosing = false;
81
+ let activeReset = null;
81
82
  const chainSpec = getChainSpec(config.node.flavor);
82
83
  const closeFuzzTarget = startFuzzTarget(fuzzConfig.version, fuzzConfig.socket, {
83
84
  ...getFuzzDetails(),
@@ -101,84 +102,94 @@ export async function mainFuzz(fuzzConfig, withRelPath) {
101
102
  }
102
103
  return runningNode.getStateEntries(hash);
103
104
  },
104
- resetState: async (header, state, ancestry) => {
105
- if (isClosing) {
106
- return Bytes.zero(HASH_SIZE).asOpaque();
107
- }
108
- if (runningNode !== null) {
109
- const finish = runningNode.close();
110
- runningNode = null;
111
- await finish;
112
- }
113
- const buildNode = (databaseBasePath) => {
114
- const isPersistent = databaseBasePath !== undefined;
115
- return mainImporter({
116
- ...config,
117
- node: {
118
- ...config.node,
119
- databaseBasePath,
120
- chainSpec: {
121
- ...config.node.chainSpec,
122
- genesisHeader: Encoder.encodeObject(Header.Codec, header, chainSpec),
123
- genesisState: new Map(state),
105
+ resetState: (header, state, ancestry) => {
106
+ const reset = (async () => {
107
+ if (isClosing) {
108
+ return Bytes.zero(HASH_SIZE).asOpaque();
109
+ }
110
+ if (runningNode !== null) {
111
+ const finish = runningNode.close();
112
+ runningNode = null;
113
+ await finish;
114
+ }
115
+ const buildNode = (databaseBasePath) => {
116
+ const isPersistent = databaseBasePath !== undefined;
117
+ return mainImporter({
118
+ ...config,
119
+ node: {
120
+ ...config.node,
121
+ databaseBasePath,
122
+ chainSpec: {
123
+ ...config.node.chainSpec,
124
+ genesisHeader: Encoder.encodeObject(Header.Codec, header, chainSpec),
125
+ genesisState: new Map(state),
126
+ },
124
127
  },
125
- },
126
- ancestry,
127
- network: null,
128
- }, withRelPath, {
129
- initGenesisFromAncestry: fuzzConfig.initGenesisFromAncestry,
130
- // Hybrid keeps leaf sets in RAM, so they must be windowed exactly
131
- // like the in-memory backend; only the large values live on disk.
132
- dummyFinalityDepth: 20,
133
- pruneBlocks: true,
134
- // The on-disk fuzz db is throwaway (we wipe it), so open it ephemeral and
135
- // skip the fsync, we do not need durability here. On full spec ephemeral
136
- // also turns on compression further down, so the big values do not grow the
137
- // db too much. Tiny stays uncompressed, its db is small and speed matters more.
138
- ephemeral: isPersistent,
139
- stateBackend: isPersistent ? hybridStateBackend : "lmdb",
140
- // Reuse the session keyspace (fjall-hybrid only, other backends
141
- // ignore it). Nothing to pass for the in-memory fallback.
142
- sharedFjallSession: isPersistent ? (fjallSession ?? undefined) : undefined,
143
- });
144
- };
145
- if (fuzzDbBase !== undefined) {
146
- try {
147
- if (hybridStateBackend === FUZZ_DB_FJALL) {
148
- // fjall-hybrid: open the values keyspace once and reuse it on every
149
- // reset. The values partition is content-addressed and immutable, so
150
- // it is fine that values pile up across resets, the unreferenced ones
151
- // just sit there. `initializeDatabase` decides whether the db is
152
- // already initialized from the in-memory blocks, which we rebuild on
153
- // every reset, not from the values store, so reusing it does not
154
- // resume the previous run.
155
- if (fjallSession === null) {
156
- // Start from a clean slate once, then keep the keyspace open.
128
+ ancestry,
129
+ network: null,
130
+ }, withRelPath, {
131
+ initGenesisFromAncestry: fuzzConfig.initGenesisFromAncestry,
132
+ // Hybrid keeps leaf sets in RAM, so they must be windowed exactly
133
+ // like the in-memory backend; only the large values live on disk.
134
+ dummyFinalityDepth: 20,
135
+ pruneBlocks: true,
136
+ // The on-disk fuzz db is throwaway (we wipe it), so open it ephemeral and
137
+ // skip the fsync, we do not need durability here. On full spec ephemeral
138
+ // also turns on compression further down, so the big values do not grow the
139
+ // db too much. Tiny stays uncompressed, its db is small and speed matters more.
140
+ ephemeral: isPersistent,
141
+ stateBackend: isPersistent ? hybridStateBackend : "lmdb",
142
+ // Reuse the session keyspace (fjall-hybrid only, other backends
143
+ // ignore it). Nothing to pass for the in-memory fallback.
144
+ sharedFjallSession: isPersistent ? (fjallSession ?? undefined) : undefined,
145
+ });
146
+ };
147
+ if (fuzzDbBase !== undefined) {
148
+ try {
149
+ if (hybridStateBackend === FUZZ_DB_FJALL) {
150
+ // fjall-hybrid: open the values keyspace once and reuse it on every
151
+ // reset. The values partition is content-addressed and immutable, so
152
+ // it is fine that values pile up across resets, the unreferenced ones
153
+ // just sit there. `initializeDatabase` decides whether the db is
154
+ // already initialized from the in-memory blocks, which we rebuild on
155
+ // every reset, not from the values store, so reusing it does not
156
+ // resume the previous run.
157
+ if (fjallSession === null) {
158
+ // Start from a clean slate once, then keep the keyspace open.
159
+ await wipeFuzzDb(fuzzDbBase);
160
+ fjallSession = await FjallValuesSession.open(`${withRelPath(fuzzDbBase)}/${FUZZ_FJALL_VALUES_SUBDIR}`, {
161
+ ephemeral: true,
162
+ cacheSizeBytes: FUZZ_FJALL_CACHE_BYTES,
163
+ });
164
+ logger.info `🗄️ Opened reusable fjall values session at ${withRelPath(fuzzDbBase)}/${FUZZ_FJALL_VALUES_SUBDIR}`;
165
+ }
166
+ }
167
+ else {
168
+ // lmdb-hybrid: keep the old behaviour, wipe and reopen on every
169
+ // reset. A fresh db each reset makes `initializeDatabase` set up
170
+ // genesis again instead of resuming the previous run.
157
171
  await wipeFuzzDb(fuzzDbBase);
158
- fjallSession = await FjallValuesSession.open(`${withRelPath(fuzzDbBase)}/${FUZZ_FJALL_VALUES_SUBDIR}`, {
159
- ephemeral: true,
160
- cacheSizeBytes: FUZZ_FJALL_CACHE_BYTES,
161
- });
162
- logger.info `🗄️ Opened reusable fjall values session at ${withRelPath(fuzzDbBase)}/${FUZZ_FJALL_VALUES_SUBDIR}`;
163
172
  }
173
+ runningNode = await buildNode(fuzzDbBase);
174
+ return await runningNode.getBestStateRootHash();
164
175
  }
165
- else {
166
- // lmdb-hybrid: keep the old behaviour, wipe and reopen on every
167
- // reset. A fresh db each reset makes `initializeDatabase` set up
168
- // genesis again instead of resuming the previous run.
169
- await wipeFuzzDb(fuzzDbBase);
176
+ catch (e) {
177
+ // A partially-opened db may leak on failure; acceptable for this degraded fallback (proper cleanup belongs in mainImporter).
178
+ logger.warn `Failed to open persistent fuzz db at ${fuzzDbBase}, falling back to in-memory: ${e}`;
179
+ runningNode = null;
170
180
  }
171
- runningNode = await buildNode(fuzzDbBase);
172
- return await runningNode.getBestStateRootHash();
173
181
  }
174
- catch (e) {
175
- // A partially-opened db may leak on failure; acceptable for this degraded fallback (proper cleanup belongs in mainImporter).
176
- logger.warn `Failed to open persistent fuzz db at ${fuzzDbBase}, falling back to in-memory: ${e}`;
177
- runningNode = null;
182
+ runningNode = await buildNode(undefined);
183
+ return await runningNode.getBestStateRootHash();
184
+ })();
185
+ activeReset = reset;
186
+ const clearActiveReset = () => {
187
+ if (activeReset === reset) {
188
+ activeReset = null;
178
189
  }
179
- }
180
- runningNode = await buildNode(undefined);
181
- return await runningNode.getBestStateRootHash();
190
+ };
191
+ reset.then(clearActiveReset, clearActiveReset);
192
+ return reset;
182
193
  },
183
194
  });
184
195
  return {
@@ -189,6 +200,9 @@ export async function mainFuzz(fuzzConfig, withRelPath) {
189
200
  // Drain the active session (flush + close DB). Swallow errors so a
190
201
  // failing close still lets the process exit 0; the db is wiped next.
191
202
  // The node references the shared fjall session, so it must close first.
203
+ if (activeReset !== null) {
204
+ await activeReset.catch((e) => logger.error `Error waiting for fuzz reset: ${e}`);
205
+ }
192
206
  if (runningNode !== null) {
193
207
  const node = runningNode;
194
208
  runningNode = null;
@@ -1,7 +1,7 @@
1
1
  import { type FjallValuesSession } from "#@typeberry/workers-api-node";
2
2
  import type { JamConfig } from "./jam-config.js";
3
3
  import type { NodeApi } from "./main.js";
4
- export type StateBackend = "lmdb" | "lmdb-hybrid" | "fjall-hybrid";
4
+ export type StateBackend = "lmdb" | "fjall" | "lmdb-hybrid" | "fjall-hybrid";
5
5
  export type ImporterOptions = {
6
6
  initGenesisFromAncestry?: boolean;
7
7
  dummyFinalityDepth?: number;
@@ -9,7 +9,9 @@ export type ImporterOptions = {
9
9
  /** Open the database without fsync/compression. Only safe for throwaway dbs (e.g. fuzzing). */
10
10
  ephemeral?: boolean;
11
11
  /**
12
- * Persistent backend used when `databaseBasePath` is set. Defaults to full LMDB.
12
+ * Persistent backend used when `databaseBasePath` is set. Defaults to config's backend (fjall unless overridden).
13
+ *
14
+ * lmdb and lmdb-hybrid are deprecated and retained as explicit fallbacks.
13
15
  */
14
16
  stateBackend?: StateBackend;
15
17
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"main-importer.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/main-importer.ts"],"names":[],"mappings":"AASA,OAAO,EACL,KAAK,kBAAkB,EAIxB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIzC,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,aAAa,GAAG,cAAc,CAAC;AAEnE,MAAM,MAAM,eAAe,GAAG;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+FAA+F;IAC/F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC,CAAC;AAEF,wBAAsB,YAAY,CAChC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAClC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,OAAO,CAAC,CAsGlB"}
1
+ {"version":3,"file":"main-importer.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/main-importer.ts"],"names":[],"mappings":"AASA,OAAO,EACL,KAAK,kBAAkB,EAKxB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIzC,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,aAAa,GAAG,cAAc,CAAC;AAE7E,MAAM,MAAM,eAAe,GAAG;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+FAA+F;IAC/F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC,CAAC;AAEF,wBAAsB,YAAY,CAChC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAClC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,OAAO,CAAC,CAqHlB"}
@@ -1,12 +1,12 @@
1
1
  import { Bytes } from "#@typeberry/bytes";
2
2
  import { PvmBackend } from "#@typeberry/config";
3
- import { KnownChainSpec } from "#@typeberry/config-node";
3
+ import { KnownChainSpec, RegularStateBackend } from "#@typeberry/config-node";
4
4
  import { bandersnatch, initWasm } from "#@typeberry/crypto";
5
5
  import { Blake2b, HASH_SIZE } from "#@typeberry/hash";
6
6
  import { createImporter, ImporterConfig } from "#@typeberry/importer";
7
7
  import { tryAsU16 } from "#@typeberry/numbers";
8
8
  import { CURRENT_SUITE, CURRENT_VERSION, Result, resultToString, version } from "#@typeberry/utils";
9
- import { HybridWorkerConfig, InMemWorkerConfig, LmdbWorkerConfig, } from "#@typeberry/workers-api-node";
9
+ import { FjallWorkerConfig, HybridWorkerConfig, InMemWorkerConfig, LmdbWorkerConfig, } from "#@typeberry/workers-api-node";
10
10
  import { getChainSpec, getDatabasePath, initializeDatabase, logger } from "./common.js";
11
11
  const zeroHash = Bytes.zero(HASH_SIZE).asOpaque();
12
12
  export async function mainImporter(config, withRelPath, options = {}) {
@@ -18,8 +18,13 @@ export async function mainImporter(config, withRelPath, options = {}) {
18
18
  logger.info `🐇 Bandersnatch ${bandesnatchNative.isOk ? "native 🚀" : `using wasm: ${bandesnatchNative.error}`}`;
19
19
  // Single source of truth for the states db backend: drives both the log line
20
20
  // below and the worker config picked further down.
21
- const dbBackend = config.node.databaseBasePath === undefined ? "in-memory" : (options.stateBackend ?? "lmdb");
21
+ const dbBackend = config.node.databaseBasePath === undefined
22
+ ? "in-memory"
23
+ : (options.stateBackend ?? config.node.stateBackend ?? RegularStateBackend.Fjall);
22
24
  logger.info `🗄️ States DB: ${dbBackend}.`;
25
+ if (dbBackend === "lmdb" || dbBackend === "lmdb-hybrid") {
26
+ logger.warn `🗄️ The ${dbBackend} state backend is deprecated. Use fjall unless you need a temporary fallback.`;
27
+ }
23
28
  const chainSpec = getChainSpec(config.node.flavor);
24
29
  const blake2b = await Blake2b.createHasher();
25
30
  const nodeName = config.nodeName;
@@ -51,17 +56,26 @@ export async function mainImporter(config, withRelPath, options = {}) {
51
56
  backend: dbBackend === "lmdb-hybrid" ? "lmdb" : "fjall",
52
57
  sharedFjallSession: options.sharedFjallSession,
53
58
  })
54
- : LmdbWorkerConfig.new({
55
- nodeName,
56
- chainSpec,
57
- blake2b,
58
- dbPath,
59
- workerParams,
60
- ephemeral,
61
- });
59
+ : dbBackend === "fjall"
60
+ ? FjallWorkerConfig.new({
61
+ nodeName,
62
+ chainSpec,
63
+ blake2b,
64
+ dbPath,
65
+ workerParams,
66
+ ephemeral,
67
+ })
68
+ : LmdbWorkerConfig.new({
69
+ nodeName,
70
+ chainSpec,
71
+ blake2b,
72
+ dbPath,
73
+ workerParams,
74
+ ephemeral,
75
+ });
62
76
  // Initialize the database with genesis state and block if there isn't one.
63
77
  logger.info `🛢️ Opening database at ${dbPath}`;
64
- const rootDb = workerConfig.openDatabase({ readonly: false });
78
+ const rootDb = await workerConfig.openDatabase({ readonly: false });
65
79
  await initializeDatabase(chainSpec, blake2b, genesisHeaderHash, rootDb, config.node.chainSpec, config.ancestry, {
66
80
  initGenesisFromAncestry: options.initGenesisFromAncestry,
67
81
  });
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/main.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAc,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEzF,OAAO,EAAE,KAAK,SAAS,EAAc,MAAM,mBAAmB,CAAC;AAe/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAkC,MAAM,EAAW,MAAM,kBAAkB,CAAC;AAKnF,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,iBAAiB,CAAC;AAWhE,MAAM,MAAM,OAAO,GAAG;IACpB,SAAS,EAAE,SAAS,CAAC;IACrB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAChE,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,oBAAoB,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IAC/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,wBAAsB,IAAI,CACxB,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAClC,SAAS,EAAE,SAAS,GAAG,IAAI,GAC1B,OAAO,CAAC,OAAO,CAAC,CA2KlB"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/main.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAc,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEzF,OAAO,EAAE,KAAK,SAAS,EAAc,MAAM,mBAAmB,CAAC;AAgB/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAkC,MAAM,EAAW,MAAM,kBAAkB,CAAC;AAYnF,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,iBAAiB,CAAC;AAWhE,MAAM,MAAM,OAAO,GAAG;IACpB,SAAS,EAAE,SAAS,CAAC;IACrB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAChE,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,oBAAoB,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IAC/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,wBAAsB,IAAI,CACxB,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAClC,SAAS,EAAE,SAAS,GAAG,IAAI,GAC1B,OAAO,CAAC,OAAO,CAAC,CA2LlB"}
@@ -1,6 +1,7 @@
1
1
  import { isMainThread } from "node:worker_threads";
2
2
  import { AUTHORSHIP_NETWORK_PORT } from "#@typeberry/comms-authorship-network";
3
3
  import { PvmBackend } from "#@typeberry/config";
4
+ import { RegularStateBackend } from "#@typeberry/config-node";
4
5
  import { initWasm } from "#@typeberry/crypto";
5
6
  import { deriveBandersnatchSecretKey, deriveEd25519SecretKey, trivialSeed, } from "#@typeberry/crypto/key-derivation.js";
6
7
  import { Blake2b } from "#@typeberry/hash";
@@ -10,7 +11,7 @@ import { Listener } from "#@typeberry/listener";
10
11
  import { tryAsU16, tryAsU32 } from "#@typeberry/numbers";
11
12
  import { CURRENT_SUITE, CURRENT_VERSION, Result, version } from "#@typeberry/utils";
12
13
  import { DirectPort, DirectWorkerConfig } from "#@typeberry/workers-api";
13
- import { InMemWorkerConfig, LmdbWorkerConfig, logHostEnvironment, ThreadPort } from "#@typeberry/workers-api-node";
14
+ import { FjallWorkerConfig, InMemWorkerConfig, LmdbWorkerConfig, logHostEnvironment, ThreadPort, } from "#@typeberry/workers-api-node";
14
15
  import { getChainSpec, getDatabasePath, initializeDatabase, logger } from "./common.js";
15
16
  import { initializeExtensions } from "./extensions.js";
16
17
  import * as metrics from "./metrics.js";
@@ -29,9 +30,12 @@ export async function main(config, withRelPath, telemetry) {
29
30
  const blake2b = await Blake2b.createHasher();
30
31
  const nodeName = config.nodeName;
31
32
  const isInMemory = config.node.databaseBasePath === undefined;
32
- logger.info `🗄️ States DB: ${isInMemory ? "in-memory" : "lmdb"}.`;
33
+ logger.info `🗄️ States DB: ${isInMemory ? "in-memory" : config.node.stateBackend}.`;
34
+ if (!isInMemory && config.node.stateBackend === RegularStateBackend.Lmdb) {
35
+ logger.warn `🗄️ The lmdb state backend is deprecated. Use state_backend="fjall" unless you need a temporary fallback.`;
36
+ }
33
37
  const { dbPath, genesisHeaderHash } = getDatabasePath(blake2b, nodeName, config.node.chainSpec.genesisHeader, withRelPath(config.node.databaseBasePath ?? "<in-memory>"));
34
- const baseConfig = { nodeName, chainSpec, blake2b, dbPath };
38
+ const baseConfig = { nodeName, chainSpec, blake2b, dbPath, stateBackend: config.node.stateBackend };
35
39
  const importerParams = {
36
40
  ...baseConfig,
37
41
  workerParams: ImporterConfig.create({
@@ -42,16 +46,29 @@ export async function main(config, withRelPath, telemetry) {
42
46
  };
43
47
  const importerConfig = isInMemory
44
48
  ? { isInMemory, config: InMemWorkerConfig.new(importerParams) }
45
- : { isInMemory, config: LmdbWorkerConfig.new(importerParams) };
49
+ : { isInMemory, config: createPersistentWorkerConfig(importerParams) };
46
50
  // Initialize the database with genesis state and block if there isn't one.
47
51
  logger.info `🛢️ Opening database at ${dbPath}`;
48
- const rootDb = importerConfig.config.openDatabase({ readonly: false });
49
- await initializeDatabase(chainSpec, blake2b, genesisHeaderHash, rootDb, config.node.chainSpec, config.ancestry);
50
- // NOTE [ToDr] even though, we should be closing the database here,
51
- // it seems that opening it in the main thread for writing, and later
52
- // in the importer thread, causes issues. Everything works fine though,
53
- // if we DO NOT close the database (I guess it's process-shared?)
54
- // await rootDb.close();
52
+ const rootDb = await importerConfig.config.openDatabase({ readonly: false });
53
+ try {
54
+ await initializeDatabase(chainSpec, blake2b, genesisHeaderHash, rootDb, config.node.chainSpec, config.ancestry);
55
+ }
56
+ catch (e) {
57
+ try {
58
+ await rootDb.close();
59
+ }
60
+ catch (closeError) {
61
+ logger.warn `Failed to close database after initialization error: ${closeError}`;
62
+ }
63
+ throw e;
64
+ }
65
+ // fjall-js shares the engine explicitly and requires every handle to close.
66
+ // Keep lmdb's historical main-thread handle open until shutdown.
67
+ let mainRootDb = rootDb;
68
+ if (!importerConfig.isInMemory && config.node.stateBackend === RegularStateBackend.Fjall) {
69
+ await rootDb.close();
70
+ mainRootDb = null;
71
+ }
55
72
  // Start block importer
56
73
  let importer;
57
74
  let closeImporter;
@@ -138,8 +155,10 @@ export async function main(config, withRelPath, telemetry) {
138
155
  await closeImporter();
139
156
  logger.log `[main] ☠️ Closing the extensions`;
140
157
  closeExtensions();
141
- logger.log `[main] 🛢️ Closing the database`;
142
- await rootDb.close();
158
+ if (mainRootDb !== null) {
159
+ logger.log `[main] 🛢️ Closing the database`;
160
+ await mainRootDb.close();
161
+ }
143
162
  logger.log `[main] 📳 Closing telemetry`;
144
163
  await telemetry?.close();
145
164
  logger.info `[main] ✅ Done.`;
@@ -167,7 +186,7 @@ const initAuthorship = async (importer, isAuthoring, isFastForward, params, base
167
186
  statesDb: params.rootDb.getStatesDb(),
168
187
  workerParams,
169
188
  }), params.networkingPort)
170
- : await spawnBlockGeneratorWorker(LmdbWorkerConfig.new({
189
+ : await spawnBlockGeneratorWorker(createPersistentWorkerConfig({
171
190
  ...baseConfig,
172
191
  workerParams,
173
192
  ports: new Map([[AUTHORSHIP_NETWORK_PORT, params.networkingPort]]),
@@ -205,7 +224,7 @@ const initNetwork = async (importer, params, baseConfig, genesisHeaderHash, netw
205
224
  statesDb: params.rootDb.getStatesDb(),
206
225
  workerParams: networkingConfig,
207
226
  }), params.authorshipPort)
208
- : await spawnNetworkWorker(LmdbWorkerConfig.new({
227
+ : await spawnNetworkWorker(createPersistentWorkerConfig({
209
228
  ...baseConfig,
210
229
  workerParams: networkingConfig,
211
230
  ports: new Map([[AUTHORSHIP_NETWORK_PORT, params.authorshipPort]]),
@@ -222,3 +241,11 @@ const initNetwork = async (importer, params, baseConfig, genesisHeaderHash, netw
222
241
  });
223
242
  return { closeNetwork: finish, networkApi: network, networkWorker: worker };
224
243
  };
244
+ function createPersistentWorkerConfig({ stateBackend, ...params }) {
245
+ switch (stateBackend) {
246
+ case RegularStateBackend.Fjall:
247
+ return FjallWorkerConfig.new(params);
248
+ case RegularStateBackend.Lmdb:
249
+ return LmdbWorkerConfig.new(params);
250
+ }
251
+ }