@typeberry/lib 0.9.0-59ce083 → 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.
- 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/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/hybrid-states.d.ts +14 -0
- package/packages/jam/database-fjall/hybrid-states.d.ts.map +1 -1
- package/packages/jam/database-fjall/hybrid-states.js +44 -4
- 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/package.json
CHANGED
|
@@ -56,7 +56,7 @@ export declare class SortedArray<V> implements ImmutableSortedArray<V> {
|
|
|
56
56
|
findIndex(v: V): number;
|
|
57
57
|
findExact(v: V): V | undefined;
|
|
58
58
|
/** Remove one matching element from the collection. */
|
|
59
|
-
removeOne(v: V):
|
|
59
|
+
removeOne(v: V): V | undefined;
|
|
60
60
|
has(v: V): boolean;
|
|
61
61
|
get length(): number;
|
|
62
62
|
/** Return a regular array that's a copy of this one. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sorted-array.d.ts","sourceRoot":"","sources":["../../../../../packages/core/collections/sorted-array.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGtD,MAAM,WAAW,oBAAoB,CAAC,CAAC,CAAE,SAAQ,QAAQ,CAAC,CAAC,CAAC;IAC1D,wBAAwB;IACxB,IAAI,KAAK,IAAI,SAAS,CAAC,EAAE,CAAC;IAC1B,6BAA6B;IAC7B,IAAI,UAAU,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IAEhC;;;OAGG;IACH,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC;IAExB;;;;;;;OAOG;IACH,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IAE/B,qDAAqD;IACrD,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC;IAEnB,+CAA+C;IAC/C,IAAI,MAAM,IAAI,MAAM,CAAC;IAErB,wDAAwD;IACxD,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC;CAC1C;AAED;;;;;;;GAOG;AACH,qBAAa,WAAW,CAAC,CAAC,CAAE,YAAW,oBAAoB,CAAC,CAAC,CAAC;aAgC1C,KAAK,EAAE,CAAC,EAAE;aACV,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC;IAhC3C;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,GAAE,SAAS,CAAC,EAAO;IAMvE;;OAEG;IACH,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,GAAE,SAAS,CAAC,EAAO;IAkB7E,SAAS,aACS,KAAK,EAAE,CAAC,EAAE,EACV,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC;IAG3C,4CAA4C;IACrC,MAAM,CAAC,CAAC,EAAE,CAAC;IAKlB;;;;OAIG;IACI,GAAG,IAAI,CAAC,GAAG,SAAS;IAIpB,SAAS,CAAC,CAAC,EAAE,CAAC;IASd,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IASrC,uDAAuD;IAChD,SAAS,CAAC,CAAC,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"sorted-array.d.ts","sourceRoot":"","sources":["../../../../../packages/core/collections/sorted-array.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGtD,MAAM,WAAW,oBAAoB,CAAC,CAAC,CAAE,SAAQ,QAAQ,CAAC,CAAC,CAAC;IAC1D,wBAAwB;IACxB,IAAI,KAAK,IAAI,SAAS,CAAC,EAAE,CAAC;IAC1B,6BAA6B;IAC7B,IAAI,UAAU,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IAEhC;;;OAGG;IACH,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC;IAExB;;;;;;;OAOG;IACH,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IAE/B,qDAAqD;IACrD,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC;IAEnB,+CAA+C;IAC/C,IAAI,MAAM,IAAI,MAAM,CAAC;IAErB,wDAAwD;IACxD,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC;CAC1C;AAED;;;;;;;GAOG;AACH,qBAAa,WAAW,CAAC,CAAC,CAAE,YAAW,oBAAoB,CAAC,CAAC,CAAC;aAgC1C,KAAK,EAAE,CAAC,EAAE;aACV,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC;IAhC3C;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,GAAE,SAAS,CAAC,EAAO;IAMvE;;OAEG;IACH,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,GAAE,SAAS,CAAC,EAAO;IAkB7E,SAAS,aACS,KAAK,EAAE,CAAC,EAAE,EACV,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC;IAG3C,4CAA4C;IACrC,MAAM,CAAC,CAAC,EAAE,CAAC;IAKlB;;;;OAIG;IACI,GAAG,IAAI,CAAC,GAAG,SAAS;IAIpB,SAAS,CAAC,CAAC,EAAE,CAAC;IASd,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IASrC,uDAAuD;IAChD,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAU9B,GAAG,CAAC,CAAC,EAAE,CAAC;IAIf,IAAW,MAAM,IAAI,MAAM,CAE1B;IAED,wDAAwD;IACjD,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE;IAI/C,SAAS,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;;;;IA8B3B,4DAA4D;IAC5D,MAAM,CAAC,wBAAwB,CAAC,CAAC,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAqClG,kDAAkD;IACjD,CAAC,MAAM,CAAC,QAAQ,CAAC;CAKnB"}
|
|
@@ -69,9 +69,11 @@ export class SortedArray {
|
|
|
69
69
|
removeOne(v) {
|
|
70
70
|
const findIdx = this.binarySearch(v);
|
|
71
71
|
if (findIdx.isEqual) {
|
|
72
|
-
// remove the element
|
|
73
|
-
this.array.splice(findIdx.idx, 1);
|
|
72
|
+
// remove the element and return the stored one
|
|
73
|
+
const [removed] = this.array.splice(findIdx.idx, 1);
|
|
74
|
+
return removed;
|
|
74
75
|
}
|
|
76
|
+
return undefined;
|
|
75
77
|
}
|
|
76
78
|
has(v) {
|
|
77
79
|
return this.binarySearch(v).isEqual;
|
|
@@ -59,6 +59,16 @@ describe("SortedArray", { timeout: 10 }, () => {
|
|
|
59
59
|
data.removeOne(2);
|
|
60
60
|
assert.deepStrictEqual(data.slice(), [3]);
|
|
61
61
|
});
|
|
62
|
+
it("should return the stored element that was removed", () => {
|
|
63
|
+
const byKey = (self, other) => cmp(self[0], other[0]);
|
|
64
|
+
const data = SortedArray.fromArray(byKey, [
|
|
65
|
+
[1, "a"],
|
|
66
|
+
[2, "b"],
|
|
67
|
+
]);
|
|
68
|
+
const removed = data.removeOne([1, "x"]);
|
|
69
|
+
assert.deepStrictEqual(removed, [1, "a"]);
|
|
70
|
+
assert.strictEqual(data.removeOne([5, "z"]), undefined);
|
|
71
|
+
});
|
|
62
72
|
it("should throw when using fromSortedArray and array is not sorted", () => {
|
|
63
73
|
const data = [1, 3, 2];
|
|
64
74
|
const tryToCreate = () => SortedArray.fromSortedArray(cmp, data);
|
|
@@ -34,7 +34,7 @@ export declare class SortedSet<V> extends SortedArray<V> implements ImmutableSor
|
|
|
34
34
|
*
|
|
35
35
|
* Putting another value that's equal via comparator will replace the current one.
|
|
36
36
|
*/
|
|
37
|
-
replace(v: V):
|
|
37
|
+
replace(v: V): V | undefined;
|
|
38
38
|
/** Create a new SortedSet from two sorted collections. */
|
|
39
39
|
static fromTwoSortedCollections<V>(first: ImmutableSortedArray<V>, second: ImmutableSortedArray<V>): SortedSet<V>;
|
|
40
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sorted-set.d.ts","sourceRoot":"","sources":["../../../../../packages/core/collections/sorted-set.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,KAAK,oBAAoB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE3E,MAAM,WAAW,kBAAkB,CAAC,CAAC,CAAE,SAAQ,oBAAoB,CAAC,CAAC,CAAC;CAAG;AAEzE;;;;;;;GAOG;AACH,qBAAa,SAAS,CAAC,CAAC,CAAE,SAAQ,WAAW,CAAC,CAAC,CAAE,YAAW,kBAAkB,CAAC,CAAC,CAAC;IAC/E;;;OAGG;IACH,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,GAAE,SAAS,CAAC,EAAO;IAkBvE;;;OAGG;IACH,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE;IAaxE;;;;OAIG;IACH,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,GAAE,SAAS,CAAC,EAAO;IAiB7E,wEAAwE;IACjE,MAAM,CAAC,CAAC,EAAE,CAAC;IAOlB;;;;OAIG;IACI,OAAO,CAAC,CAAC,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"sorted-set.d.ts","sourceRoot":"","sources":["../../../../../packages/core/collections/sorted-set.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,KAAK,oBAAoB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE3E,MAAM,WAAW,kBAAkB,CAAC,CAAC,CAAE,SAAQ,oBAAoB,CAAC,CAAC,CAAC;CAAG;AAEzE;;;;;;;GAOG;AACH,qBAAa,SAAS,CAAC,CAAC,CAAE,SAAQ,WAAW,CAAC,CAAC,CAAE,YAAW,kBAAkB,CAAC,CAAC,CAAC;IAC/E;;;OAGG;IACH,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,GAAE,SAAS,CAAC,EAAO;IAkBvE;;;OAGG;IACH,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE;IAaxE;;;;OAIG;IACH,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,GAAE,SAAS,CAAC,EAAO;IAiB7E,wEAAwE;IACjE,MAAM,CAAC,CAAC,EAAE,CAAC;IAOlB;;;;OAIG;IACI,OAAO,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAUnC,0DAA0D;IAC1D,MAAM,CAAC,wBAAwB,CAAC,CAAC,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;CA0BnG"}
|
|
@@ -73,8 +73,12 @@ export class SortedSet extends SortedArray {
|
|
|
73
73
|
*/
|
|
74
74
|
replace(v) {
|
|
75
75
|
const findIdx = this.binarySearch(v);
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
if (findIdx.isEqual) {
|
|
77
|
+
const [displaced] = this.array.splice(findIdx.idx, 1, v);
|
|
78
|
+
return displaced;
|
|
79
|
+
}
|
|
80
|
+
this.array.splice(findIdx.idx, 0, v);
|
|
81
|
+
return undefined;
|
|
78
82
|
}
|
|
79
83
|
/** Create a new SortedSet from two sorted collections. */
|
|
80
84
|
static fromTwoSortedCollections(first, second) {
|
|
@@ -65,6 +65,30 @@ describe("SortedSet", () => {
|
|
|
65
65
|
assert.deepStrictEqual(data.length, 3);
|
|
66
66
|
});
|
|
67
67
|
});
|
|
68
|
+
describe("replace", () => {
|
|
69
|
+
const byKey = (self, other) => cmp(self[0], other[0]);
|
|
70
|
+
it("should return the displaced element when replacing an equal one", () => {
|
|
71
|
+
const data = SortedSet.fromArrayUnique(byKey, [
|
|
72
|
+
[1, "a"],
|
|
73
|
+
[2, "b"],
|
|
74
|
+
]);
|
|
75
|
+
const displaced = data.replace([1, "c"]);
|
|
76
|
+
assert.deepStrictEqual(displaced, [1, "a"]);
|
|
77
|
+
assert.deepStrictEqual(data.slice(), [
|
|
78
|
+
[1, "c"],
|
|
79
|
+
[2, "b"],
|
|
80
|
+
]);
|
|
81
|
+
});
|
|
82
|
+
it("should return undefined when inserting a fresh element", () => {
|
|
83
|
+
const data = SortedSet.fromArrayUnique(byKey, [[1, "a"]]);
|
|
84
|
+
const displaced = data.replace([2, "b"]);
|
|
85
|
+
assert.strictEqual(displaced, undefined);
|
|
86
|
+
assert.deepStrictEqual(data.slice(), [
|
|
87
|
+
[1, "a"],
|
|
88
|
+
[2, "b"],
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
68
92
|
describe("fromTwoSortedCollections", () => {
|
|
69
93
|
it("should merge two sorted sets without duplicates", () => {
|
|
70
94
|
const arr1 = [1, 2, 3];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;AAC1B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;AAC1B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
|
|
@@ -5,6 +5,7 @@ import { StateEntryUpdateAction, type StateKey } from "#@typeberry/state-merklei
|
|
|
5
5
|
import { type LeafNode, type ValueHash } from "#@typeberry/trie";
|
|
6
6
|
export declare function updateLeafs(leafs: SortedSet<LeafNode>, blake2b: Blake2b, data: Iterable<[StateEntryUpdateAction, StateKey | TruncatedHash, BytesBlob]>): {
|
|
7
7
|
values: [ValueHash, BytesBlob][];
|
|
8
|
+
removed: ValueHash[];
|
|
8
9
|
leafs: SortedSet<LeafNode>;
|
|
9
10
|
};
|
|
10
11
|
//# sourceMappingURL=leaf-db-update.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaf-db-update.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database/leaf-db-update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,KAAK,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AACvF,OAAO,EAAgB,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI9E,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,CAAC,QAAQ,CAAC,EAC1B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,QAAQ,CAAC,CAAC,sBAAsB,EAAE,QAAQ,GAAG,aAAa,EAAE,SAAS,CAAC,CAAC,GAC5E;IACD,MAAM,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC;IACjC,KAAK,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;CAC5B,
|
|
1
|
+
{"version":3,"file":"leaf-db-update.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database/leaf-db-update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,KAAK,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AACvF,OAAO,EAAgB,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI9E,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,CAAC,QAAQ,CAAC,EAC1B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,QAAQ,CAAC,CAAC,sBAAsB,EAAE,QAAQ,GAAG,aAAa,EAAE,SAAS,CAAC,CAAC,GAC5E;IACD,MAAM,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC;IACjC,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,KAAK,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;CAC5B,CAyCA"}
|
|
@@ -7,18 +7,34 @@ export function updateLeafs(leafs, blake2b, data) {
|
|
|
7
7
|
const blake2bTrieHasher = getBlake2bTrieHasher(blake2b);
|
|
8
8
|
// We will collect all values that don't fit directly into leaf nodes.
|
|
9
9
|
const values = [];
|
|
10
|
+
// Value hashes that are no longer referenced by the leaf they were stored under.
|
|
11
|
+
// NOTE this does not yet mean they can be removed from the DB - the same value
|
|
12
|
+
// may still be referenced by another leaf or another state. It's up to value
|
|
13
|
+
// ref-counting to decide that.
|
|
14
|
+
const removed = [];
|
|
10
15
|
for (const [action, key, value] of data) {
|
|
11
16
|
if (action === StateEntryUpdateAction.Insert) {
|
|
12
17
|
const leafNode = InMemoryTrie.constructLeaf(blake2bTrieHasher, key.asOpaque(), value);
|
|
13
|
-
leafs.replace(leafNode);
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
const displaced = leafs.replace(leafNode);
|
|
19
|
+
const newHash = leafNode.hasEmbeddedValue() ? null : leafNode.getValueHash();
|
|
20
|
+
const oldHash = displaced !== undefined && !displaced.hasEmbeddedValue() ? displaced.getValueHash() : null;
|
|
21
|
+
// Re-inserting the exact same value under the same key is a no-op for the DB.
|
|
22
|
+
if (newHash !== null && oldHash !== null && newHash.isEqualTo(oldHash)) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (newHash !== null) {
|
|
26
|
+
values.push([newHash, value]);
|
|
27
|
+
}
|
|
28
|
+
if (oldHash !== null) {
|
|
29
|
+
removed.push(oldHash);
|
|
16
30
|
}
|
|
17
31
|
}
|
|
18
32
|
else if (action === StateEntryUpdateAction.Remove) {
|
|
19
33
|
const leafNode = InMemoryTrie.constructLeaf(blake2bTrieHasher, key.asOpaque(), BytesBlob.empty());
|
|
20
|
-
leafs.removeOne(leafNode);
|
|
21
|
-
|
|
34
|
+
const removedLeaf = leafs.removeOne(leafNode);
|
|
35
|
+
if (removedLeaf !== undefined && !removedLeaf.hasEmbeddedValue()) {
|
|
36
|
+
removed.push(removedLeaf.getValueHash());
|
|
37
|
+
}
|
|
22
38
|
}
|
|
23
39
|
else {
|
|
24
40
|
assertNever(action);
|
|
@@ -26,6 +42,7 @@ export function updateLeafs(leafs, blake2b, data) {
|
|
|
26
42
|
}
|
|
27
43
|
return {
|
|
28
44
|
values,
|
|
45
|
+
removed,
|
|
29
46
|
leafs,
|
|
30
47
|
};
|
|
31
48
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leaf-db-update.test.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database/leaf-db-update.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { before, describe, it } from "node:test";
|
|
3
|
+
import { Bytes, BytesBlob } from "#@typeberry/bytes";
|
|
4
|
+
import { SortedSet } from "#@typeberry/collections";
|
|
5
|
+
import { Blake2b, HASH_SIZE } from "#@typeberry/hash";
|
|
6
|
+
import { StateEntryUpdateAction } from "#@typeberry/state-merkleization";
|
|
7
|
+
import { leafComparator } from "#@typeberry/trie";
|
|
8
|
+
import { updateLeafs } from "./leaf-db-update.js";
|
|
9
|
+
let blake2b;
|
|
10
|
+
before(async () => {
|
|
11
|
+
blake2b = await Blake2b.createHasher();
|
|
12
|
+
});
|
|
13
|
+
function key(n) {
|
|
14
|
+
return Bytes.fill(HASH_SIZE, n).asOpaque();
|
|
15
|
+
}
|
|
16
|
+
/** A value larger than 32 bytes, so it does not fit into the leaf and is hash-referenced. */
|
|
17
|
+
function bigValue(b) {
|
|
18
|
+
return Bytes.fill(64, b);
|
|
19
|
+
}
|
|
20
|
+
/** A value that fits directly into the leaf node. */
|
|
21
|
+
function smallValue(b) {
|
|
22
|
+
return Bytes.fill(4, b);
|
|
23
|
+
}
|
|
24
|
+
function emptyLeafs() {
|
|
25
|
+
return SortedSet.fromArray(leafComparator, []);
|
|
26
|
+
}
|
|
27
|
+
function asStrings(hashes) {
|
|
28
|
+
return hashes.map((h) => h.toString());
|
|
29
|
+
}
|
|
30
|
+
describe("updateLeafs", () => {
|
|
31
|
+
it("collects a freshly inserted non-embedded value and removes nothing", () => {
|
|
32
|
+
const leafs = emptyLeafs();
|
|
33
|
+
const res = updateLeafs(leafs, blake2b, [[StateEntryUpdateAction.Insert, key(1), bigValue(0xaa)]]);
|
|
34
|
+
assert.strictEqual(res.values.length, 1);
|
|
35
|
+
assert.deepStrictEqual(res.removed, []);
|
|
36
|
+
});
|
|
37
|
+
it("collects nothing for an embedded value", () => {
|
|
38
|
+
const leafs = emptyLeafs();
|
|
39
|
+
const res = updateLeafs(leafs, blake2b, [[StateEntryUpdateAction.Insert, key(1), smallValue(0xaa)]]);
|
|
40
|
+
assert.deepStrictEqual(res.values, []);
|
|
41
|
+
assert.deepStrictEqual(res.removed, []);
|
|
42
|
+
});
|
|
43
|
+
it("emits the displaced value hash in `removed` when a leaf is replaced", () => {
|
|
44
|
+
const leafs = emptyLeafs();
|
|
45
|
+
const first = updateLeafs(leafs, blake2b, [[StateEntryUpdateAction.Insert, key(1), bigValue(0xaa)]]);
|
|
46
|
+
const oldHash = first.values[0][0];
|
|
47
|
+
const res = updateLeafs(leafs, blake2b, [[StateEntryUpdateAction.Insert, key(1), bigValue(0xbb)]]);
|
|
48
|
+
assert.strictEqual(res.values.length, 1);
|
|
49
|
+
assert.deepStrictEqual(asStrings(res.removed), asStrings([oldHash]));
|
|
50
|
+
});
|
|
51
|
+
it("cancels out when the same value is re-inserted at the same key", () => {
|
|
52
|
+
const leafs = emptyLeafs();
|
|
53
|
+
updateLeafs(leafs, blake2b, [[StateEntryUpdateAction.Insert, key(1), bigValue(0xaa)]]);
|
|
54
|
+
const res = updateLeafs(leafs, blake2b, [[StateEntryUpdateAction.Insert, key(1), bigValue(0xaa)]]);
|
|
55
|
+
assert.deepStrictEqual(res.values, []);
|
|
56
|
+
assert.deepStrictEqual(res.removed, []);
|
|
57
|
+
});
|
|
58
|
+
it("emits the old hash when a non-embedded value becomes embedded", () => {
|
|
59
|
+
const leafs = emptyLeafs();
|
|
60
|
+
const first = updateLeafs(leafs, blake2b, [[StateEntryUpdateAction.Insert, key(1), bigValue(0xaa)]]);
|
|
61
|
+
const oldHash = first.values[0][0];
|
|
62
|
+
const res = updateLeafs(leafs, blake2b, [[StateEntryUpdateAction.Insert, key(1), smallValue(0xbb)]]);
|
|
63
|
+
assert.deepStrictEqual(res.values, []);
|
|
64
|
+
assert.deepStrictEqual(asStrings(res.removed), asStrings([oldHash]));
|
|
65
|
+
});
|
|
66
|
+
it("emits the removed value hash when a non-embedded leaf is removed", () => {
|
|
67
|
+
const leafs = emptyLeafs();
|
|
68
|
+
const first = updateLeafs(leafs, blake2b, [[StateEntryUpdateAction.Insert, key(1), bigValue(0xaa)]]);
|
|
69
|
+
const oldHash = first.values[0][0];
|
|
70
|
+
const res = updateLeafs(leafs, blake2b, [[StateEntryUpdateAction.Remove, key(1), BytesBlob.empty()]]);
|
|
71
|
+
assert.deepStrictEqual(res.values, []);
|
|
72
|
+
assert.deepStrictEqual(asStrings(res.removed), asStrings([oldHash]));
|
|
73
|
+
});
|
|
74
|
+
it("removes nothing when an embedded leaf is removed", () => {
|
|
75
|
+
const leafs = emptyLeafs();
|
|
76
|
+
updateLeafs(leafs, blake2b, [[StateEntryUpdateAction.Insert, key(1), smallValue(0xaa)]]);
|
|
77
|
+
const res = updateLeafs(leafs, blake2b, [[StateEntryUpdateAction.Remove, key(1), BytesBlob.empty()]]);
|
|
78
|
+
assert.deepStrictEqual(res.values, []);
|
|
79
|
+
assert.deepStrictEqual(res.removed, []);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -27,6 +27,7 @@ export declare class InMemorySerializedStates implements StatesDb<SerializedStat
|
|
|
27
27
|
getStateRoot(state: SerializedState<LeafDb>): Promise<StateRootHash>;
|
|
28
28
|
updateAndSetState(header: HeaderHash, state: SerializedState<LeafDb>, update: Partial<State & ServicesUpdate>): Promise<Result<OK, StateUpdateError>>;
|
|
29
29
|
getState(header: HeaderHash): SerializedState<LeafDb> | null;
|
|
30
|
+
commitFinalized(_headers: HeaderHash[]): void;
|
|
30
31
|
markUnused(header: HeaderHash): void;
|
|
31
32
|
close(): Promise<void>;
|
|
32
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serialized-states-db.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database/serialized-states-db.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,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EACL,eAAe,EACf,KAAK,YAAY,EAGlB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE5E,qCAAqC;AACrC,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;AAEhG,sCAAsC;AACtC,qBAAa,wBAAyB,YAAW,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC,YAAY,CAAC;IAc1G,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAd1B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAyE;IAC5F,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8D;WAE1E,GAAG,CAAC,EAAE,SAAS,EAAE,EAAE;QAAE,SAAS,EAAE,SAAS,CAAA;KAAE;IAKxD,MAAM,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE;QAAE,SAAS,EAAE,SAAS,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE;IAIpF,OAAO;IAKD,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAiBxG,YAAY,CAAC,KAAK,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;IAIpE,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;IAsBxC,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,GAAG,IAAI;IAkB5D,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAI9B,KAAK;CACZ"}
|
|
1
|
+
{"version":3,"file":"serialized-states-db.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database/serialized-states-db.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,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EACL,eAAe,EACf,KAAK,YAAY,EAGlB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE5E,qCAAqC;AACrC,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;AAEhG,sCAAsC;AACtC,qBAAa,wBAAyB,YAAW,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC,YAAY,CAAC;IAc1G,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAd1B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAyE;IAC5F,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8D;WAE1E,GAAG,CAAC,EAAE,SAAS,EAAE,EAAE;QAAE,SAAS,EAAE,SAAS,CAAA;KAAE;IAKxD,MAAM,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE;QAAE,SAAS,EAAE,SAAS,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE;IAIpF,OAAO;IAKD,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAiBxG,YAAY,CAAC,KAAK,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;IAIpE,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;IAsBxC,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,GAAG,IAAI;IAkB5D,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,IAAI;IAK7C,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAI9B,KAAK;CACZ"}
|
|
@@ -70,6 +70,10 @@ export class InMemorySerializedStates {
|
|
|
70
70
|
});
|
|
71
71
|
return SerializedState.new(this.spec, this.blake2b, leafDb);
|
|
72
72
|
}
|
|
73
|
+
commitFinalized(_headers) {
|
|
74
|
+
// No value pruning here: this in-memory db keeps every value in a plain map
|
|
75
|
+
// and is not the long-running fuzz target the refcounting is meant for.
|
|
76
|
+
}
|
|
73
77
|
markUnused(header) {
|
|
74
78
|
this.db.delete(header);
|
|
75
79
|
}
|
|
@@ -33,6 +33,16 @@ export interface StatesDb<T extends State = State> {
|
|
|
33
33
|
updateAndSetState(header: HeaderHash, state: T, update: Partial<State & ServicesUpdate>): Promise<Result<OK, StateUpdateError>>;
|
|
34
34
|
/** Retrieve posterior state of given header. */
|
|
35
35
|
getState(header: HeaderHash): T | null;
|
|
36
|
+
/**
|
|
37
|
+
* Notify the backend about newly finalized blocks, in ancestor-first order.
|
|
38
|
+
*
|
|
39
|
+
* Backends that share data between states (e.g. a content-addressed values DB)
|
|
40
|
+
* use this to move the blocks' references from speculative to finalized.
|
|
41
|
+
* MUST be called before `markUnused` of the same finality round, otherwise
|
|
42
|
+
* the newly finalized blocks would be treated as dead forks and values still
|
|
43
|
+
* referenced by the finalized state could be dropped.
|
|
44
|
+
*/
|
|
45
|
+
commitFinalized(headers: HeaderHash[]): void;
|
|
36
46
|
/** Mark state as no longer needed. Backend may remove it asynchronously. */
|
|
37
47
|
markUnused(header: HeaderHash): void;
|
|
38
48
|
/**
|
|
@@ -56,6 +66,7 @@ export declare class InMemoryStates implements StatesDb<InMemoryState> {
|
|
|
56
66
|
/** Insert a full state into the database. */
|
|
57
67
|
insertInitialState(headerHash: HeaderHash, state: InMemoryState): Promise<Result<OK, StateUpdateError>>;
|
|
58
68
|
getState(headerHash: HeaderHash): InMemoryState | null;
|
|
69
|
+
commitFinalized(_headers: HeaderHash[]): void;
|
|
59
70
|
markUnused(header: HeaderHash): void;
|
|
60
71
|
close(): Promise<void>;
|
|
61
72
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"states.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database/states.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAElE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,KAAK,EAAe,MAAM,kBAAkB,CAAC;AAE/F,OAAO,EAAe,EAAE,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE3D,0DAA0D;AAC1D,oBAAY,gBAAgB;IAC1B,oDAAoD;IACpD,QAAQ,IAAI;IACZ,iDAAiD;IACjD,MAAM,IAAI;CACX;AAED,wFAAwF;AACxF,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,KAAK;IACrC,qEAAqE;IACrE,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;CACpG;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK;IAC/C,4CAA4C;IAC5C,YAAY,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAE/C;;;;OAIG;IACH,iBAAiB,CACf,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,CAAC,EACR,MAAM,EAAE,OAAO,CAAC,KAAK,GAAG,cAAc,CAAC,GACtC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAEzC,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC;IAEvC,4EAA4E;IAC5E,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAErC;;;;;OAKG;IACH,eAAe,CAAC,IAAI,MAAM,GAAG,IAAI,CAAC;IAElC,6CAA6C;IAC7C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,qBAAa,cAAe,YAAW,QAAQ,CAAC,aAAa,CAAC;IAQxC,OAAO,CAAC,QAAQ,CAAC,IAAI;IAPzC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAmE;IACtF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAE3C,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS;IAI1B,OAAO;IAID,iBAAiB,CACrB,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,OAAO,CAAC,KAAK,GAAG,cAAc,CAAC,GACtC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAgBlC,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;IAKhE,6CAA6C;IACvC,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAM7G,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,aAAa,GAAG,IAAI;IAStD,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAI9B,KAAK;CACZ"}
|
|
1
|
+
{"version":3,"file":"states.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database/states.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAElE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,KAAK,EAAe,MAAM,kBAAkB,CAAC;AAE/F,OAAO,EAAe,EAAE,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE3D,0DAA0D;AAC1D,oBAAY,gBAAgB;IAC1B,oDAAoD;IACpD,QAAQ,IAAI;IACZ,iDAAiD;IACjD,MAAM,IAAI;CACX;AAED,wFAAwF;AACxF,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,KAAK;IACrC,qEAAqE;IACrE,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;CACpG;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK;IAC/C,4CAA4C;IAC5C,YAAY,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAE/C;;;;OAIG;IACH,iBAAiB,CACf,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,CAAC,EACR,MAAM,EAAE,OAAO,CAAC,KAAK,GAAG,cAAc,CAAC,GACtC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAEzC,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC;IAEvC;;;;;;;;OAQG;IACH,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAE7C,4EAA4E;IAC5E,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAErC;;;;;OAKG;IACH,eAAe,CAAC,IAAI,MAAM,GAAG,IAAI,CAAC;IAElC,6CAA6C;IAC7C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,qBAAa,cAAe,YAAW,QAAQ,CAAC,aAAa,CAAC;IAQxC,OAAO,CAAC,QAAQ,CAAC,IAAI;IAPzC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAmE;IACtF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAE3C,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS;IAI1B,OAAO;IAID,iBAAiB,CACrB,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,OAAO,CAAC,KAAK,GAAG,cAAc,CAAC,GACtC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAgBlC,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;IAKhE,6CAA6C;IACvC,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAM7G,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,aAAa,GAAG,IAAI;IAStD,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,IAAI;IAI7C,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAI9B,KAAK;CACZ"}
|
|
@@ -53,6 +53,9 @@ export class InMemoryStates {
|
|
|
53
53
|
}
|
|
54
54
|
return InMemoryState.copyFrom(this.spec, state, state.intoServicesData());
|
|
55
55
|
}
|
|
56
|
+
commitFinalized(_headers) {
|
|
57
|
+
// nothing to do: every state is a full, independent copy.
|
|
58
|
+
}
|
|
56
59
|
markUnused(header) {
|
|
57
60
|
this.db.delete(header);
|
|
58
61
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { HeaderHash } from "#@typeberry/block";
|
|
2
|
+
import type { ValueHash } from "#@typeberry/trie";
|
|
3
|
+
/** The value hashes a single block introduced and stopped referencing. */
|
|
4
|
+
export interface ValueDelta {
|
|
5
|
+
/** Value hashes (non-embedded) that the block started referencing. */
|
|
6
|
+
inserted: ValueHash[];
|
|
7
|
+
/** Value hashes (non-embedded) that the block stopped referencing. */
|
|
8
|
+
removed: ValueHash[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Synchronous, read-only view of the persisted refcounting state.
|
|
12
|
+
*
|
|
13
|
+
* Both LMDB and fjall offer synchronous reads, so reads can go straight
|
|
14
|
+
* to the backing store. All writes go through `ValueRefsUpdate` batches
|
|
15
|
+
* instead, since persistent backends can only write asynchronously
|
|
16
|
+
* (LMDB transactions, fjall inserts + persist).
|
|
17
|
+
*/
|
|
18
|
+
export interface ValueRefsReader {
|
|
19
|
+
/** How many leaves of the finalized-tip state reference the value. Missing keys read as `0`. */
|
|
20
|
+
getFinalizedCount(hash: ValueHash): number;
|
|
21
|
+
/** How many surviving, not-yet-finalized blocks inserted the value. Missing keys read as `0`. */
|
|
22
|
+
getPendingCount(hash: ValueHash): number;
|
|
23
|
+
/** The value delta of a not-yet-finalized block, if known. */
|
|
24
|
+
getDelta(header: HeaderHash): ValueDelta | undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A batch of refcounting mutations produced by a single `ValueRefs` operation.
|
|
28
|
+
*
|
|
29
|
+
* The backend is responsible for applying the batch using its own write
|
|
30
|
+
* primitive - ideally atomically with the state write that triggered it
|
|
31
|
+
* (one LMDB transaction, one fjall persist).
|
|
32
|
+
*
|
|
33
|
+
* Counts are absolute values rather than increments, so applying the same
|
|
34
|
+
* update more than once (e.g. on crash-replay) is harmless.
|
|
35
|
+
*
|
|
36
|
+
* When atomicity is not available, apply in field order and `removeValues`
|
|
37
|
+
* strictly last: a crash after counts are persisted but before values are
|
|
38
|
+
* removed only leaks values, while the opposite order could drop a value
|
|
39
|
+
* the persisted counts still consider referencd.
|
|
40
|
+
*/
|
|
41
|
+
export interface ValueRefsUpdate {
|
|
42
|
+
/** New absolute finalized counts. Count `0` means the entry should be removed. */
|
|
43
|
+
finalizedCounts: [ValueHash, number][];
|
|
44
|
+
/** New absolute pending counts. Count `0` means the entry should be removed. */
|
|
45
|
+
pendingCounts: [ValueHash, number][];
|
|
46
|
+
/** Deltas of freshly imported blocks to persist. */
|
|
47
|
+
setDeltas: [HeaderHash, ValueDelta][];
|
|
48
|
+
/** Deltas consumed by finalization or fork pruning. */
|
|
49
|
+
removeDeltas: HeaderHash[];
|
|
50
|
+
/** Values that are no longer referenced and can be removed from the values DB. */
|
|
51
|
+
removeValues: ValueHash[];
|
|
52
|
+
}
|
|
53
|
+
/** `true` if applying the update would not change anything. */
|
|
54
|
+
export declare function isEmptyUpdate(update: ValueRefsUpdate): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Decides when a content-addressed value can be removed from the values DB.
|
|
57
|
+
*
|
|
58
|
+
* A value is needed as long as some surviving state references it. Surviving
|
|
59
|
+
* states are the finalized tip plus its unfinalized descendants, so we track:
|
|
60
|
+
* - `finalized`: how many leaves in the current finalized-tip state reference V,
|
|
61
|
+
* advanced strictly by replaying finalized blocks' deltas (never on prune);
|
|
62
|
+
* - `pending`: how many surviving, not-yet-finalized blocks inserted V (an
|
|
63
|
+
* over-approximation of unfinalized references).
|
|
64
|
+
*
|
|
65
|
+
* A value is removable exactly when both counts reach zero.
|
|
66
|
+
*
|
|
67
|
+
* This class only makes decisions: every operation reads the current state
|
|
68
|
+
* through `ValueRefsReader` and returns a `ValueRefsUpdate` describing what
|
|
69
|
+
* should change. Nothing is written here - the owning backend applies the
|
|
70
|
+
* update with whatever consistency guarantees it can provide.
|
|
71
|
+
*/
|
|
72
|
+
export declare class ValueRefs {
|
|
73
|
+
private readonly reader;
|
|
74
|
+
constructor(reader: ValueRefsReader);
|
|
75
|
+
/** Record values referenced by the genesis / initial finalized state. */
|
|
76
|
+
onInitial(inserted: ValueHash[]): ValueRefsUpdate;
|
|
77
|
+
/**
|
|
78
|
+
* Record a freshly imported, not-yet-finalized block.
|
|
79
|
+
*
|
|
80
|
+
* Importing the same header twice is a no-op, otherwise the second import
|
|
81
|
+
* would double-count `pending` references and pin the values forever.
|
|
82
|
+
*/
|
|
83
|
+
onImport(header: HeaderHash, delta: ValueDelta): ValueRefsUpdate;
|
|
84
|
+
/**
|
|
85
|
+
* Apply the value deltas of newly finalized blocks, in finalized (ancestor-first) order.
|
|
86
|
+
*
|
|
87
|
+
* Moves each inserted value from `pending` to `finalized`, drops `finalized`
|
|
88
|
+
* references for removed values, and collects anything that becomes unreferenced.
|
|
89
|
+
*/
|
|
90
|
+
commitFinalized(headers: HeaderHash[]): ValueRefsUpdate;
|
|
91
|
+
/**
|
|
92
|
+
* Release the speculative references of a state being discarded.
|
|
93
|
+
*
|
|
94
|
+
* For a header that was still unfinalized (a dead fork) the returned update
|
|
95
|
+
* releases its inserted values, which may become collectable. For an already
|
|
96
|
+
* finalized state the update is empty (check with `isEmptyUpdate`): its delta
|
|
97
|
+
* was consumed on finality and its values are accounted for in `finalized`.
|
|
98
|
+
*/
|
|
99
|
+
releaseUnfinalized(header: HeaderHash): ValueRefsUpdate;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* In-memory refcounting store, reusable by the in-memory and hybrid states DBs
|
|
103
|
+
* (the hybrids cannot resume from disk anyway, so persisting counts buys nothing).
|
|
104
|
+
*
|
|
105
|
+
* NOTE: `apply` does not touch the values DB - the caller owns it and must
|
|
106
|
+
* handle `update.removeValues` itself.
|
|
107
|
+
*/
|
|
108
|
+
export declare class InMemoryValueRefsStore implements ValueRefsReader {
|
|
109
|
+
private readonly finalized;
|
|
110
|
+
private readonly pending;
|
|
111
|
+
private readonly deltas;
|
|
112
|
+
getFinalizedCount(hash: ValueHash): number;
|
|
113
|
+
getPendingCount(hash: ValueHash): number;
|
|
114
|
+
getDelta(header: HeaderHash): ValueDelta | undefined;
|
|
115
|
+
apply(update: ValueRefsUpdate): void;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=value-refs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"value-refs.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database/value-refs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,0EAA0E;AAC1E,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,sEAAsE;IACtE,OAAO,EAAE,SAAS,EAAE,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,gGAAgG;IAChG,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;IAC3C,iGAAiG;IACjG,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;IACzC,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;CACtD;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,eAAe;IAC9B,kFAAkF;IAClF,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC;IACvC,gFAAgF;IAChF,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC;IACrC,oDAAoD;IACpD,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,CAAC;IACtC,uDAAuD;IACvD,YAAY,EAAE,UAAU,EAAE,CAAC;IAC3B,kFAAkF;IAClF,YAAY,EAAE,SAAS,EAAE,CAAC;CAC3B;AAED,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAQ9D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,SAAS;IACR,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,eAAe;IAEpD,yEAAyE;IACzE,SAAS,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,eAAe;IAQjD;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,eAAe;IAYhE;;;;;OAKG;IACH,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,eAAe;IAoBvD;;;;;;;OAOG;IACH,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,eAAe;CAYxD;AA0ED;;;;;;GAMG;AACH,qBAAa,sBAAuB,YAAW,eAAe;IAC5D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2D;IACrF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2D;IACnF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgE;IAEvF,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM;IAI1C,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM;IAIxC,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS;IAIpD,KAAK,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI;CAUrC"}
|
|
@@ -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
|
+
});
|
|
@@ -39,6 +39,14 @@ export declare class FjallValuesSession {
|
|
|
39
39
|
*
|
|
40
40
|
* Construction is async, and value writes are flushed explicitly, because fjall
|
|
41
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.
|
|
42
50
|
*/
|
|
43
51
|
export declare class HybridSerializedStates implements StatesDb<SerializedState<LeafDb>>, InitStatesDb<StateEntries> {
|
|
44
52
|
private readonly spec;
|
|
@@ -66,13 +74,19 @@ export declare class HybridSerializedStates implements StatesDb<SerializedState<
|
|
|
66
74
|
private readonly valuesDb;
|
|
67
75
|
/** Shared content-addressed values partition (owned by `session`). */
|
|
68
76
|
private readonly values;
|
|
77
|
+
private readonly refsStore;
|
|
78
|
+
private readonly refs;
|
|
79
|
+
private pendingCleanup;
|
|
69
80
|
private constructor();
|
|
70
81
|
insertInitialState(headerHash: HeaderHash, entries: StateEntries): Promise<Result<OK, StateUpdateError>>;
|
|
71
82
|
updateAndSetState(header: HeaderHash, state: SerializedState<LeafDb>, update: Partial<State & ServicesUpdate>): Promise<Result<OK, StateUpdateError>>;
|
|
72
83
|
getStateRoot(state: SerializedState<LeafDb>): Promise<StateRootHash>;
|
|
73
84
|
getState(header: HeaderHash): SerializedState<LeafDb> | null;
|
|
85
|
+
commitFinalized(headers: HeaderHash[]): void;
|
|
74
86
|
markUnused(header: HeaderHash): void;
|
|
75
87
|
diskSizeInBytes(): number | null;
|
|
88
|
+
/** Apply a refcounting update and remove values that lost their last reference. */
|
|
89
|
+
private applyRefs;
|
|
76
90
|
close(): Promise<void>;
|
|
77
91
|
/** Write new large values to fjall in a single batch, then flush. */
|
|
78
92
|
private writeValues;
|
|
@@ -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":"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,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;AAC9C,OAAO,EAAa,KAAK,gBAAgB,EAAE,KAAK,SAAS,EAAgB,MAAM,WAAW,CAAC;AAI3F;;;;;;;;;GASG;AACH,qBAAa,kBAAkB;IAE3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,SAAS;IAH5B,OAAO;IAMP,4EAA4E;WAC/D,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAM9F,mEAAmE;IAC7D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,wDAAwD;IACxD,WAAW,IAAI,MAAM,GAAG,IAAI;IAI5B,6EAA6E;IACvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,sBAAuB,YAAW,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC,YAAY,CAAC;IA0CxG,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,WAAW;WA7CjB,GAAG,CAAC,EACf,IAAI,EACJ,OAAO,EACP,MAAM,EACN,SAAS,EACT,cAAc,GACf,EAAE;QACD,IAAI,EAAE,SAAS,CAAC;QAChB,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAMnC;;;;;;;OAOG;IACH,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,kBAAkB,GAAG,sBAAsB;IAI1G,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyE;IAErG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAC1D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiC;IAEtD,OAAO,CAAC,cAAc,CAAuC;IAE7D,OAAO;IAWD,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,eAAe,IAAI,MAAM,GAAG,IAAI;IAIhC,mFAAmF;IACnF,OAAO,CAAC,SAAS;IAcX,KAAK;IAWX,qEAAqE;YACvD,WAAW;IAoBzB,+BAA+B;IAC/B,OAAO,CAAC,SAAS;CAOlB"}
|
|
@@ -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
|
-
//
|
|
132
|
-
//
|
|
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,
|
|
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
|
-
//
|
|
73
|
-
//
|
|
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
|
}
|