@typeberry/lib 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/core/collections/sorted-array.d.ts +1 -1
- package/packages/core/collections/sorted-array.d.ts.map +1 -1
- package/packages/core/collections/sorted-array.js +4 -2
- package/packages/core/collections/sorted-array.test.js +10 -0
- package/packages/core/collections/sorted-set.d.ts +1 -1
- package/packages/core/collections/sorted-set.d.ts.map +1 -1
- package/packages/core/collections/sorted-set.js +6 -2
- package/packages/core/collections/sorted-set.test.js +24 -0
- package/packages/core/utils/index.d.ts +1 -0
- package/packages/core/utils/index.d.ts.map +1 -1
- package/packages/core/utils/index.js +1 -0
- package/packages/core/utils/shutdown.d.ts +18 -0
- package/packages/core/utils/shutdown.d.ts.map +1 -0
- package/packages/core/utils/shutdown.js +3 -0
- package/packages/core/utils/shutdown.node.d.ts +5 -0
- package/packages/core/utils/shutdown.node.d.ts.map +1 -0
- package/packages/core/utils/shutdown.node.js +76 -0
- package/packages/core/utils/shutdown.test.d.ts +2 -0
- package/packages/core/utils/shutdown.test.d.ts.map +1 -0
- package/packages/core/utils/shutdown.test.js +164 -0
- package/packages/extensions/ipc/server.d.ts.map +1 -1
- package/packages/extensions/ipc/server.js +7 -0
- 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/index.d.ts +1 -0
- package/packages/jam/database/index.d.ts.map +1 -1
- package/packages/jam/database/index.js +1 -0
- package/packages/jam/database/leaf-db-update.d.ts +1 -0
- package/packages/jam/database/leaf-db-update.d.ts.map +1 -1
- package/packages/jam/database/leaf-db-update.js +22 -5
- package/packages/jam/database/leaf-db-update.test.d.ts +2 -0
- package/packages/jam/database/leaf-db-update.test.d.ts.map +1 -0
- package/packages/jam/database/leaf-db-update.test.js +81 -0
- package/packages/jam/database/serialized-states-db.d.ts +4 -0
- package/packages/jam/database/serialized-states-db.d.ts.map +1 -1
- package/packages/jam/database/serialized-states-db.js +18 -2
- 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/states.d.ts +11 -0
- package/packages/jam/database/states.d.ts.map +1 -1
- package/packages/jam/database/states.js +3 -0
- package/packages/jam/database/value-refs.d.ts +117 -0
- package/packages/jam/database/value-refs.d.ts.map +1 -0
- package/packages/jam/database/value-refs.js +206 -0
- package/packages/jam/database/value-refs.test.d.ts +2 -0
- package/packages/jam/database/value-refs.test.d.ts.map +1 -0
- package/packages/jam/database/value-refs.test.js +165 -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 +61 -10
- package/packages/jam/database-fjall/hybrid-states.d.ts.map +1 -1
- package/packages/jam/database-fjall/hybrid-states.js +131 -26
- package/packages/jam/database-fjall/hybrid-states.test.js +125 -3
- 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 +29 -17
- package/packages/jam/database-fjall/root.d.ts.map +1 -1
- package/packages/jam/database-fjall/root.js +33 -13
- 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.d.ts +12 -0
- package/packages/jam/database-lmdb/hybrid-states.d.ts.map +1 -1
- package/packages/jam/database-lmdb/hybrid-states.js +45 -4
- package/packages/jam/database-lmdb/hybrid-states.test.js +94 -2
- package/packages/jam/database-lmdb/states.d.ts +1 -0
- package/packages/jam/database-lmdb/states.d.ts.map +1 -1
- package/packages/jam/database-lmdb/states.js +5 -0
- package/packages/jam/node/export.d.ts.map +1 -1
- package/packages/jam/node/export.js +18 -9
- package/packages/jam/node/main-fuzz.d.ts +4 -1
- package/packages/jam/node/main-fuzz.d.ts.map +1 -1
- package/packages/jam/node/main-fuzz.js +127 -55
- package/packages/jam/node/main-importer.d.ts +11 -2
- package/packages/jam/node/main-importer.d.ts.map +1 -1
- package/packages/jam/node/main-importer.js +27 -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 +58 -9
- package/packages/workers/api-node/config.d.ts.map +1 -1
- package/packages/workers/api-node/config.js +140 -18
- 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hybrid-states.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-fjall/hybrid-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,EACL,KAAK,YAAY,
|
|
1
|
+
{"version":3,"file":"hybrid-states.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-fjall/hybrid-states.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,40 +1,114 @@
|
|
|
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";
|
|
6
6
|
import { OK, Result } from "#@typeberry/utils";
|
|
7
7
|
import { FjallRoot, toUint8Array } from "./root.js";
|
|
8
8
|
const logger = Logger.new(import.meta.filename, "db");
|
|
9
|
+
/**
|
|
10
|
+
* One open fjall keyspace together with its content-addressed `values`
|
|
11
|
+
* partition.
|
|
12
|
+
*
|
|
13
|
+
* Opening the keyspace is the slow part, so the fuzz target opens one session
|
|
14
|
+
* per run and reuses it for every reset (see `HybridSerializedStates.fromSession`).
|
|
15
|
+
* The values partition is immutable - the key is the hash of the value - so it
|
|
16
|
+
* is fine that values pile up across resets, the unreferenced ones just sit
|
|
17
|
+
* there unused.
|
|
18
|
+
*/
|
|
19
|
+
export class FjallValuesSession {
|
|
20
|
+
root;
|
|
21
|
+
values;
|
|
22
|
+
constructor(root,
|
|
23
|
+
/** Shared content-addressed values partition, reused across resets. */
|
|
24
|
+
values) {
|
|
25
|
+
this.root = root;
|
|
26
|
+
this.values = values;
|
|
27
|
+
}
|
|
28
|
+
/** Open (or create) the keyspace at `dbPath` and its `values` partition. */
|
|
29
|
+
static async open(dbPath, options = {}) {
|
|
30
|
+
if (options.readOnly === true) {
|
|
31
|
+
throw new Error("FjallValuesSession requires a writable keyspace.");
|
|
32
|
+
}
|
|
33
|
+
const root = await FjallRoot.open(dbPath, options);
|
|
34
|
+
try {
|
|
35
|
+
const values = await root.writablePartition("values");
|
|
36
|
+
return new FjallValuesSession(root, values);
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
await root.close();
|
|
40
|
+
throw e;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Flush the journal to disk (a no-op for ephemeral keyspaces). */
|
|
44
|
+
async persist() {
|
|
45
|
+
await this.root.persist();
|
|
46
|
+
}
|
|
47
|
+
/** Size of the keyspace directory on disk, in bytes. */
|
|
48
|
+
sizeInBytes() {
|
|
49
|
+
return this.root.sizeInBytes();
|
|
50
|
+
}
|
|
51
|
+
/** Release the keyspace handle (skips the sync-all fsync when ephemeral). */
|
|
52
|
+
async close() {
|
|
53
|
+
await this.root.close();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
9
56
|
/**
|
|
10
57
|
* Hybrid serialized-states db (fjall variant).
|
|
11
58
|
*
|
|
12
|
-
* States (leafs) are kept in
|
|
13
|
-
* Reads
|
|
14
|
-
*
|
|
59
|
+
* States (leafs) are kept in memory, only the large values go to fjall on disk.
|
|
60
|
+
* Reads hit fjall directly, which keeps its own bounded block cache. Meant for
|
|
61
|
+
* long fuzzing, used together with pruning so the heap stays bounded.
|
|
62
|
+
*
|
|
63
|
+
* Construction is async, and value writes are flushed explicitly, because fjall
|
|
64
|
+
* has no transaction primitive.
|
|
15
65
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
66
|
+
* Values that no longer belong to any surviving state are removed from fjall,
|
|
67
|
+
* decided by in-memory refcounting (`ValueRefs`) driven by the importer's
|
|
68
|
+
* finality signal. Counts are not persisted: this db cannot resume from disk
|
|
69
|
+
* anyway (the leaf sets live in memory), so values left over by a previous run
|
|
70
|
+
* are never collected. An instance backed by a shared session (fuzz reset
|
|
71
|
+
* reuse) only ever prunes values it inserted itself, since its refcounts start
|
|
72
|
+
* empty - values left behind by earlier resets stay untouched.
|
|
19
73
|
*/
|
|
20
74
|
export class HybridSerializedStates {
|
|
21
75
|
spec;
|
|
22
76
|
blake2b;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
static async new({ spec, blake2b, dbPath, ephemeral, }) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
return new HybridSerializedStates(spec, blake2b,
|
|
77
|
+
session;
|
|
78
|
+
ownsSession;
|
|
79
|
+
static async new({ spec, blake2b, dbPath, ephemeral, cacheSizeBytes, }) {
|
|
80
|
+
const session = await FjallValuesSession.open(dbPath, { ephemeral, cacheSizeBytes });
|
|
81
|
+
// This instance owns the session it just opened, so its `close()` closes it.
|
|
82
|
+
return new HybridSerializedStates(spec, blake2b, session, true);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Wrap an already-open `FjallValuesSession` and reuse its keyspace.
|
|
86
|
+
*
|
|
87
|
+
* The new instance starts with its own empty in-memory leaf sets but shares
|
|
88
|
+
* the values partition on disk. Its `close()` does not close the session, the
|
|
89
|
+
* session owner closes it once. The fuzz target uses this to keep one keyspace
|
|
90
|
+
* across resets and only rebuild the in-memory state for each vector.
|
|
91
|
+
*/
|
|
92
|
+
static fromSession(spec, blake2b, session) {
|
|
93
|
+
return new HybridSerializedStates(spec, blake2b, session, false);
|
|
29
94
|
}
|
|
30
95
|
inMemStates = HashDictionary.new();
|
|
31
96
|
// A single shared values accessor reused by every `LeafDb` we hand out.
|
|
32
97
|
valuesDb;
|
|
33
|
-
|
|
98
|
+
/** Shared content-addressed values partition (owned by `session`). */
|
|
99
|
+
values;
|
|
100
|
+
refsStore = new InMemoryValueRefsStore();
|
|
101
|
+
refs = new ValueRefs(this.refsStore);
|
|
102
|
+
// Queue of not-yet-committed value removals, awaited on close.
|
|
103
|
+
pendingCleanup = Promise.resolve();
|
|
104
|
+
constructor(spec, blake2b, session,
|
|
105
|
+
/** Whether `close()` should close the underlying session. */
|
|
106
|
+
ownsSession) {
|
|
34
107
|
this.spec = spec;
|
|
35
108
|
this.blake2b = blake2b;
|
|
36
|
-
this.
|
|
37
|
-
this.
|
|
109
|
+
this.session = session;
|
|
110
|
+
this.ownsSession = ownsSession;
|
|
111
|
+
this.values = session.values;
|
|
38
112
|
this.valuesDb = { get: (key) => this.readValue(key) };
|
|
39
113
|
}
|
|
40
114
|
async insertInitialState(headerHash, entries) {
|
|
@@ -44,13 +118,14 @@ export class HybridSerializedStates {
|
|
|
44
118
|
return res;
|
|
45
119
|
}
|
|
46
120
|
this.inMemStates.set(headerHash, leafs);
|
|
121
|
+
this.applyRefs(this.refs.onInitial(values.map((v) => v[0])));
|
|
47
122
|
return Result.ok(OK);
|
|
48
123
|
}
|
|
49
124
|
async updateAndSetState(header, state, update) {
|
|
50
125
|
const updatedValues = serializeStateUpdate(this.spec, this.blake2b, update);
|
|
51
126
|
// Clone the leaf set before mutating: the previous state keeps using its own.
|
|
52
127
|
const newLeafs = SortedSet.fromSortedArray(leafComparator, state.backend.leafs.array);
|
|
53
|
-
const { values, leafs } = updateLeafs(newLeafs, this.blake2b, updatedValues);
|
|
128
|
+
const { values, removed, leafs } = updateLeafs(newLeafs, this.blake2b, updatedValues);
|
|
54
129
|
const res = await this.writeValues(values);
|
|
55
130
|
if (res.isError) {
|
|
56
131
|
// Leave the caller's state untouched: its new leaves would reference
|
|
@@ -61,6 +136,7 @@ export class HybridSerializedStates {
|
|
|
61
136
|
// values are durably written.
|
|
62
137
|
state.updateBackend(LeafDb.fromLeaves(leafs, this.valuesDb));
|
|
63
138
|
this.inMemStates.set(header, leafs);
|
|
139
|
+
this.applyRefs(this.refs.onImport(header, { inserted: values.map((v) => v[0]), removed }));
|
|
64
140
|
return Result.ok(OK);
|
|
65
141
|
}
|
|
66
142
|
async getStateRoot(state) {
|
|
@@ -74,27 +150,56 @@ export class HybridSerializedStates {
|
|
|
74
150
|
const leafDb = LeafDb.fromLeaves(leafs, this.valuesDb);
|
|
75
151
|
return SerializedState.new(this.spec, this.blake2b, leafDb);
|
|
76
152
|
}
|
|
153
|
+
commitFinalized(headers) {
|
|
154
|
+
this.applyRefs(this.refs.commitFinalized(headers));
|
|
155
|
+
}
|
|
77
156
|
markUnused(header) {
|
|
78
|
-
//
|
|
79
|
-
//
|
|
157
|
+
// Release the speculative references first (a no-op for finalized states,
|
|
158
|
+
// whose deltas were already consumed by `commitFinalized`).
|
|
159
|
+
this.applyRefs(this.refs.releaseUnfinalized(header));
|
|
80
160
|
this.inMemStates.delete(header);
|
|
81
161
|
}
|
|
82
162
|
diskSizeInBytes() {
|
|
83
|
-
return this.
|
|
163
|
+
return this.session.sizeInBytes();
|
|
164
|
+
}
|
|
165
|
+
/** Apply a refcounting update and remove values that lost their last reference. */
|
|
166
|
+
applyRefs(update) {
|
|
167
|
+
this.refsStore.apply(update);
|
|
168
|
+
if (update.removeValues.length === 0) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// Queued, not awaited: a failed removal only leaks a value. No explicit
|
|
172
|
+
// persist - removals are flushed together with the next value write.
|
|
173
|
+
this.pendingCleanup = this.pendingCleanup
|
|
174
|
+
.then(() => Promise.all(update.removeValues.map((v) => this.values.remove(v.raw))))
|
|
175
|
+
.catch((e) => {
|
|
176
|
+
logger.error `Failed to remove unreferenced values: ${e}`;
|
|
177
|
+
});
|
|
84
178
|
}
|
|
85
179
|
async close() {
|
|
86
|
-
|
|
180
|
+
// Finish any queued value removals before tearing down (or releasing) the
|
|
181
|
+
// session - they operate on the shared `values` partition.
|
|
182
|
+
await this.pendingCleanup;
|
|
183
|
+
// Instances backed by a shared session (fuzz reset reuse) keep the keyspace
|
|
184
|
+
// open for the next reset. The session owner closes it once.
|
|
185
|
+
if (this.ownsSession) {
|
|
186
|
+
await this.session.close();
|
|
187
|
+
}
|
|
87
188
|
}
|
|
88
|
-
/** Write new large values to fjall, then flush. */
|
|
189
|
+
/** Write new large values to fjall in a single batch, then flush. */
|
|
89
190
|
async writeValues(values) {
|
|
90
191
|
if (values.length === 0) {
|
|
91
192
|
return Result.ok(OK);
|
|
92
193
|
}
|
|
93
194
|
try {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
195
|
+
// Flush queued removals first: a pending cleanup might be about to delete
|
|
196
|
+
// a content hash we are re-inserting now. Writing behind it keeps the
|
|
197
|
+
// re-inserted value on disk (removals are only queued at refcount 0, so
|
|
198
|
+
// none can be queued for a value still referenced).
|
|
199
|
+
await this.pendingCleanup;
|
|
200
|
+
const entries = values.map(([hash, val]) => ({ key: hash.raw, value: val.raw }));
|
|
201
|
+
await this.values.insertBatch(entries);
|
|
202
|
+
await this.session.persist();
|
|
98
203
|
}
|
|
99
204
|
catch (e) {
|
|
100
205
|
logger.error `${e}`;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import { afterEach, before, beforeEach, describe, it } from "node:test";
|
|
4
|
+
import { tryAsServiceId } from "#@typeberry/block";
|
|
4
5
|
import { Bytes, BytesBlob } from "#@typeberry/bytes";
|
|
5
6
|
import { tinyChainSpec } from "#@typeberry/config";
|
|
6
7
|
import { Blake2b, HASH_SIZE } from "#@typeberry/hash";
|
|
7
|
-
import { InMemoryState } from "#@typeberry/state";
|
|
8
|
+
import { InMemoryState, StorageItem, UpdateStorage, } from "#@typeberry/state";
|
|
9
|
+
import { testState } from "#@typeberry/state/test.utils.js";
|
|
8
10
|
import { StateEntries } from "#@typeberry/state-merkleization";
|
|
9
|
-
import { deepEqual, OK, Result } from "#@typeberry/utils";
|
|
10
|
-
import { HybridSerializedStates } from "./hybrid-states.js";
|
|
11
|
+
import { asOpaqueType, deepEqual, OK, Result } from "#@typeberry/utils";
|
|
12
|
+
import { FjallValuesSession, HybridSerializedStates } from "./hybrid-states.js";
|
|
11
13
|
let blake2b;
|
|
12
14
|
before(async () => {
|
|
13
15
|
blake2b = await Blake2b.createHasher();
|
|
@@ -66,6 +68,36 @@ describe("Fjall hybrid serialized states", () => {
|
|
|
66
68
|
await states.close();
|
|
67
69
|
}
|
|
68
70
|
});
|
|
71
|
+
it("shares an open values session across resets without closing it", async () => {
|
|
72
|
+
// The fuzz reset path opens the values keyspace once per session and reuses
|
|
73
|
+
// it: each "reset" builds a fresh states instance sharing that session, and
|
|
74
|
+
// closing a session-backed states must NOT close the shared keyspace.
|
|
75
|
+
const session = await FjallValuesSession.open(dbPath);
|
|
76
|
+
try {
|
|
77
|
+
const big = BytesBlob.blobFromString("z".repeat(100));
|
|
78
|
+
const key = Bytes.fill(HASH_SIZE, 7).asOpaque();
|
|
79
|
+
const entries = StateEntries.fromEntriesUnsafe([[key, big]]);
|
|
80
|
+
// First "reset": write values through a states instance, then close it.
|
|
81
|
+
const first = HybridSerializedStates.fromSession(spec, blake2b, session);
|
|
82
|
+
const res = await first.insertInitialState(headerHash, entries);
|
|
83
|
+
deepEqual(res, Result.ok(OK));
|
|
84
|
+
await first.close();
|
|
85
|
+
// Second "reset": a fresh states sharing the same session. Its in-memory
|
|
86
|
+
// leaf set is independent (empty until it inserts)...
|
|
87
|
+
const second = HybridSerializedStates.fromSession(spec, blake2b, session);
|
|
88
|
+
assert.strictEqual(second.getState(headerHash), null);
|
|
89
|
+
// ...but the on-disk values store is the same one, still open and usable
|
|
90
|
+
// (a closed keyspace would throw here).
|
|
91
|
+
await second.insertInitialState(headerHash, entries);
|
|
92
|
+
const state = second.getState(headerHash);
|
|
93
|
+
assert.ok(state !== null);
|
|
94
|
+
assert.strictEqual(`${state.backend.get(key)}`, `${big}`);
|
|
95
|
+
await second.close();
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
await session.close();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
69
101
|
it("drops the leaf set on markUnused while values stay on disk", async () => {
|
|
70
102
|
const states = await HybridSerializedStates.new({ spec, blake2b, dbPath });
|
|
71
103
|
try {
|
|
@@ -81,3 +113,93 @@ describe("Fjall hybrid serialized states", () => {
|
|
|
81
113
|
}
|
|
82
114
|
});
|
|
83
115
|
});
|
|
116
|
+
function hh(n) {
|
|
117
|
+
return Bytes.fill(HASH_SIZE, n).asOpaque();
|
|
118
|
+
}
|
|
119
|
+
const storageKey = asOpaqueType(BytesBlob.blobFromString("test-key"));
|
|
120
|
+
/** A state update writing a single large (non-embedded) value under `storageKey`. */
|
|
121
|
+
function storageUpdate(value) {
|
|
122
|
+
const item = StorageItem.create({ key: storageKey, value: BytesBlob.blobFromString(value) });
|
|
123
|
+
return {
|
|
124
|
+
storage: new Map([[tryAsServiceId(0), [UpdateStorage.set({ storage: item })]]]),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// > 32 bytes => stored in the values db (not embedded in the leaf).
|
|
128
|
+
const BIG_1 = "a".repeat(100);
|
|
129
|
+
const BIG_2 = "b".repeat(100);
|
|
130
|
+
/** `true` if every value referenced by the state can still be resolved. */
|
|
131
|
+
function canReadFully(state) {
|
|
132
|
+
if (state === null) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
state.backend.intoStateEntries();
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Value removals are queued, so poll for the expected outcome. */
|
|
144
|
+
async function eventually(check, what, timeoutMs = 5_000) {
|
|
145
|
+
const start = Date.now();
|
|
146
|
+
while (!check()) {
|
|
147
|
+
if (Date.now() - start > timeoutMs) {
|
|
148
|
+
assert.fail(`Timed out waiting for: ${what}`);
|
|
149
|
+
}
|
|
150
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
describe("Fjall hybrid serialized states value refcounting", () => {
|
|
154
|
+
const spec = tinyChainSpec;
|
|
155
|
+
let dbPath = "";
|
|
156
|
+
beforeEach(() => {
|
|
157
|
+
dbPath = createTempDir();
|
|
158
|
+
});
|
|
159
|
+
afterEach(() => {
|
|
160
|
+
fs.rmSync(dbPath, { recursive: true });
|
|
161
|
+
});
|
|
162
|
+
it("removes a replaced value from disk once the replacement finalizes", async () => {
|
|
163
|
+
const states = await HybridSerializedStates.new({ spec, blake2b, dbPath });
|
|
164
|
+
try {
|
|
165
|
+
await states.insertInitialState(hh(0), StateEntries.serializeInMemory(spec, blake2b, testState()));
|
|
166
|
+
const s0 = states.getState(hh(0));
|
|
167
|
+
assert.ok(s0 !== null);
|
|
168
|
+
await states.updateAndSetState(hh(1), s0, storageUpdate(BIG_1));
|
|
169
|
+
// a handle to the post-1 state, surviving the pruning below
|
|
170
|
+
const stale1 = states.getState(hh(1));
|
|
171
|
+
const s1 = states.getState(hh(1));
|
|
172
|
+
assert.ok(s1 !== null);
|
|
173
|
+
await states.updateAndSetState(hh(2), s1, storageUpdate(BIG_2));
|
|
174
|
+
states.commitFinalized([hh(1)]);
|
|
175
|
+
assert.ok(canReadFully(stale1), "still referenced by the finalized tip");
|
|
176
|
+
states.commitFinalized([hh(2)]);
|
|
177
|
+
await eventually(() => !canReadFully(stale1), "replaced value removed from fjall");
|
|
178
|
+
assert.ok(canReadFully(states.getState(hh(2))), "the new finalized tip stays fully readable");
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
await states.close();
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
it("collects values of a pruned dead fork and keeps the surviving chain intact", async () => {
|
|
185
|
+
const states = await HybridSerializedStates.new({ spec, blake2b, dbPath });
|
|
186
|
+
try {
|
|
187
|
+
await states.insertInitialState(hh(0), StateEntries.serializeInMemory(spec, blake2b, testState()));
|
|
188
|
+
const s0 = states.getState(hh(0));
|
|
189
|
+
assert.ok(s0 !== null);
|
|
190
|
+
await states.updateAndSetState(hh(1), s0, storageUpdate(BIG_1));
|
|
191
|
+
// a dead fork on top of genesis, inserting a different value
|
|
192
|
+
const fork = states.getState(hh(0));
|
|
193
|
+
assert.ok(fork !== null);
|
|
194
|
+
await states.updateAndSetState(hh(0xaa), fork, storageUpdate(BIG_2));
|
|
195
|
+
const staleFork = states.getState(hh(0xaa));
|
|
196
|
+
states.markUnused(hh(0xaa));
|
|
197
|
+
assert.strictEqual(states.getState(hh(0xaa)), null);
|
|
198
|
+
await eventually(() => !canReadFully(staleFork), "fork-only value removed from fjall");
|
|
199
|
+
assert.ok(canReadFully(states.getState(hh(1))), "surviving chain is unaffected");
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
await states.close();
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-fjall/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-fjall/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC"}
|
|
@@ -1,25 +1,33 @@
|
|
|
1
|
-
import { type Partition } from "@fjall-js/fjall";
|
|
2
|
-
export type { Partition };
|
|
1
|
+
import { type Partition, type ReadonlyPartition } from "@fjall-js/fjall";
|
|
2
|
+
export type { Partition, ReadonlyPartition };
|
|
3
|
+
export type FjallPartition = Partition | ReadonlyPartition;
|
|
3
4
|
/**
|
|
4
5
|
* Normalize a value read from fjall (a Node `Buffer`) into a plain `Uint8Array`.
|
|
5
6
|
*/
|
|
6
7
|
export declare function toUint8Array(value: Buffer | null): Uint8Array | null;
|
|
7
8
|
export type FjallRootOptions = {
|
|
9
|
+
/** Open a read-only wrapper surface over the shared keyspace. */
|
|
10
|
+
readOnly?: boolean;
|
|
8
11
|
/**
|
|
9
|
-
* When set, durability flushes (`persist`)
|
|
12
|
+
* When set, we skip explicit durability flushes (`persist`).
|
|
10
13
|
*
|
|
11
|
-
* Only safe for throwaway databases
|
|
12
|
-
*
|
|
14
|
+
* Only safe for throwaway databases, like the fuzz target that wipes on every
|
|
15
|
+
* reset.
|
|
13
16
|
*/
|
|
14
17
|
ephemeral?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Cache size in bytes, shared by all partitions of the keyspace. fjall reads
|
|
20
|
+
* through this cache, so it bounds how much we keep in memory. When not set,
|
|
21
|
+
* typeberry uses a conservative default.
|
|
22
|
+
*/
|
|
23
|
+
cacheSizeBytes?: number;
|
|
15
24
|
};
|
|
16
25
|
/**
|
|
17
|
-
*
|
|
26
|
+
* Thin wrapper over the fjall keyspace.
|
|
18
27
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* of that.
|
|
28
|
+
* fjall is an LSM-tree: it reads and writes through normal file i/o and keeps
|
|
29
|
+
* only a bounded block cache in memory, so the resident set stays bounded even
|
|
30
|
+
* when the store on disk is big.
|
|
23
31
|
*/
|
|
24
32
|
export declare class FjallRoot {
|
|
25
33
|
private readonly keyspace;
|
|
@@ -28,9 +36,13 @@ export declare class FjallRoot {
|
|
|
28
36
|
private readonly options;
|
|
29
37
|
private constructor();
|
|
30
38
|
/** Open (or create) a fjall keyspace at the given path. */
|
|
31
|
-
static open(dbPath: string, options
|
|
39
|
+
static open(dbPath: string, options?: FjallRootOptions): Promise<FjallRoot>;
|
|
40
|
+
/** Whether this root was opened through fjall's read-only surface. */
|
|
41
|
+
get readOnly(): boolean;
|
|
32
42
|
/** Open (or create) a partition under this keyspace. */
|
|
33
|
-
partition(name: string): Promise<
|
|
43
|
+
partition(name: string): Promise<FjallPartition>;
|
|
44
|
+
/** Open a writable partition, failing early when this root is read-only. */
|
|
45
|
+
writablePartition(name: string): Promise<Partition>;
|
|
34
46
|
/**
|
|
35
47
|
* Flush the journal to disk so prior writes survive a crash.
|
|
36
48
|
*
|
|
@@ -39,14 +51,14 @@ export declare class FjallRoot {
|
|
|
39
51
|
*/
|
|
40
52
|
persist(): Promise<void>;
|
|
41
53
|
/**
|
|
42
|
-
*
|
|
54
|
+
* Size of the keyspace directory on disk, in bytes.
|
|
43
55
|
*
|
|
44
|
-
* Returns `null`
|
|
45
|
-
*
|
|
46
|
-
*
|
|
56
|
+
* Returns `null` when the directory cannot be walked (e.g. not created yet).
|
|
57
|
+
* A fjall keyspace is a directory of partition and journal files, so we sum
|
|
58
|
+
* them recursively.
|
|
47
59
|
*/
|
|
48
60
|
sizeInBytes(): number | null;
|
|
49
|
-
/**
|
|
61
|
+
/** Release this keyspace handle. Call persist() first when durability is needed. */
|
|
50
62
|
close(): Promise<void>;
|
|
51
63
|
}
|
|
52
64
|
//# sourceMappingURL=root.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-fjall/root.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-fjall/root.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,SAAS,EAEd,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAC;AAIzB,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC;AAC7C,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,iBAAiB,CAAC;AAE3D;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,UAAU,GAAG,IAAI,CAKpE;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;;GAMG;AACH,qBAAa,SAAS;IAElB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJ1B,OAAO;IAOP,2DAA2D;WAC9C,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC;IAWrF,sEAAsE;IACtE,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,wDAAwD;IAClD,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAItD,4EAA4E;IACtE,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAOzD;;;;;OAKG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAU9B;;;;;;OAMG;IACH,WAAW,IAAI,MAAM,GAAG,IAAI;IAQ5B,oFAAoF;IAC9E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { open } from "@fjall-js/fjall";
|
|
3
|
+
import { open, openReadonly, } from "@fjall-js/fjall";
|
|
4
|
+
const DEFAULT_CACHE_SIZE_BYTES = 64 * 1024 * 1024;
|
|
4
5
|
/**
|
|
5
6
|
* Normalize a value read from fjall (a Node `Buffer`) into a plain `Uint8Array`.
|
|
6
7
|
*/
|
|
@@ -11,12 +12,11 @@ export function toUint8Array(value) {
|
|
|
11
12
|
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
14
|
-
*
|
|
15
|
+
* Thin wrapper over the fjall keyspace.
|
|
15
16
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* of that.
|
|
17
|
+
* fjall is an LSM-tree: it reads and writes through normal file i/o and keeps
|
|
18
|
+
* only a bounded block cache in memory, so the resident set stays bounded even
|
|
19
|
+
* when the store on disk is big.
|
|
20
20
|
*/
|
|
21
21
|
export class FjallRoot {
|
|
22
22
|
keyspace;
|
|
@@ -30,14 +30,31 @@ export class FjallRoot {
|
|
|
30
30
|
this.options = options;
|
|
31
31
|
}
|
|
32
32
|
/** Open (or create) a fjall keyspace at the given path. */
|
|
33
|
-
static async open(dbPath, options) {
|
|
34
|
-
|
|
33
|
+
static async open(dbPath, options = {}) {
|
|
34
|
+
// fjall-js 0.3 shares one engine per path. Readers must use the read-only
|
|
35
|
+
// wrapper surface; durability is driven explicitly through persist().
|
|
36
|
+
const config = {
|
|
37
|
+
path: dbPath,
|
|
38
|
+
cacheSizeBytes: options.cacheSizeBytes ?? DEFAULT_CACHE_SIZE_BYTES,
|
|
39
|
+
};
|
|
40
|
+
const keyspace = options.readOnly === true ? await openReadonly(config) : await open(config);
|
|
35
41
|
return new FjallRoot(keyspace, dbPath, options);
|
|
36
42
|
}
|
|
43
|
+
/** Whether this root was opened through fjall's read-only surface. */
|
|
44
|
+
get readOnly() {
|
|
45
|
+
return this.options.readOnly === true;
|
|
46
|
+
}
|
|
37
47
|
/** Open (or create) a partition under this keyspace. */
|
|
38
48
|
async partition(name) {
|
|
39
49
|
return this.keyspace.partition(name);
|
|
40
50
|
}
|
|
51
|
+
/** Open a writable partition, failing early when this root is read-only. */
|
|
52
|
+
async writablePartition(name) {
|
|
53
|
+
if (this.readOnly) {
|
|
54
|
+
throw new Error(`Cannot open writable fjall partition '${name}' from a read-only keyspace.`);
|
|
55
|
+
}
|
|
56
|
+
return (await this.keyspace.partition(name));
|
|
57
|
+
}
|
|
41
58
|
/**
|
|
42
59
|
* Flush the journal to disk so prior writes survive a crash.
|
|
43
60
|
*
|
|
@@ -48,14 +65,17 @@ export class FjallRoot {
|
|
|
48
65
|
if (this.options.ephemeral === true) {
|
|
49
66
|
return;
|
|
50
67
|
}
|
|
68
|
+
if (this.readOnly) {
|
|
69
|
+
throw new Error("Cannot persist a read-only fjall keyspace.");
|
|
70
|
+
}
|
|
51
71
|
await this.keyspace.persist();
|
|
52
72
|
}
|
|
53
73
|
/**
|
|
54
|
-
*
|
|
74
|
+
* Size of the keyspace directory on disk, in bytes.
|
|
55
75
|
*
|
|
56
|
-
* Returns `null`
|
|
57
|
-
*
|
|
58
|
-
*
|
|
76
|
+
* Returns `null` when the directory cannot be walked (e.g. not created yet).
|
|
77
|
+
* A fjall keyspace is a directory of partition and journal files, so we sum
|
|
78
|
+
* them recursively.
|
|
59
79
|
*/
|
|
60
80
|
sizeInBytes() {
|
|
61
81
|
try {
|
|
@@ -65,7 +85,7 @@ export class FjallRoot {
|
|
|
65
85
|
return null;
|
|
66
86
|
}
|
|
67
87
|
}
|
|
68
|
-
/**
|
|
88
|
+
/** Release this keyspace handle. Call persist() first when durability is needed. */
|
|
69
89
|
async close() {
|
|
70
90
|
await this.keyspace.close();
|
|
71
91
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { HeaderHash, StateRootHash } from "#@typeberry/block";
|
|
2
|
+
import type { ChainSpec } from "#@typeberry/config";
|
|
3
|
+
import { type InitStatesDb, LeafDb, type StatesDb, StateUpdateError } from "#@typeberry/database";
|
|
4
|
+
import type { Blake2b } from "#@typeberry/hash";
|
|
5
|
+
import type { ServicesUpdate, State } from "#@typeberry/state";
|
|
6
|
+
import type { StateEntries } from "#@typeberry/state-merkleization";
|
|
7
|
+
import { SerializedState } from "#@typeberry/state-merkleization";
|
|
8
|
+
import { OK, Result } from "#@typeberry/utils";
|
|
9
|
+
import { type FjallRoot } from "./root.js";
|
|
10
|
+
/** fjall-backed full on-disk serialized state storage. */
|
|
11
|
+
export declare class FjallStates implements StatesDb<SerializedState<LeafDb>>, InitStatesDb<StateEntries> {
|
|
12
|
+
private readonly spec;
|
|
13
|
+
private readonly blake2b;
|
|
14
|
+
private readonly root;
|
|
15
|
+
private readonly states;
|
|
16
|
+
private readonly values;
|
|
17
|
+
static open(spec: ChainSpec, blake2b: Blake2b, root: FjallRoot): Promise<FjallStates>;
|
|
18
|
+
private readonly valuesDb;
|
|
19
|
+
private constructor();
|
|
20
|
+
insertInitialState(headerHash: HeaderHash, entries: StateEntries): Promise<Result<OK, StateUpdateError>>;
|
|
21
|
+
updateAndSetState(headerHash: HeaderHash, state: SerializedState<LeafDb>, update: Partial<State & ServicesUpdate>): Promise<Result<OK, StateUpdateError>>;
|
|
22
|
+
getStateRoot(state: SerializedState<LeafDb>): Promise<StateRootHash>;
|
|
23
|
+
getState(headerHash: HeaderHash): SerializedState<LeafDb> | null;
|
|
24
|
+
commitFinalized(_headers: HeaderHash[]): void;
|
|
25
|
+
markUnused(headerHash: HeaderHash): void;
|
|
26
|
+
diskSizeInBytes(): number | null;
|
|
27
|
+
close(): Promise<void>;
|
|
28
|
+
private updateAndCommit;
|
|
29
|
+
private readValue;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=states.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"states.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-fjall/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,EACL,KAAK,YAAY,EACjB,MAAM,EACN,KAAK,QAAQ,EACb,gBAAgB,EAGjB,MAAM,qBAAqB,CAAC;AAC7B,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;AAE/G,OAAO,EAAE,EAAE,EAAE,MAAM,EAAkB,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAuB,KAAK,SAAS,EAAgC,MAAM,WAAW,CAAC;AAI9F,0DAA0D;AAC1D,qBAAa,WAAY,YAAW,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC,YAAY,CAAC;IAS7F,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;WAZZ,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IAK3F,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IAEpC,OAAO;IAUD,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAQxG,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;IAUlC,YAAY,CAAC,KAAK,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;IAI1E,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,GAAG,IAAI;IAahE,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,IAAI;IAK7C,UAAU,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAMxC,eAAe,IAAI,MAAM,GAAG,IAAI;IAI1B,KAAK;YAEG,eAAe;IAyB7B,OAAO,CAAC,SAAS;CAOlB"}
|