@typeberry/lib 0.8.4 → 0.9.0-4a9f363
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 +6 -4
- package/packages/configs/index.d.ts +30 -1
- package/packages/configs/index.d.ts.map +1 -1
- package/packages/configs/index.js +4 -2
- package/packages/configs/typeberry-dev-full.json +29 -0
- package/packages/core/bytes/bytes.d.ts +1 -0
- package/packages/core/bytes/bytes.d.ts.map +1 -1
- package/packages/core/bytes/bytes.js +8 -0
- 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/debug.d.ts +4 -2
- package/packages/core/utils/debug.d.ts.map +1 -1
- package/packages/core/utils/debug.js +18 -13
- package/packages/core/utils/debug.test.js +12 -6
- 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 +22 -3
- package/packages/jam/config-node/node-config.d.ts.map +1 -1
- package/packages/jam/config-node/node-config.js +33 -6
- package/packages/jam/config-node/node-config.test.js +11 -4
- 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 +1 -0
- package/packages/jam/database/serialized-states-db.d.ts.map +1 -1
- package/packages/jam/database/serialized-states-db.js +4 -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 +96 -0
- package/packages/jam/database-fjall/hybrid-states.d.ts.map +1 -0
- package/packages/jam/database-fjall/hybrid-states.js +218 -0
- package/packages/jam/database-fjall/hybrid-states.test.d.ts +2 -0
- package/packages/jam/database-fjall/hybrid-states.test.d.ts.map +1 -0
- package/packages/jam/database-fjall/hybrid-states.test.js +113 -0
- package/packages/jam/database-fjall/index.d.ts +5 -0
- package/packages/jam/database-fjall/index.d.ts.map +1 -0
- package/packages/jam/database-fjall/index.js +4 -0
- package/packages/jam/database-fjall/root.d.ts +64 -0
- package/packages/jam/database-fjall/root.d.ts.map +1 -0
- package/packages/jam/database-fjall/root.js +105 -0
- 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/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/jamnp-s/tasks/ticket-distribution.d.ts +18 -10
- package/packages/jam/jamnp-s/tasks/ticket-distribution.d.ts.map +1 -1
- package/packages/jam/jamnp-s/tasks/ticket-distribution.js +44 -68
- package/packages/jam/jamnp-s/tasks/ticket-distribution.test.js +30 -8
- 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 +142 -55
- package/packages/jam/node/main-importer.d.ts +15 -3
- package/packages/jam/node/main-importer.d.ts.map +1 -1
- package/packages/jam/node/main-importer.js +30 -14
- 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/jam/safrole/bandersnatch-vrf.d.ts +24 -4
- package/packages/jam/safrole/bandersnatch-vrf.d.ts.map +1 -1
- package/packages/jam/safrole/bandersnatch-vrf.js +63 -26
- package/packages/jam/safrole/bandersnatch-vrf.test.js +12 -9
- package/packages/jam/safrole/bandersnatch-wasm.d.ts +10 -0
- package/packages/jam/safrole/bandersnatch-wasm.d.ts.map +1 -1
- package/packages/jam/safrole/bandersnatch-wasm.js +12 -0
- package/packages/jam/safrole/safrole.js +5 -5
- package/packages/jam/safrole/safrole.test.js +13 -13
- package/packages/jam/ticket-pool/index.d.ts +4 -0
- package/packages/jam/ticket-pool/index.d.ts.map +1 -0
- package/packages/jam/ticket-pool/index.js +3 -0
- package/packages/jam/ticket-pool/pending-ticket-pool.d.ts +30 -0
- package/packages/jam/ticket-pool/pending-ticket-pool.d.ts.map +1 -0
- package/packages/jam/ticket-pool/pending-ticket-pool.js +56 -0
- package/packages/jam/ticket-pool/pending-ticket-pool.test.d.ts +2 -0
- package/packages/jam/ticket-pool/pending-ticket-pool.test.d.ts.map +1 -0
- package/packages/jam/ticket-pool/pending-ticket-pool.test.js +67 -0
- package/packages/jam/ticket-pool/ticket-validator.d.ts +47 -0
- package/packages/jam/ticket-pool/ticket-validator.d.ts.map +1 -0
- package/packages/jam/ticket-pool/ticket-validator.js +34 -0
- package/packages/jam/ticket-pool/ticket-validator.test.d.ts +2 -0
- package/packages/jam/ticket-pool/ticket-validator.test.d.ts.map +1 -0
- package/packages/jam/ticket-pool/ticket-validator.test.js +35 -0
- package/packages/jam/ticket-pool/verified-ticket-pool.d.ts +26 -0
- package/packages/jam/ticket-pool/verified-ticket-pool.d.ts.map +1 -0
- package/packages/jam/ticket-pool/verified-ticket-pool.js +41 -0
- package/packages/jam/ticket-pool/verified-ticket-pool.test.d.ts +2 -0
- package/packages/jam/ticket-pool/verified-ticket-pool.test.d.ts.map +1 -0
- package/packages/jam/ticket-pool/verified-ticket-pool.test.js +54 -0
- 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 +65 -9
- package/packages/workers/api-node/config.d.ts.map +1 -1
- package/packages/workers/api-node/config.js +151 -26
- package/packages/workers/api-node/config.test.js +88 -2
- 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/{generator.d.ts → block-generator.d.ts} +5 -5
- package/packages/workers/block-authorship/block-generator.d.ts.map +1 -0
- package/packages/workers/block-authorship/{generator.js → block-generator.js} +3 -3
- package/packages/workers/block-authorship/block-generator.test.d.ts +2 -0
- package/packages/workers/block-authorship/block-generator.test.d.ts.map +1 -0
- package/packages/workers/block-authorship/{generator.test.js → block-generator.test.js} +8 -8
- package/packages/workers/block-authorship/epoch-authoring-slots.d.ts +35 -0
- package/packages/workers/block-authorship/epoch-authoring-slots.d.ts.map +1 -0
- package/packages/workers/block-authorship/epoch-authoring-slots.js +86 -0
- package/packages/workers/block-authorship/epoch-tracker.d.ts +29 -0
- package/packages/workers/block-authorship/epoch-tracker.d.ts.map +1 -0
- package/packages/workers/block-authorship/epoch-tracker.js +80 -0
- package/packages/workers/block-authorship/index.d.ts.map +1 -1
- package/packages/workers/block-authorship/index.js +1 -1
- package/packages/workers/block-authorship/main.d.ts +3 -0
- package/packages/workers/block-authorship/main.d.ts.map +1 -1
- package/packages/workers/block-authorship/main.js +198 -316
- package/packages/workers/block-authorship/ticket-generator/bootstrap-main.d.ts +2 -0
- package/packages/workers/block-authorship/ticket-generator/bootstrap-main.d.ts.map +1 -0
- package/packages/workers/block-authorship/ticket-generator/bootstrap-main.js +23 -0
- package/packages/workers/block-authorship/ticket-generator/index.d.ts +16 -0
- package/packages/workers/block-authorship/ticket-generator/index.d.ts.map +1 -0
- package/packages/workers/block-authorship/ticket-generator/index.js +62 -0
- package/packages/workers/block-authorship/ticket-generator/protocol.d.ts +50 -0
- package/packages/workers/block-authorship/ticket-generator/protocol.d.ts.map +1 -0
- package/packages/workers/block-authorship/ticket-generator/protocol.js +54 -0
- package/packages/workers/block-authorship/{ticket-generator.d.ts → ticket-generator/ticket-generator.d.ts} +4 -0
- package/packages/workers/block-authorship/ticket-generator/ticket-generator.d.ts.map +1 -0
- package/packages/workers/block-authorship/{ticket-generator.js → ticket-generator/ticket-generator.js} +19 -9
- package/packages/workers/block-authorship/ticket-generator/ticket-generator.test.d.ts.map +1 -0
- package/packages/workers/block-authorship/{ticket-generator.test.js → ticket-generator/ticket-generator.test.js} +13 -9
- package/packages/workers/block-authorship/ticket-generator/worker-pool.d.ts +36 -0
- package/packages/workers/block-authorship/ticket-generator/worker-pool.d.ts.map +1 -0
- package/packages/workers/block-authorship/ticket-generator/worker-pool.js +111 -0
- package/packages/workers/block-authorship/ticket-validator.d.ts +31 -0
- package/packages/workers/block-authorship/ticket-validator.d.ts.map +1 -0
- package/packages/workers/block-authorship/ticket-validator.js +59 -0
- package/packages/workers/comms-authorship-network/protocol.d.ts +14 -4
- package/packages/workers/comms-authorship-network/protocol.d.ts.map +1 -1
- package/packages/workers/comms-authorship-network/protocol.js +12 -6
- package/packages/workers/comms-authorship-network/tickets-message.d.ts +0 -14
- package/packages/workers/comms-authorship-network/tickets-message.d.ts.map +1 -1
- package/packages/workers/comms-authorship-network/tickets-message.js +0 -17
- package/packages/workers/importer/importer.d.ts +2 -2
- package/packages/workers/importer/importer.d.ts.map +1 -1
- package/packages/workers/importer/importer.js +5 -5
- 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/importer/stats.d.ts +1 -3
- package/packages/workers/importer/stats.d.ts.map +1 -1
- package/packages/workers/importer/stats.js +12 -12
- package/packages/workers/jam-network/main.d.ts.map +1 -1
- package/packages/workers/jam-network/main.js +26 -5
- package/packages/workers/block-authorship/generator.d.ts.map +0 -1
- package/packages/workers/block-authorship/generator.test.d.ts +0 -2
- package/packages/workers/block-authorship/generator.test.d.ts.map +0 -1
- package/packages/workers/block-authorship/ticket-generator.d.ts.map +0 -1
- package/packages/workers/block-authorship/ticket-generator.test.d.ts.map +0 -1
- /package/packages/configs/{typeberry-dev.json → typeberry-dev-tiny.json} +0 -0
- /package/packages/workers/block-authorship/{ticket-generator.test.d.ts → ticket-generator/ticket-generator.test.d.ts} +0 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { HashDictionary } from "#@typeberry/collections";
|
|
2
|
+
/** `true` if applying the update would not change anything. */
|
|
3
|
+
export function isEmptyUpdate(update) {
|
|
4
|
+
return (update.finalizedCounts.length === 0 &&
|
|
5
|
+
update.pendingCounts.length === 0 &&
|
|
6
|
+
update.setDeltas.length === 0 &&
|
|
7
|
+
update.removeDeltas.length === 0 &&
|
|
8
|
+
update.removeValues.length === 0);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Decides when a content-addressed value can be removed from the values DB.
|
|
12
|
+
*
|
|
13
|
+
* A value is needed as long as some surviving state references it. Surviving
|
|
14
|
+
* states are the finalized tip plus its unfinalized descendants, so we track:
|
|
15
|
+
* - `finalized`: how many leaves in the current finalized-tip state reference V,
|
|
16
|
+
* advanced strictly by replaying finalized blocks' deltas (never on prune);
|
|
17
|
+
* - `pending`: how many surviving, not-yet-finalized blocks inserted V (an
|
|
18
|
+
* over-approximation of unfinalized references).
|
|
19
|
+
*
|
|
20
|
+
* A value is removable exactly when both counts reach zero.
|
|
21
|
+
*
|
|
22
|
+
* This class only makes decisions: every operation reads the current state
|
|
23
|
+
* through `ValueRefsReader` and returns a `ValueRefsUpdate` describing what
|
|
24
|
+
* should change. Nothing is written here - the owning backend applies the
|
|
25
|
+
* update with whatever consistency guarantees it can provide.
|
|
26
|
+
*/
|
|
27
|
+
export class ValueRefs {
|
|
28
|
+
reader;
|
|
29
|
+
constructor(reader) {
|
|
30
|
+
this.reader = reader;
|
|
31
|
+
}
|
|
32
|
+
/** Record values referenced by the genesis / initial finalized state. */
|
|
33
|
+
onInitial(inserted) {
|
|
34
|
+
const update = new UpdateBuilder(this.reader);
|
|
35
|
+
for (const v of inserted) {
|
|
36
|
+
update.incFinalized(v);
|
|
37
|
+
}
|
|
38
|
+
return update.build();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Record a freshly imported, not-yet-finalized block.
|
|
42
|
+
*
|
|
43
|
+
* Importing the same header twice is a no-op, otherwise the second import
|
|
44
|
+
* would double-count `pending` references and pin the values forever.
|
|
45
|
+
*/
|
|
46
|
+
onImport(header, delta) {
|
|
47
|
+
const update = new UpdateBuilder(this.reader);
|
|
48
|
+
if (update.getDelta(header) !== undefined) {
|
|
49
|
+
return update.build();
|
|
50
|
+
}
|
|
51
|
+
update.setDelta(header, delta);
|
|
52
|
+
for (const v of delta.inserted) {
|
|
53
|
+
update.incPending(v);
|
|
54
|
+
}
|
|
55
|
+
return update.build();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Apply the value deltas of newly finalized blocks, in finalized (ancestor-first) order.
|
|
59
|
+
*
|
|
60
|
+
* Moves each inserted value from `pending` to `finalized`, drops `finalized`
|
|
61
|
+
* references for removed values, and collects anything that becomes unreferenced.
|
|
62
|
+
*/
|
|
63
|
+
commitFinalized(headers) {
|
|
64
|
+
const update = new UpdateBuilder(this.reader);
|
|
65
|
+
for (const header of headers) {
|
|
66
|
+
const delta = update.getDelta(header);
|
|
67
|
+
if (delta === undefined) {
|
|
68
|
+
// already committed, or not tracked (e.g. genesis)
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
for (const v of delta.inserted) {
|
|
72
|
+
update.incFinalized(v);
|
|
73
|
+
update.decPending(v);
|
|
74
|
+
}
|
|
75
|
+
for (const v of delta.removed) {
|
|
76
|
+
update.decFinalized(v);
|
|
77
|
+
}
|
|
78
|
+
update.removeDelta(header);
|
|
79
|
+
}
|
|
80
|
+
return update.build();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Release the speculative references of a state being discarded.
|
|
84
|
+
*
|
|
85
|
+
* For a header that was still unfinalized (a dead fork) the returned update
|
|
86
|
+
* releases its inserted values, which may become collectable. For an already
|
|
87
|
+
* finalized state the update is empty (check with `isEmptyUpdate`): its delta
|
|
88
|
+
* was consumed on finality and its values are accounted for in `finalized`.
|
|
89
|
+
*/
|
|
90
|
+
releaseUnfinalized(header) {
|
|
91
|
+
const update = new UpdateBuilder(this.reader);
|
|
92
|
+
const delta = update.getDelta(header);
|
|
93
|
+
if (delta === undefined) {
|
|
94
|
+
return update.build();
|
|
95
|
+
}
|
|
96
|
+
for (const v of delta.inserted) {
|
|
97
|
+
update.decPending(v);
|
|
98
|
+
}
|
|
99
|
+
update.removeDelta(header);
|
|
100
|
+
return update.build();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Accumulates mutations of a single operation as an overlay over the reader,
|
|
105
|
+
* so that later steps observe earlier ones (e.g. several blocks finalized at once).
|
|
106
|
+
*/
|
|
107
|
+
class UpdateBuilder {
|
|
108
|
+
reader;
|
|
109
|
+
finalized = HashDictionary.new();
|
|
110
|
+
pending = HashDictionary.new();
|
|
111
|
+
setDeltas = HashDictionary.new();
|
|
112
|
+
removedDeltas = HashDictionary.new();
|
|
113
|
+
/** Values that lost a reference and may need removal - verified against final counts in `build`. */
|
|
114
|
+
removalCandidates = HashDictionary.new();
|
|
115
|
+
constructor(reader) {
|
|
116
|
+
this.reader = reader;
|
|
117
|
+
}
|
|
118
|
+
getDelta(header) {
|
|
119
|
+
if (this.removedDeltas.has(header)) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
return this.setDeltas.get(header) ?? this.reader.getDelta(header);
|
|
123
|
+
}
|
|
124
|
+
setDelta(header, delta) {
|
|
125
|
+
this.setDeltas.set(header, delta);
|
|
126
|
+
}
|
|
127
|
+
removeDelta(header) {
|
|
128
|
+
this.removedDeltas.set(header, header);
|
|
129
|
+
}
|
|
130
|
+
incFinalized(v) {
|
|
131
|
+
this.finalized.set(v, this.getFinalized(v) + 1);
|
|
132
|
+
}
|
|
133
|
+
decFinalized(v) {
|
|
134
|
+
this.finalized.set(v, Math.max(0, this.getFinalized(v) - 1));
|
|
135
|
+
this.removalCandidates.set(v, v);
|
|
136
|
+
}
|
|
137
|
+
incPending(v) {
|
|
138
|
+
this.pending.set(v, this.getPending(v) + 1);
|
|
139
|
+
}
|
|
140
|
+
decPending(v) {
|
|
141
|
+
this.pending.set(v, Math.max(0, this.getPending(v) - 1));
|
|
142
|
+
this.removalCandidates.set(v, v);
|
|
143
|
+
}
|
|
144
|
+
build() {
|
|
145
|
+
const removeValues = [];
|
|
146
|
+
for (const v of this.removalCandidates.values()) {
|
|
147
|
+
if (this.getFinalized(v) === 0 && this.getPending(v) === 0) {
|
|
148
|
+
removeValues.push(v);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
finalizedCounts: Array.from(this.finalized),
|
|
153
|
+
pendingCounts: Array.from(this.pending),
|
|
154
|
+
setDeltas: Array.from(this.setDeltas),
|
|
155
|
+
removeDeltas: Array.from(this.removedDeltas.keys()),
|
|
156
|
+
removeValues,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
getFinalized(v) {
|
|
160
|
+
return this.finalized.get(v) ?? this.reader.getFinalizedCount(v);
|
|
161
|
+
}
|
|
162
|
+
getPending(v) {
|
|
163
|
+
return this.pending.get(v) ?? this.reader.getPendingCount(v);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* In-memory refcounting store, reusable by the in-memory and hybrid states DBs
|
|
168
|
+
* (the hybrids cannot resume from disk anyway, so persisting counts buys nothing).
|
|
169
|
+
*
|
|
170
|
+
* NOTE: `apply` does not touch the values DB - the caller owns it and must
|
|
171
|
+
* handle `update.removeValues` itself.
|
|
172
|
+
*/
|
|
173
|
+
export class InMemoryValueRefsStore {
|
|
174
|
+
finalized = HashDictionary.new();
|
|
175
|
+
pending = HashDictionary.new();
|
|
176
|
+
deltas = HashDictionary.new();
|
|
177
|
+
getFinalizedCount(hash) {
|
|
178
|
+
return this.finalized.get(hash) ?? 0;
|
|
179
|
+
}
|
|
180
|
+
getPendingCount(hash) {
|
|
181
|
+
return this.pending.get(hash) ?? 0;
|
|
182
|
+
}
|
|
183
|
+
getDelta(header) {
|
|
184
|
+
return this.deltas.get(header);
|
|
185
|
+
}
|
|
186
|
+
apply(update) {
|
|
187
|
+
applyCounts(this.finalized, update.finalizedCounts);
|
|
188
|
+
applyCounts(this.pending, update.pendingCounts);
|
|
189
|
+
for (const [header, delta] of update.setDeltas) {
|
|
190
|
+
this.deltas.set(header, delta);
|
|
191
|
+
}
|
|
192
|
+
for (const header of update.removeDeltas) {
|
|
193
|
+
this.deltas.delete(header);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function applyCounts(store, counts) {
|
|
198
|
+
for (const [hash, count] of counts) {
|
|
199
|
+
if (count === 0) {
|
|
200
|
+
store.delete(hash);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
store.set(hash, count);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"value-refs.test.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database/value-refs.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { Bytes } from "#@typeberry/bytes";
|
|
4
|
+
import { HASH_SIZE } from "#@typeberry/hash";
|
|
5
|
+
import { InMemoryValueRefsStore, isEmptyUpdate, ValueRefs, } from "./value-refs.js";
|
|
6
|
+
function vh(n) {
|
|
7
|
+
return Bytes.fill(HASH_SIZE, n).asOpaque();
|
|
8
|
+
}
|
|
9
|
+
function hh(n) {
|
|
10
|
+
return Bytes.fill(HASH_SIZE, n).asOpaque();
|
|
11
|
+
}
|
|
12
|
+
/** Tracks which value hashes are present in the (simulated) values DB. */
|
|
13
|
+
class TrackingValues {
|
|
14
|
+
present = new Set();
|
|
15
|
+
write(h) {
|
|
16
|
+
this.present.add(h.toString());
|
|
17
|
+
}
|
|
18
|
+
delete(h) {
|
|
19
|
+
this.present.delete(h.toString());
|
|
20
|
+
}
|
|
21
|
+
has(h) {
|
|
22
|
+
return this.present.has(h.toString());
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function setup() {
|
|
26
|
+
const store = new InMemoryValueRefsStore();
|
|
27
|
+
const refs = new ValueRefs(store);
|
|
28
|
+
const values = new TrackingValues();
|
|
29
|
+
// what every backend is expected to do with an update
|
|
30
|
+
const apply = (update) => {
|
|
31
|
+
store.apply(update);
|
|
32
|
+
for (const v of update.removeValues) {
|
|
33
|
+
values.delete(v);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
return { refs, store, values, apply };
|
|
37
|
+
}
|
|
38
|
+
/** Simulate importing a block: write inserted values + record the delta. */
|
|
39
|
+
function importBlock({ refs, values, apply }, header, delta) {
|
|
40
|
+
const full = { inserted: delta.inserted ?? [], removed: delta.removed ?? [] };
|
|
41
|
+
for (const v of full.inserted) {
|
|
42
|
+
values.write(v);
|
|
43
|
+
}
|
|
44
|
+
apply(refs.onImport(header, full));
|
|
45
|
+
}
|
|
46
|
+
describe("ValueRefs", () => {
|
|
47
|
+
it("deletes a value once its only finalized reference is removed", () => {
|
|
48
|
+
const s = setup();
|
|
49
|
+
const { refs, values, apply } = s;
|
|
50
|
+
const V = vh(1);
|
|
51
|
+
importBlock(s, hh(1), { inserted: [V] });
|
|
52
|
+
apply(refs.commitFinalized([hh(1)]));
|
|
53
|
+
assert.ok(values.has(V), "kept while referenced by finalized tip");
|
|
54
|
+
importBlock(s, hh(2), { removed: [V] });
|
|
55
|
+
apply(refs.commitFinalized([hh(2)]));
|
|
56
|
+
assert.ok(!values.has(V), "deleted after last finalized reference removed");
|
|
57
|
+
});
|
|
58
|
+
it("keeps a value referenced under multiple keys until all references are gone", () => {
|
|
59
|
+
const s = setup();
|
|
60
|
+
const { refs, values, apply } = s;
|
|
61
|
+
const V = vh(1);
|
|
62
|
+
importBlock(s, hh(1), { inserted: [V] });
|
|
63
|
+
apply(refs.commitFinalized([hh(1)]));
|
|
64
|
+
importBlock(s, hh(2), { inserted: [V] });
|
|
65
|
+
apply(refs.commitFinalized([hh(2)]));
|
|
66
|
+
importBlock(s, hh(3), { removed: [V] });
|
|
67
|
+
apply(refs.commitFinalized([hh(3)]));
|
|
68
|
+
assert.ok(values.has(V), "kept while one reference remains");
|
|
69
|
+
importBlock(s, hh(4), { removed: [V] });
|
|
70
|
+
apply(refs.commitFinalized([hh(4)]));
|
|
71
|
+
assert.ok(!values.has(V), "deleted after the last reference");
|
|
72
|
+
});
|
|
73
|
+
it("keeps a value removed on the finalized chain while an unfinalized fork re-adds it", () => {
|
|
74
|
+
const s = setup();
|
|
75
|
+
const { refs, values, apply } = s;
|
|
76
|
+
const V = vh(1);
|
|
77
|
+
// genesis already references V
|
|
78
|
+
apply(refs.onInitial([V]));
|
|
79
|
+
values.write(V);
|
|
80
|
+
// finalized chain removes V
|
|
81
|
+
importBlock(s, hh(10), { removed: [V] });
|
|
82
|
+
// an unfinalized fork re-adds V
|
|
83
|
+
importBlock(s, hh(20), { inserted: [V] });
|
|
84
|
+
apply(refs.commitFinalized([hh(10)]));
|
|
85
|
+
assert.ok(values.has(V), "kept because an unfinalized fork still references V");
|
|
86
|
+
});
|
|
87
|
+
it("collects a value that lived only on a discarded fork", () => {
|
|
88
|
+
const s = setup();
|
|
89
|
+
const { refs, values, apply } = s;
|
|
90
|
+
const W = vh(2);
|
|
91
|
+
importBlock(s, hh(30), { inserted: [W] });
|
|
92
|
+
assert.ok(values.has(W));
|
|
93
|
+
const update = refs.releaseUnfinalized(hh(30));
|
|
94
|
+
apply(update);
|
|
95
|
+
assert.strictEqual(isEmptyUpdate(update), false);
|
|
96
|
+
assert.ok(!values.has(W), "dead-fork-only value is collected");
|
|
97
|
+
});
|
|
98
|
+
it("does not touch finalized references when releasing an already finalized header", () => {
|
|
99
|
+
const s = setup();
|
|
100
|
+
const { refs, values, apply } = s;
|
|
101
|
+
const V = vh(1);
|
|
102
|
+
importBlock(s, hh(1), { inserted: [V] });
|
|
103
|
+
apply(refs.commitFinalized([hh(1)]));
|
|
104
|
+
const update = refs.releaseUnfinalized(hh(1));
|
|
105
|
+
apply(update);
|
|
106
|
+
assert.strictEqual(isEmptyUpdate(update), true, "header was already finalized");
|
|
107
|
+
assert.ok(values.has(V), "finalized value is untouched");
|
|
108
|
+
});
|
|
109
|
+
it("ignores unknown headers in commitFinalized", () => {
|
|
110
|
+
const { refs } = setup();
|
|
111
|
+
const update = refs.commitFinalized([hh(99)]);
|
|
112
|
+
assert.strictEqual(isEmptyUpdate(update), true);
|
|
113
|
+
});
|
|
114
|
+
it("ignores a duplicate import of the same header", () => {
|
|
115
|
+
const s = setup();
|
|
116
|
+
const { refs, values, apply } = s;
|
|
117
|
+
const V = vh(1);
|
|
118
|
+
importBlock(s, hh(1), { inserted: [V] });
|
|
119
|
+
const second = refs.onImport(hh(1), { inserted: [V], removed: [] });
|
|
120
|
+
assert.strictEqual(isEmptyUpdate(second), true, "duplicate import is a no-op");
|
|
121
|
+
// had the duplicate bumped `pending` twice, releasing the fork would leave
|
|
122
|
+
// a dangling count and the value would never be collected
|
|
123
|
+
apply(refs.releaseUnfinalized(hh(1)));
|
|
124
|
+
assert.ok(!values.has(V));
|
|
125
|
+
});
|
|
126
|
+
it("counts a header only once within a single commitFinalized call", () => {
|
|
127
|
+
const s = setup();
|
|
128
|
+
const { refs, values, apply } = s;
|
|
129
|
+
const V = vh(1);
|
|
130
|
+
importBlock(s, hh(1), { inserted: [V] });
|
|
131
|
+
apply(refs.commitFinalized([hh(1), hh(1)]));
|
|
132
|
+
importBlock(s, hh(2), { removed: [V] });
|
|
133
|
+
apply(refs.commitFinalized([hh(2)]));
|
|
134
|
+
assert.ok(!values.has(V), "a double-counted finalized reference would keep the value alive");
|
|
135
|
+
});
|
|
136
|
+
it("does not remove a value re-referenced later within the same batch", () => {
|
|
137
|
+
const s = setup();
|
|
138
|
+
const { refs, values, apply } = s;
|
|
139
|
+
const V = vh(1);
|
|
140
|
+
apply(refs.onInitial([V]));
|
|
141
|
+
values.write(V);
|
|
142
|
+
// one block removes V, its descendant re-adds it; both finalize at once
|
|
143
|
+
importBlock(s, hh(1), { removed: [V] });
|
|
144
|
+
importBlock(s, hh(2), { inserted: [V] });
|
|
145
|
+
const update = refs.commitFinalized([hh(1), hh(2)]);
|
|
146
|
+
apply(update);
|
|
147
|
+
assert.strictEqual(update.removeValues.length, 0);
|
|
148
|
+
assert.ok(values.has(V), "the re-added reference keeps the value alive");
|
|
149
|
+
});
|
|
150
|
+
it("emits absolute counts so re-applying an update is harmless", () => {
|
|
151
|
+
const s = setup();
|
|
152
|
+
const { refs, store, values, apply } = s;
|
|
153
|
+
const V = vh(1);
|
|
154
|
+
importBlock(s, hh(1), { inserted: [V] });
|
|
155
|
+
const update = refs.commitFinalized([hh(1)]);
|
|
156
|
+
apply(update);
|
|
157
|
+
// crash-replay: same batch applied again
|
|
158
|
+
apply(update);
|
|
159
|
+
assert.strictEqual(store.getFinalizedCount(V), 1);
|
|
160
|
+
assert.strictEqual(store.getPendingCount(V), 0);
|
|
161
|
+
importBlock(s, hh(2), { removed: [V] });
|
|
162
|
+
apply(refs.commitFinalized([hh(2)]));
|
|
163
|
+
assert.ok(!values.has(V), "refcounting still exact after replay");
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type BlockView, type ExtrinsicView, type HeaderHash, type HeaderView, type StateRootHash } from "#@typeberry/block";
|
|
2
|
+
import type { ChainSpec } from "#@typeberry/config";
|
|
3
|
+
import type { BlocksDb } from "#@typeberry/database/blocks.js";
|
|
4
|
+
import { type WithHash } from "#@typeberry/hash";
|
|
5
|
+
import { type FjallRoot } from "./root.js";
|
|
6
|
+
/** fjall-backed block storage. */
|
|
7
|
+
export declare class FjallBlocks implements BlocksDb {
|
|
8
|
+
private readonly chainSpec;
|
|
9
|
+
private readonly root;
|
|
10
|
+
private readonly headers;
|
|
11
|
+
private readonly extrinsics;
|
|
12
|
+
private readonly postStateRoots;
|
|
13
|
+
static open(chainSpec: ChainSpec, root: FjallRoot): Promise<FjallBlocks>;
|
|
14
|
+
private constructor();
|
|
15
|
+
setPostStateRoot(hash: HeaderHash, postStateRoot: StateRootHash): Promise<void>;
|
|
16
|
+
getPostStateRoot(hash: HeaderHash): StateRootHash | null;
|
|
17
|
+
insertBlock(block: WithHash<HeaderHash, BlockView>): Promise<void>;
|
|
18
|
+
setBestHeaderHash(hash: HeaderHash): Promise<void>;
|
|
19
|
+
getBestHeaderHash(): HeaderHash;
|
|
20
|
+
getHeader(hash: HeaderHash): HeaderView | null;
|
|
21
|
+
getExtrinsic(hash: HeaderHash): ExtrinsicView | null;
|
|
22
|
+
markUnused(hash: HeaderHash): void;
|
|
23
|
+
close(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=blocks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-fjall/blocks.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,aAAa,EAElB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3D,OAAO,EAAuB,KAAK,SAAS,EAAgC,MAAM,WAAW,CAAC;AAK9F,kCAAkC;AAClC,qBAAa,WAAY,YAAW,QAAQ;IAWxC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,cAAc;WAdpB,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IAS9E,OAAO;IAQD,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrF,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,aAAa,GAAG,IAAI;IAQlD,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAOlE,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAKxD,iBAAiB,IAAI,UAAU;IAQ/B,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI;IAQ9C,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,aAAa,GAAG,IAAI;IAQpD,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAQ5B,KAAK;CACZ"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Extrinsic, Header, } from "#@typeberry/block";
|
|
2
|
+
import { Bytes } from "#@typeberry/bytes";
|
|
3
|
+
import { Decoder } from "#@typeberry/codec";
|
|
4
|
+
import { HASH_SIZE } from "#@typeberry/hash";
|
|
5
|
+
import { Logger } from "#@typeberry/logger";
|
|
6
|
+
import { toUint8Array } from "./root.js";
|
|
7
|
+
const BEST_BLOCK = new TextEncoder().encode("best hash and posterior state root");
|
|
8
|
+
const logger = Logger.new(import.meta.filename, "db");
|
|
9
|
+
/** fjall-backed block storage. */
|
|
10
|
+
export class FjallBlocks {
|
|
11
|
+
chainSpec;
|
|
12
|
+
root;
|
|
13
|
+
headers;
|
|
14
|
+
extrinsics;
|
|
15
|
+
postStateRoots;
|
|
16
|
+
static async open(chainSpec, root) {
|
|
17
|
+
const [headers, extrinsics, postStateRoots] = await Promise.all([
|
|
18
|
+
root.partition("headers"),
|
|
19
|
+
root.partition("extrinsics"),
|
|
20
|
+
root.partition("postStateRoots"),
|
|
21
|
+
]);
|
|
22
|
+
return new FjallBlocks(chainSpec, root, headers, extrinsics, postStateRoots);
|
|
23
|
+
}
|
|
24
|
+
constructor(chainSpec, root, headers, extrinsics, postStateRoots) {
|
|
25
|
+
this.chainSpec = chainSpec;
|
|
26
|
+
this.root = root;
|
|
27
|
+
this.headers = headers;
|
|
28
|
+
this.extrinsics = extrinsics;
|
|
29
|
+
this.postStateRoots = postStateRoots;
|
|
30
|
+
}
|
|
31
|
+
async setPostStateRoot(hash, postStateRoot) {
|
|
32
|
+
await writable(this.postStateRoots, this.root).insert(hash.raw, postStateRoot.raw);
|
|
33
|
+
}
|
|
34
|
+
getPostStateRoot(hash) {
|
|
35
|
+
const postStateRoot = toUint8Array(this.postStateRoots.get(hash.raw));
|
|
36
|
+
if (postStateRoot === null) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return Bytes.fromBlob(postStateRoot, HASH_SIZE).asOpaque();
|
|
40
|
+
}
|
|
41
|
+
async insertBlock(block) {
|
|
42
|
+
await Promise.all([
|
|
43
|
+
writable(this.headers, this.root).insert(block.hash.raw, block.data.header.view().encoded().raw),
|
|
44
|
+
writable(this.extrinsics, this.root).insert(block.hash.raw, block.data.extrinsic.view().encoded().raw),
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
async setBestHeaderHash(hash) {
|
|
48
|
+
await writable(this.headers, this.root).insert(BEST_BLOCK, hash.raw);
|
|
49
|
+
await this.root.persist();
|
|
50
|
+
}
|
|
51
|
+
getBestHeaderHash() {
|
|
52
|
+
const bestHeaderHash = toUint8Array(this.headers.get(BEST_BLOCK));
|
|
53
|
+
if (bestHeaderHash === null) {
|
|
54
|
+
return Bytes.zero(HASH_SIZE).asOpaque();
|
|
55
|
+
}
|
|
56
|
+
return Bytes.fromBlob(bestHeaderHash, HASH_SIZE).asOpaque();
|
|
57
|
+
}
|
|
58
|
+
getHeader(hash) {
|
|
59
|
+
const data = toUint8Array(this.headers.get(hash.raw));
|
|
60
|
+
if (data === null) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return Decoder.decodeObject(Header.Codec.View, data, this.chainSpec);
|
|
64
|
+
}
|
|
65
|
+
getExtrinsic(hash) {
|
|
66
|
+
const data = toUint8Array(this.extrinsics.get(hash.raw));
|
|
67
|
+
if (data === null) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return Decoder.decodeObject(Extrinsic.Codec.View, data, this.chainSpec);
|
|
71
|
+
}
|
|
72
|
+
markUnused(hash) {
|
|
73
|
+
void Promise.all([
|
|
74
|
+
writable(this.headers, this.root).remove(hash.raw),
|
|
75
|
+
writable(this.extrinsics, this.root).remove(hash.raw),
|
|
76
|
+
writable(this.postStateRoots, this.root).remove(hash.raw),
|
|
77
|
+
]).catch((e) => logger.warn `Failed to prune block ${hash}: ${e}`);
|
|
78
|
+
}
|
|
79
|
+
async close() { }
|
|
80
|
+
}
|
|
81
|
+
function writable(partition, root) {
|
|
82
|
+
if (root.readOnly) {
|
|
83
|
+
throw new Error("Cannot write through a read-only fjall partition.");
|
|
84
|
+
}
|
|
85
|
+
return partition;
|
|
86
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocks.test.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-fjall/blocks.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import { afterEach, before, beforeEach, describe, it } from "node:test";
|
|
4
|
+
import { testBlockView } from "#@typeberry/block/test-helpers.js";
|
|
5
|
+
import { Bytes } from "#@typeberry/bytes";
|
|
6
|
+
import { tinyChainSpec } from "#@typeberry/config";
|
|
7
|
+
import { Blake2b, HASH_SIZE, WithHash } from "#@typeberry/hash";
|
|
8
|
+
import { FjallBlocks } from "./blocks.js";
|
|
9
|
+
import { FjallRoot } from "./root.js";
|
|
10
|
+
let blake2b;
|
|
11
|
+
before(async () => {
|
|
12
|
+
blake2b = await Blake2b.createHasher();
|
|
13
|
+
});
|
|
14
|
+
describe("Fjall blocks database", () => {
|
|
15
|
+
let tmpDir = "";
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
tmpDir = fs.mkdtempSync("typeberry-fjall-blocks-");
|
|
18
|
+
});
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
it("sets and retrieves the best header hash", async () => {
|
|
23
|
+
const root = await FjallRoot.open(tmpDir, { ephemeral: true });
|
|
24
|
+
const blocks = await FjallBlocks.open(tinyChainSpec, root);
|
|
25
|
+
try {
|
|
26
|
+
await blocks.setBestHeaderHash(Bytes.fill(HASH_SIZE, 5).asOpaque());
|
|
27
|
+
assert.strictEqual(blocks.getBestHeaderHash().toString(), "0x0505050505050505050505050505050505050505050505050505050505050505");
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
await blocks.close();
|
|
31
|
+
await root.close();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
it("sets and retrieves post state roots", async () => {
|
|
35
|
+
const root = await FjallRoot.open(tmpDir, { ephemeral: true });
|
|
36
|
+
const blocks = await FjallBlocks.open(tinyChainSpec, root);
|
|
37
|
+
try {
|
|
38
|
+
await blocks.setPostStateRoot(Bytes.fill(HASH_SIZE, 5).asOpaque(), Bytes.fill(HASH_SIZE, 10).asOpaque());
|
|
39
|
+
assert.strictEqual(blocks.getPostStateRoot(Bytes.fill(HASH_SIZE, 1).asOpaque())?.toString(), undefined);
|
|
40
|
+
assert.strictEqual(blocks.getPostStateRoot(Bytes.fill(HASH_SIZE, 5).asOpaque())?.toString(), "0x0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a");
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
await blocks.close();
|
|
44
|
+
await root.close();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
it("stores and retrieves a block", async () => {
|
|
48
|
+
const root = await FjallRoot.open(tmpDir, { ephemeral: true });
|
|
49
|
+
const blocks = await FjallBlocks.open(tinyChainSpec, root);
|
|
50
|
+
try {
|
|
51
|
+
const block = testBlockView();
|
|
52
|
+
const headerHash = blake2b.hashBytes(block.header.view().encoded()).asOpaque();
|
|
53
|
+
await blocks.insertBlock(WithHash.new(headerHash, block));
|
|
54
|
+
assert.deepStrictEqual(blocks.getHeader(headerHash)?.materialize(), block.header.materialize());
|
|
55
|
+
assert.deepStrictEqual(blocks.getExtrinsic(headerHash)?.materialize(), block.extrinsic.materialize());
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
await blocks.close();
|
|
59
|
+
await root.close();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
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 { SerializedState, type StateEntries } from "#@typeberry/state-merkleization";
|
|
7
|
+
import { OK, Result } from "#@typeberry/utils";
|
|
8
|
+
import { type FjallRootOptions, type Partition } from "./root.js";
|
|
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 declare class FjallValuesSession {
|
|
20
|
+
private readonly root;
|
|
21
|
+
/** Shared content-addressed values partition, reused across resets. */
|
|
22
|
+
readonly values: Partition;
|
|
23
|
+
private constructor();
|
|
24
|
+
/** Open (or create) the keyspace at `dbPath` and its `values` partition. */
|
|
25
|
+
static open(dbPath: string, options?: FjallRootOptions): Promise<FjallValuesSession>;
|
|
26
|
+
/** Flush the journal to disk (a no-op for ephemeral keyspaces). */
|
|
27
|
+
persist(): Promise<void>;
|
|
28
|
+
/** Size of the keyspace directory on disk, in bytes. */
|
|
29
|
+
sizeInBytes(): number | null;
|
|
30
|
+
/** Release the keyspace handle (skips the sync-all fsync when ephemeral). */
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Hybrid serialized-states db (fjall variant).
|
|
35
|
+
*
|
|
36
|
+
* States (leafs) are kept in memory, only the large values go to fjall on disk.
|
|
37
|
+
* Reads hit fjall directly, which keeps its own bounded block cache. Meant for
|
|
38
|
+
* long fuzzing, used together with pruning so the heap stays bounded.
|
|
39
|
+
*
|
|
40
|
+
* Construction is async, and value writes are flushed explicitly, because fjall
|
|
41
|
+
* has no transaction primitive.
|
|
42
|
+
*
|
|
43
|
+
* Values that no longer belong to any surviving state are removed from fjall,
|
|
44
|
+
* decided by in-memory refcounting (`ValueRefs`) driven by the importer's
|
|
45
|
+
* finality signal. Counts are not persisted: this db cannot resume from disk
|
|
46
|
+
* anyway (the leaf sets live in memory), so values left over by a previous run
|
|
47
|
+
* are never collected. An instance backed by a shared session (fuzz reset
|
|
48
|
+
* reuse) only ever prunes values it inserted itself, since its refcounts start
|
|
49
|
+
* empty - values left behind by earlier resets stay untouched.
|
|
50
|
+
*/
|
|
51
|
+
export declare class HybridSerializedStates implements StatesDb<SerializedState<LeafDb>>, InitStatesDb<StateEntries> {
|
|
52
|
+
private readonly spec;
|
|
53
|
+
private readonly blake2b;
|
|
54
|
+
private readonly session;
|
|
55
|
+
/** Whether `close()` should close the underlying session. */
|
|
56
|
+
private readonly ownsSession;
|
|
57
|
+
static new({ spec, blake2b, dbPath, ephemeral, cacheSizeBytes, }: {
|
|
58
|
+
spec: ChainSpec;
|
|
59
|
+
blake2b: Blake2b;
|
|
60
|
+
dbPath: string;
|
|
61
|
+
ephemeral?: boolean;
|
|
62
|
+
cacheSizeBytes?: number;
|
|
63
|
+
}): Promise<HybridSerializedStates>;
|
|
64
|
+
/**
|
|
65
|
+
* Wrap an already-open `FjallValuesSession` and reuse its keyspace.
|
|
66
|
+
*
|
|
67
|
+
* The new instance starts with its own empty in-memory leaf sets but shares
|
|
68
|
+
* the values partition on disk. Its `close()` does not close the session, the
|
|
69
|
+
* session owner closes it once. The fuzz target uses this to keep one keyspace
|
|
70
|
+
* across resets and only rebuild the in-memory state for each vector.
|
|
71
|
+
*/
|
|
72
|
+
static fromSession(spec: ChainSpec, blake2b: Blake2b, session: FjallValuesSession): HybridSerializedStates;
|
|
73
|
+
private readonly inMemStates;
|
|
74
|
+
private readonly valuesDb;
|
|
75
|
+
/** Shared content-addressed values partition (owned by `session`). */
|
|
76
|
+
private readonly values;
|
|
77
|
+
private readonly refsStore;
|
|
78
|
+
private readonly refs;
|
|
79
|
+
private pendingCleanup;
|
|
80
|
+
private constructor();
|
|
81
|
+
insertInitialState(headerHash: HeaderHash, entries: StateEntries): Promise<Result<OK, StateUpdateError>>;
|
|
82
|
+
updateAndSetState(header: HeaderHash, state: SerializedState<LeafDb>, update: Partial<State & ServicesUpdate>): Promise<Result<OK, StateUpdateError>>;
|
|
83
|
+
getStateRoot(state: SerializedState<LeafDb>): Promise<StateRootHash>;
|
|
84
|
+
getState(header: HeaderHash): SerializedState<LeafDb> | null;
|
|
85
|
+
commitFinalized(headers: HeaderHash[]): void;
|
|
86
|
+
markUnused(header: HeaderHash): void;
|
|
87
|
+
diskSizeInBytes(): number | null;
|
|
88
|
+
/** Apply a refcounting update and remove values that lost their last reference. */
|
|
89
|
+
private applyRefs;
|
|
90
|
+
close(): Promise<void>;
|
|
91
|
+
/** Write new large values to fjall in a single batch, then flush. */
|
|
92
|
+
private writeValues;
|
|
93
|
+
/** Read a value from fjall. */
|
|
94
|
+
private readValue;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=hybrid-states.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hybrid-states.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-fjall/hybrid-states.ts"],"names":[],"mappings":"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"}
|