@typeberry/lib 0.9.0-3f2c45b → 0.9.0-6850f84

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 (56) 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/index.d.ts +1 -0
  11. package/packages/core/utils/index.d.ts.map +1 -1
  12. package/packages/core/utils/index.js +1 -0
  13. package/packages/core/utils/shutdown.d.ts +18 -0
  14. package/packages/core/utils/shutdown.d.ts.map +1 -0
  15. package/packages/core/utils/shutdown.js +3 -0
  16. package/packages/core/utils/shutdown.node.d.ts +5 -0
  17. package/packages/core/utils/shutdown.node.d.ts.map +1 -0
  18. package/packages/core/utils/shutdown.node.js +76 -0
  19. package/packages/core/utils/shutdown.test.d.ts +2 -0
  20. package/packages/core/utils/shutdown.test.d.ts.map +1 -0
  21. package/packages/core/utils/shutdown.test.js +164 -0
  22. package/packages/extensions/ipc/server.d.ts.map +1 -1
  23. package/packages/extensions/ipc/server.js +7 -0
  24. package/packages/jam/database/index.d.ts +1 -0
  25. package/packages/jam/database/index.d.ts.map +1 -1
  26. package/packages/jam/database/index.js +1 -0
  27. package/packages/jam/database/leaf-db-update.d.ts +1 -0
  28. package/packages/jam/database/leaf-db-update.d.ts.map +1 -1
  29. package/packages/jam/database/leaf-db-update.js +22 -5
  30. package/packages/jam/database/leaf-db-update.test.d.ts +2 -0
  31. package/packages/jam/database/leaf-db-update.test.d.ts.map +1 -0
  32. package/packages/jam/database/leaf-db-update.test.js +81 -0
  33. package/packages/jam/database/serialized-states-db.d.ts +1 -0
  34. package/packages/jam/database/serialized-states-db.d.ts.map +1 -1
  35. package/packages/jam/database/serialized-states-db.js +4 -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/hybrid-states.d.ts +14 -0
  46. package/packages/jam/database-fjall/hybrid-states.d.ts.map +1 -1
  47. package/packages/jam/database-fjall/hybrid-states.js +44 -4
  48. package/packages/jam/database-lmdb/hybrid-states.d.ts +12 -0
  49. package/packages/jam/database-lmdb/hybrid-states.d.ts.map +1 -1
  50. package/packages/jam/database-lmdb/hybrid-states.js +45 -4
  51. package/packages/jam/database-lmdb/states.d.ts +1 -0
  52. package/packages/jam/database-lmdb/states.d.ts.map +1 -1
  53. package/packages/jam/database-lmdb/states.js +5 -0
  54. package/packages/jam/node/main-fuzz.d.ts +4 -1
  55. package/packages/jam/node/main-fuzz.d.ts.map +1 -1
  56. package/packages/jam/node/main-fuzz.js +111 -82
@@ -1,5 +1,5 @@
1
1
  import { HashDictionary, SortedSet } from "#@typeberry/collections";
2
- import { LeafDb, StateUpdateError, updateLeafs, } from "#@typeberry/database";
2
+ import { InMemoryValueRefsStore, LeafDb, StateUpdateError, updateLeafs, ValueRefs, } from "#@typeberry/database";
3
3
  import { Logger } from "#@typeberry/logger";
4
4
  import { SerializedState, StateEntryUpdateAction, serializeStateUpdate, } from "#@typeberry/state-merkleization";
5
5
  import { leafComparator } from "#@typeberry/trie";
@@ -53,6 +53,14 @@ export class FjallValuesSession {
53
53
  *
54
54
  * Construction is async, and value writes are flushed explicitly, because fjall
55
55
  * has no transaction primitive.
56
+ *
57
+ * Values that no longer belong to any surviving state are removed from fjall,
58
+ * decided by in-memory refcounting (`ValueRefs`) driven by the importer's
59
+ * finality signal. Counts are not persisted: this db cannot resume from disk
60
+ * anyway (the leaf sets live in memory), so values left over by a previous run
61
+ * are never collected. An instance backed by a shared session (fuzz reset
62
+ * reuse) only ever prunes values it inserted itself, since its refcounts start
63
+ * empty - values left behind by earlier resets stay untouched.
56
64
  */
57
65
  export class HybridSerializedStates {
58
66
  spec;
@@ -80,6 +88,10 @@ export class HybridSerializedStates {
80
88
  valuesDb;
81
89
  /** Shared content-addressed values partition (owned by `session`). */
82
90
  values;
91
+ refsStore = new InMemoryValueRefsStore();
92
+ refs = new ValueRefs(this.refsStore);
93
+ // Queue of not-yet-committed value removals, awaited on close.
94
+ pendingCleanup = Promise.resolve();
83
95
  constructor(spec, blake2b, session,
84
96
  /** Whether `close()` should close the underlying session. */
85
97
  ownsSession) {
@@ -97,13 +109,14 @@ export class HybridSerializedStates {
97
109
  return res;
98
110
  }
99
111
  this.inMemStates.set(headerHash, leafs);
112
+ this.applyRefs(this.refs.onInitial(values.map((v) => v[0])));
100
113
  return Result.ok(OK);
101
114
  }
102
115
  async updateAndSetState(header, state, update) {
103
116
  const updatedValues = serializeStateUpdate(this.spec, this.blake2b, update);
104
117
  // Clone the leaf set before mutating: the previous state keeps using its own.
105
118
  const newLeafs = SortedSet.fromSortedArray(leafComparator, state.backend.leafs.array);
106
- const { values, leafs } = updateLeafs(newLeafs, this.blake2b, updatedValues);
119
+ const { values, removed, leafs } = updateLeafs(newLeafs, this.blake2b, updatedValues);
107
120
  const res = await this.writeValues(values);
108
121
  if (res.isError) {
109
122
  // Leave the caller's state untouched: its new leaves would reference
@@ -114,6 +127,7 @@ export class HybridSerializedStates {
114
127
  // values are durably written.
115
128
  state.updateBackend(LeafDb.fromLeaves(leafs, this.valuesDb));
116
129
  this.inMemStates.set(header, leafs);
130
+ this.applyRefs(this.refs.onImport(header, { inserted: values.map((v) => v[0]), removed }));
117
131
  return Result.ok(OK);
118
132
  }
119
133
  async getStateRoot(state) {
@@ -127,15 +141,36 @@ export class HybridSerializedStates {
127
141
  const leafDb = LeafDb.fromLeaves(leafs, this.valuesDb);
128
142
  return SerializedState.new(this.spec, this.blake2b, leafDb);
129
143
  }
144
+ commitFinalized(headers) {
145
+ this.applyRefs(this.refs.commitFinalized(headers));
146
+ }
130
147
  markUnused(header) {
131
- // We only remove the state from memory - values are not pruned at all,
132
- // but since they are stored on disk we should be safe.
148
+ // Release the speculative references first (a no-op for finalized states,
149
+ // whose deltas were already consumed by `commitFinalized`).
150
+ this.applyRefs(this.refs.releaseUnfinalized(header));
133
151
  this.inMemStates.delete(header);
134
152
  }
135
153
  diskSizeInBytes() {
136
154
  return this.session.sizeInBytes();
137
155
  }
156
+ /** Apply a refcounting update and remove values that lost their last reference. */
157
+ applyRefs(update) {
158
+ this.refsStore.apply(update);
159
+ if (update.removeValues.length === 0) {
160
+ return;
161
+ }
162
+ // Queued, not awaited: a failed removal only leaks a value. No explicit
163
+ // persist - removals are flushed together with the next value write.
164
+ this.pendingCleanup = this.pendingCleanup
165
+ .then(() => Promise.all(update.removeValues.map((v) => this.values.remove(v.raw))))
166
+ .catch((e) => {
167
+ logger.error `Failed to remove unreferenced values: ${e}`;
168
+ });
169
+ }
138
170
  async close() {
171
+ // Finish any queued value removals before tearing down (or releasing) the
172
+ // session - they operate on the shared `values` partition.
173
+ await this.pendingCleanup;
139
174
  // Instances backed by a shared session (fuzz reset reuse) keep the keyspace
140
175
  // open for the next reset. The session owner closes it once.
141
176
  if (this.ownsSession) {
@@ -148,6 +183,11 @@ export class HybridSerializedStates {
148
183
  return Result.ok(OK);
149
184
  }
150
185
  try {
186
+ // Flush queued removals first: a pending cleanup might be about to delete
187
+ // a content hash we are re-inserting now. Writing behind it keeps the
188
+ // re-inserted value on disk (removals are only queued at refcount 0, so
189
+ // none can be queued for a value still referenced).
190
+ await this.pendingCleanup;
151
191
  const entries = values.map(([hash, val]) => ({ key: hash.raw, value: val.raw }));
152
192
  await this.values.insertBatch(entries);
153
193
  await this.session.persist();
@@ -12,6 +12,12 @@ import { OK, Result } from "#@typeberry/utils";
12
12
  * Reads go straight to lmdb, which keeps its own page cache.
13
13
  * NOTE: this DB is designed for long fuzzing and to be used with pruning to
14
14
  * keep the heap usage bounded.
15
+ *
16
+ * Values that no longer belong to any surviving state are removed from lmdb,
17
+ * decided by in-memory refcounting (`ValueRefs`) driven by the importer's
18
+ * finality signal. Counts are not persisted: this db cannot resume from disk
19
+ * anyway (the leaf sets live in memory), so values left over by a previous
20
+ * run are never collected.
15
21
  */
16
22
  export declare class HybridSerializedStates implements StatesDb<SerializedState<LeafDb>>, InitStatesDb<StateEntries> {
17
23
  private readonly spec;
@@ -20,6 +26,9 @@ export declare class HybridSerializedStates implements StatesDb<SerializedState<
20
26
  private readonly inMemStates;
21
27
  private readonly lmdbValues;
22
28
  private readonly valuesDb;
29
+ private readonly refsStore;
30
+ private readonly refs;
31
+ private pendingCleanup;
23
32
  static new({ spec, blake2b, dbPath, readOnly, ephemeral, compression, }: {
24
33
  spec: ChainSpec;
25
34
  blake2b: Blake2b;
@@ -33,7 +42,10 @@ export declare class HybridSerializedStates implements StatesDb<SerializedState<
33
42
  updateAndSetState(header: HeaderHash, state: SerializedState<LeafDb>, update: Partial<State & ServicesUpdate>): Promise<Result<OK, StateUpdateError>>;
34
43
  getStateRoot(state: SerializedState<LeafDb>): Promise<StateRootHash>;
35
44
  getState(header: HeaderHash): SerializedState<LeafDb> | null;
45
+ commitFinalized(headers: HeaderHash[]): void;
36
46
  markUnused(header: HeaderHash): void;
47
+ /** Apply a refcounting update and remove values that lost their last reference. */
48
+ private applyRefs;
37
49
  diskSizeInBytes(): number | null;
38
50
  close(): Promise<void>;
39
51
  /** Write new large values to LMDB in one transaction. */
@@ -1 +1 @@
1
- {"version":3,"file":"hybrid-states.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-lmdb/hybrid-states.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGlE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EACL,KAAK,YAAY,EACjB,MAAM,EACN,KAAK,QAAQ,EACb,gBAAgB,EAGjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EACL,eAAe,EACf,KAAK,YAAY,EAGlB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG9C;;;;;;;GAOG;AACH,qBAAa,sBAAuB,YAAW,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC,YAAY,CAAC;IA0BxG,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IA3BvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyE;IACrG,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IAEnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IAEpC,MAAM,CAAC,GAAG,CAAC,EACT,IAAI,EACJ,OAAO,EACP,MAAM,EACN,QAAQ,EACR,SAAS,EACT,WAAW,GACZ,EAAE;QACD,IAAI,EAAE,SAAS,CAAC;QAChB,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB;IAKD,OAAO;IASD,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAcxG,iBAAiB,CACrB,MAAM,EAAE,UAAU,EAClB,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;IAkBlC,YAAY,CAAC,KAAK,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;IAI1E,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,GAAG,IAAI;IAS5D,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAMpC,eAAe,IAAI,MAAM,GAAG,IAAI;IAI1B,KAAK;IAKX,yDAAyD;YAC3C,WAAW;IAgBzB,8BAA8B;IAC9B,OAAO,CAAC,SAAS;CAOlB"}
1
+ {"version":3,"file":"hybrid-states.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-lmdb/hybrid-states.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGlE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EACL,KAAK,YAAY,EAEjB,MAAM,EACN,KAAK,QAAQ,EACb,gBAAgB,EAKjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C,OAAO,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EACL,eAAe,EACf,KAAK,YAAY,EAGlB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAK9C;;;;;;;;;;;;;GAaG;AACH,qBAAa,sBAAuB,YAAW,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC,YAAY,CAAC;IA8BxG,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IA/BvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyE;IACrG,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IAEnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAC1D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiC;IAEtD,OAAO,CAAC,cAAc,CAAuC;IAE7D,MAAM,CAAC,GAAG,CAAC,EACT,IAAI,EACJ,OAAO,EACP,MAAM,EACN,QAAQ,EACR,SAAS,EACT,WAAW,GACZ,EAAE;QACD,IAAI,EAAE,SAAS,CAAC;QAChB,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB;IAKD,OAAO;IASD,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAexG,iBAAiB,CACrB,MAAM,EAAE,UAAU,EAClB,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;IAmBlC,YAAY,CAAC,KAAK,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;IAI1E,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,GAAG,IAAI;IAS5D,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI;IAI5C,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAOpC,mFAAmF;IACnF,OAAO,CAAC,SAAS;IAmBjB,eAAe,IAAI,MAAM,GAAG,IAAI;IAI1B,KAAK;IAMX,yDAAyD;YAC3C,WAAW;IAqBzB,8BAA8B;IAC9B,OAAO,CAAC,SAAS;CAOlB"}
@@ -1,9 +1,11 @@
1
1
  import { HashDictionary, SortedSet } from "#@typeberry/collections";
2
- import { LeafDb, StateUpdateError, updateLeafs, } from "#@typeberry/database";
2
+ import { InMemoryValueRefsStore, LeafDb, StateUpdateError, updateLeafs, ValueRefs, } from "#@typeberry/database";
3
+ import { Logger } from "#@typeberry/logger";
3
4
  import { SerializedState, StateEntryUpdateAction, serializeStateUpdate, } from "#@typeberry/state-merkleization";
4
5
  import { leafComparator } from "#@typeberry/trie";
5
6
  import { OK, Result } from "#@typeberry/utils";
6
7
  import { LmdbRoot } from "./root.js";
8
+ const logger = Logger.new(import.meta.filename, "db");
7
9
  /**
8
10
  * Hybrid serialized-states db.
9
11
  *
@@ -11,6 +13,12 @@ import { LmdbRoot } from "./root.js";
11
13
  * Reads go straight to lmdb, which keeps its own page cache.
12
14
  * NOTE: this DB is designed for long fuzzing and to be used with pruning to
13
15
  * keep the heap usage bounded.
16
+ *
17
+ * Values that no longer belong to any surviving state are removed from lmdb,
18
+ * decided by in-memory refcounting (`ValueRefs`) driven by the importer's
19
+ * finality signal. Counts are not persisted: this db cannot resume from disk
20
+ * anyway (the leaf sets live in memory), so values left over by a previous
21
+ * run are never collected.
14
22
  */
15
23
  export class HybridSerializedStates {
16
24
  spec;
@@ -20,6 +28,10 @@ export class HybridSerializedStates {
20
28
  lmdbValues;
21
29
  // A single shared values accessor reused by every `LeafDb` we hand out.
22
30
  valuesDb;
31
+ refsStore = new InMemoryValueRefsStore();
32
+ refs = new ValueRefs(this.refsStore);
33
+ // Queue of not-yet-committed value removals, awaited on close.
34
+ pendingCleanup = Promise.resolve();
23
35
  static new({ spec, blake2b, dbPath, readOnly, ephemeral, compression, }) {
24
36
  const root = LmdbRoot.new(dbPath, { readOnly, ephemeral, compression });
25
37
  return new HybridSerializedStates(spec, blake2b, root);
@@ -38,13 +50,14 @@ export class HybridSerializedStates {
38
50
  return res;
39
51
  }
40
52
  this.inMemStates.set(headerHash, leafs);
53
+ this.applyRefs(this.refs.onInitial(values.map((v) => v[0])));
41
54
  return Result.ok(OK);
42
55
  }
43
56
  async updateAndSetState(header, state, update) {
44
57
  const updatedValues = serializeStateUpdate(this.spec, this.blake2b, update);
45
58
  // Clone the leaf set before mutating: the previous state keeps using its own.
46
59
  const newLeafs = SortedSet.fromSortedArray(leafComparator, state.backend.leafs.array);
47
- const { values, leafs } = updateLeafs(newLeafs, this.blake2b, updatedValues);
60
+ const { values, removed, leafs } = updateLeafs(newLeafs, this.blake2b, updatedValues);
48
61
  const res = await this.writeValues(values);
49
62
  if (res.isError) {
50
63
  // Leave the caller's state untouched: its new leaves would reference
@@ -55,6 +68,7 @@ export class HybridSerializedStates {
55
68
  // values are durably written.
56
69
  state.updateBackend(LeafDb.fromLeaves(leafs, this.valuesDb));
57
70
  this.inMemStates.set(header, leafs);
71
+ this.applyRefs(this.refs.onImport(header, { inserted: values.map((v) => v[0]), removed }));
58
72
  return Result.ok(OK);
59
73
  }
60
74
  async getStateRoot(state) {
@@ -68,15 +82,37 @@ export class HybridSerializedStates {
68
82
  const leafDb = LeafDb.fromLeaves(leafs, this.valuesDb);
69
83
  return SerializedState.new(this.spec, this.blake2b, leafDb);
70
84
  }
85
+ commitFinalized(headers) {
86
+ this.applyRefs(this.refs.commitFinalized(headers));
87
+ }
71
88
  markUnused(header) {
72
- // We only remove the state from memory - values are not pruned at all,
73
- // but since they are stored on disk we should be safe.
89
+ // Release the speculative references first (a no-op for finalized states,
90
+ // whose deltas were already consumed by `commitFinalized`).
91
+ this.applyRefs(this.refs.releaseUnfinalized(header));
74
92
  this.inMemStates.delete(header);
75
93
  }
94
+ /** Apply a refcounting update and remove values that lost their last reference. */
95
+ applyRefs(update) {
96
+ this.refsStore.apply(update);
97
+ if (update.removeValues.length === 0) {
98
+ return;
99
+ }
100
+ // Queued, not awaited: a failed removal only leaks a value.
101
+ this.pendingCleanup = this.pendingCleanup
102
+ .then(() => this.lmdbValues.transaction(() => {
103
+ for (const v of update.removeValues) {
104
+ this.lmdbValues.remove(v.raw);
105
+ }
106
+ }))
107
+ .catch((e) => {
108
+ logger.error `Failed to remove unreferenced values: ${e}`;
109
+ });
110
+ }
76
111
  diskSizeInBytes() {
77
112
  return this.root.sizeInBytes();
78
113
  }
79
114
  async close() {
115
+ await this.pendingCleanup;
80
116
  await this.lmdbValues.close();
81
117
  await this.root.close();
82
118
  }
@@ -86,6 +122,11 @@ export class HybridSerializedStates {
86
122
  return Result.ok(OK);
87
123
  }
88
124
  try {
125
+ // Flush queued removals first: a pending cleanup might be about to delete
126
+ // a content hash we are re-inserting now. Writing behind it keeps the
127
+ // re-inserted value on disk (removals are only queued at refcount 0, so
128
+ // none can be queued for a value still referenced).
129
+ await this.pendingCleanup;
89
130
  await this.lmdbValues.transaction(() => {
90
131
  for (const [hash, val] of values) {
91
132
  this.lmdbValues.put(hash.raw, val.raw);
@@ -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,5 +1,6 @@
1
1
  import { type FuzzVersion } from "#@typeberry/ext-ipc";
2
2
  import { v1 as fuzzV1 } from "#@typeberry/fuzz-proto";
3
+ import { type Closer } from "#@typeberry/utils";
3
4
  import type { JamConfig } from "./jam-config.js";
4
5
  export type FuzzConfig = {
5
6
  version: FuzzVersion;
@@ -24,5 +25,7 @@ export declare function getFuzzDetails(): {
24
25
  nodeVersion: fuzzV1.Version;
25
26
  gpVersion: fuzzV1.Version;
26
27
  };
27
- export declare function mainFuzz(fuzzConfig: FuzzConfig, withRelPath: (v: string) => string): Promise<() => void>;
28
+ export declare function mainFuzz(fuzzConfig: FuzzConfig, withRelPath: (v: string) => string): Promise<{
29
+ close: Closer;
30
+ }>;
28
31
  //# sourceMappingURL=main-fuzz.d.ts.map
@@ -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;AAOrD,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,uBAuJxF"}
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"}
@@ -75,6 +75,10 @@ export async function mainFuzz(fuzzConfig, withRelPath) {
75
75
  // every reset, because opening it is the slow part. Only the in-memory blocks
76
76
  // and leaf sets are rebuilt for each vector. fjall-hybrid only.
77
77
  let fjallSession = null;
78
+ // Set when close() starts. Guards resetState so a fuzz command arriving
79
+ // mid-shutdown can't build a fresh node that close() then orphans.
80
+ let isClosing = false;
81
+ let activeReset = null;
78
82
  const chainSpec = getChainSpec(config.node.flavor);
79
83
  const closeFuzzTarget = startFuzzTarget(fuzzConfig.version, fuzzConfig.socket, {
80
84
  ...getFuzzDetails(),
@@ -98,97 +102,122 @@ export async function mainFuzz(fuzzConfig, withRelPath) {
98
102
  }
99
103
  return runningNode.getStateEntries(hash);
100
104
  },
101
- resetState: async (header, state, ancestry) => {
102
- if (runningNode !== null) {
103
- const finish = runningNode.close();
104
- runningNode = null;
105
- await finish;
106
- }
107
- const buildNode = (databaseBasePath) => {
108
- const isPersistent = databaseBasePath !== undefined;
109
- return mainImporter({
110
- ...config,
111
- node: {
112
- ...config.node,
113
- databaseBasePath,
114
- chainSpec: {
115
- ...config.node.chainSpec,
116
- genesisHeader: Encoder.encodeObject(Header.Codec, header, chainSpec),
117
- 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
+ },
118
127
  },
119
- },
120
- ancestry,
121
- network: null,
122
- }, withRelPath, {
123
- initGenesisFromAncestry: fuzzConfig.initGenesisFromAncestry,
124
- // Hybrid keeps leaf sets in RAM, so they must be windowed exactly
125
- // like the in-memory backend; only the large values live on disk.
126
- dummyFinalityDepth: 20,
127
- pruneBlocks: true,
128
- // The on-disk fuzz db is throwaway (we wipe it), so open it ephemeral and
129
- // skip the fsync, we do not need durability here. On full spec ephemeral
130
- // also turns on compression further down, so the big values do not grow the
131
- // db too much. Tiny stays uncompressed, its db is small and speed matters more.
132
- ephemeral: isPersistent,
133
- stateBackend: isPersistent ? hybridStateBackend : "lmdb",
134
- // Reuse the session keyspace (fjall-hybrid only, other backends
135
- // ignore it). Nothing to pass for the in-memory fallback.
136
- sharedFjallSession: isPersistent ? (fjallSession ?? undefined) : undefined,
137
- });
138
- };
139
- if (fuzzDbBase !== undefined) {
140
- try {
141
- if (hybridStateBackend === FUZZ_DB_FJALL) {
142
- // fjall-hybrid: open the values keyspace once and reuse it on every
143
- // reset. The values partition is content-addressed and immutable, so
144
- // it is fine that values pile up across resets, the unreferenced ones
145
- // just sit there. `initializeDatabase` decides whether the db is
146
- // already initialized from the in-memory blocks, which we rebuild on
147
- // every reset, not from the values store, so reusing it does not
148
- // resume the previous run.
149
- if (fjallSession === null) {
150
- // 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.
151
171
  await wipeFuzzDb(fuzzDbBase);
152
- fjallSession = await FjallValuesSession.open(`${withRelPath(fuzzDbBase)}/${FUZZ_FJALL_VALUES_SUBDIR}`, {
153
- ephemeral: true,
154
- cacheSizeBytes: FUZZ_FJALL_CACHE_BYTES,
155
- });
156
- logger.info `🗄️ Opened reusable fjall values session at ${withRelPath(fuzzDbBase)}/${FUZZ_FJALL_VALUES_SUBDIR}`;
157
172
  }
173
+ runningNode = await buildNode(fuzzDbBase);
174
+ return await runningNode.getBestStateRootHash();
158
175
  }
159
- else {
160
- // lmdb-hybrid: keep the old behaviour, wipe and reopen on every
161
- // reset. A fresh db each reset makes `initializeDatabase` set up
162
- // genesis again instead of resuming the previous run.
163
- 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;
164
180
  }
165
- runningNode = await buildNode(fuzzDbBase);
166
- return await runningNode.getBestStateRootHash();
167
181
  }
168
- catch (e) {
169
- // A partially-opened db may leak on failure; acceptable for this degraded fallback (proper cleanup belongs in mainImporter).
170
- logger.warn `Failed to open persistent fuzz db at ${fuzzDbBase}, falling back to in-memory: ${e}`;
171
- 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;
172
189
  }
173
- }
174
- runningNode = await buildNode(undefined);
175
- return await runningNode.getBestStateRootHash();
190
+ };
191
+ reset.then(clearActiveReset, clearActiveReset);
192
+ return reset;
176
193
  },
177
194
  });
178
- return () => {
179
- closeFuzzTarget();
180
- // Close the reused fjall values session (if any) before wiping its files, so
181
- // the keyspace handle is released first.
182
- const closed = fjallSession?.close() ?? Promise.resolve();
183
- fjallSession = null;
184
- if (fuzzDbBase !== undefined) {
185
- // best-effort cleanup on shutdown; ignore failures (dir may already be gone).
186
- closed
187
- .catch(() => { })
188
- .finally(() => {
189
- wipeFuzzDb(fuzzDbBase).catch(() => { });
190
- });
191
- }
195
+ return {
196
+ close: async () => {
197
+ isClosing = true;
198
+ // Stop accepting connections + unlink the socket.
199
+ closeFuzzTarget();
200
+ // Drain the active session (flush + close DB). Swallow errors so a
201
+ // failing close still lets the process exit 0; the db is wiped next.
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
+ }
206
+ if (runningNode !== null) {
207
+ const node = runningNode;
208
+ runningNode = null;
209
+ await node.close().catch((e) => logger.error `Error closing fuzz node: ${e}`);
210
+ }
211
+ // Release the reused fjall values keyspace before wiping its files.
212
+ if (fjallSession !== null) {
213
+ const session = fjallSession;
214
+ fjallSession = null;
215
+ await session.close().catch((e) => logger.error `Error closing fjall session: ${e}`);
216
+ }
217
+ if (fuzzDbBase !== undefined) {
218
+ await wipeFuzzDb(fuzzDbBase).catch(() => { });
219
+ }
220
+ },
192
221
  };
193
222
  }
194
223
  function isValidStateBackend(val) {