@typeberry/lib 0.11.0-829b7ea → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -1
- package/packages/jam/config-node/node-config.d.ts +11 -1
- package/packages/jam/config-node/node-config.d.ts.map +1 -1
- package/packages/jam/config-node/node-config.js +25 -3
- package/packages/jam/config-node/node-config.test.js +7 -3
- package/packages/jam/database/states.d.ts +8 -1
- package/packages/jam/database/states.d.ts.map +1 -1
- package/packages/jam/database/value-refs.d.ts +5 -4
- package/packages/jam/database/value-refs.d.ts.map +1 -1
- package/packages/jam/database-lmdb/blocks.d.ts +24 -0
- package/packages/jam/database-lmdb/blocks.d.ts.map +1 -0
- package/packages/jam/database-lmdb/blocks.js +82 -0
- package/packages/jam/database-lmdb/hybrid-states.d.ts +56 -0
- package/packages/jam/database-lmdb/hybrid-states.d.ts.map +1 -0
- package/packages/jam/database-lmdb/hybrid-states.js +149 -0
- package/packages/jam/database-lmdb/hybrid-states.test.d.ts +2 -0
- package/packages/jam/database-lmdb/hybrid-states.test.d.ts.map +1 -0
- package/packages/jam/database-lmdb/hybrid-states.test.js +180 -0
- package/packages/jam/database-lmdb/index.d.ts +5 -0
- package/packages/jam/database-lmdb/index.d.ts.map +1 -0
- package/packages/jam/database-lmdb/index.js +4 -0
- package/packages/jam/database-lmdb/root.d.ts +26 -0
- package/packages/jam/database-lmdb/root.d.ts.map +1 -0
- package/packages/jam/database-lmdb/root.js +53 -0
- package/packages/jam/database-lmdb/states.d.ts +76 -0
- package/packages/jam/database-lmdb/states.d.ts.map +1 -0
- package/packages/jam/database-lmdb/states.js +145 -0
- package/packages/jam/database-lmdb/states.test.d.ts +2 -0
- package/packages/jam/database-lmdb/states.test.d.ts.map +1 -0
- package/packages/jam/database-lmdb/states.test.js +202 -0
- package/packages/jam/node/export.d.ts.map +1 -1
- package/packages/jam/node/export.js +17 -8
- package/packages/jam/node/main-fuzz.d.ts.map +1 -1
- package/packages/jam/node/main-fuzz.js +5 -3
- package/packages/jam/node/main-importer.d.ts +6 -2
- package/packages/jam/node/main-importer.d.ts.map +1 -1
- package/packages/jam/node/main-importer.js +27 -12
- package/packages/jam/node/main.d.ts.map +1 -1
- package/packages/jam/node/main.js +16 -6
- package/packages/jam/rpc/test/e2e-server.d.ts.map +1 -1
- package/packages/jam/rpc/test/e2e-server.js +5 -3
- package/packages/jam/rpc/test/e2e.js +1 -1
- package/packages/jam/state-merkleization/index.d.ts +1 -1
- package/packages/jam/state-merkleization/index.js +1 -1
- package/packages/workers/api-node/config.d.ts +40 -6
- package/packages/workers/api-node/config.d.ts.map +1 -1
- package/packages/workers/api-node/config.js +82 -6
- package/packages/workers/api-node/config.test.js +63 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typeberry/lib",
|
|
3
|
-
"version": "0.11.0
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Typeberry Library",
|
|
5
5
|
"main": "./bin/lib/index.js",
|
|
6
6
|
"types": "./bin/lib/index.d.ts",
|
|
@@ -214,6 +214,8 @@
|
|
|
214
214
|
"#@typeberry/database/*": "./packages/jam/database/*",
|
|
215
215
|
"#@typeberry/database-fjall": "./packages/jam/database-fjall/index.js",
|
|
216
216
|
"#@typeberry/database-fjall/*": "./packages/jam/database-fjall/*",
|
|
217
|
+
"#@typeberry/database-lmdb": "./packages/jam/database-lmdb/index.js",
|
|
218
|
+
"#@typeberry/database-lmdb/*": "./packages/jam/database-lmdb/*",
|
|
217
219
|
"#@typeberry/executor": "./packages/jam/executor/index.js",
|
|
218
220
|
"#@typeberry/executor/*": "./packages/jam/executor/*",
|
|
219
221
|
"#@typeberry/fuzz-proto": "./packages/jam/fuzz-proto/index.js",
|
|
@@ -20,13 +20,21 @@ export declare enum KnownChainSpec {
|
|
|
20
20
|
/** Full chain spec. */
|
|
21
21
|
Full = "full"
|
|
22
22
|
}
|
|
23
|
+
/** Persistent regular-node database backend. */
|
|
24
|
+
export declare enum RegularStateBackend {
|
|
25
|
+
/** @deprecated lmdb remains available as an explicit fallback, but fjall is the default backend. */
|
|
26
|
+
Lmdb = "lmdb",
|
|
27
|
+
Fjall = "fjall"
|
|
28
|
+
}
|
|
23
29
|
export declare const knownChainSpecFromJson: FromJson<KnownChainSpec>;
|
|
30
|
+
export declare const regularStateBackendFromJson: FromJson<RegularStateBackend>;
|
|
24
31
|
type NodeConfigurationJson = {
|
|
25
32
|
$schema: string;
|
|
26
33
|
version: number;
|
|
27
34
|
flavor: KnownChainSpec;
|
|
28
35
|
chain_spec: JipChainSpec;
|
|
29
36
|
database_base_path?: string;
|
|
37
|
+
state_backend?: RegularStateBackend;
|
|
30
38
|
authorship: AuthorshipOptions;
|
|
31
39
|
rpc?: RpcOptions;
|
|
32
40
|
};
|
|
@@ -37,11 +45,13 @@ export declare class NodeConfiguration {
|
|
|
37
45
|
readonly chainSpec: JipChainSpec;
|
|
38
46
|
/** If database path is not provided, we load an in-memory db. */
|
|
39
47
|
readonly databaseBasePath: string | undefined;
|
|
48
|
+
/** Persistent database backend used when `databaseBasePath` is set. */
|
|
49
|
+
readonly stateBackend: RegularStateBackend;
|
|
40
50
|
readonly authorship: AuthorshipOptions;
|
|
41
51
|
/** Optional RPC server configuration. When present, an in-process RPC server is started. */
|
|
42
52
|
readonly rpc: RpcOptions | undefined;
|
|
43
53
|
static fromJson: import("@typeberry/json-parser").FromJsonWithParser<unknown, NodeConfiguration>;
|
|
44
|
-
static new({ $schema, version, flavor, chain_spec, database_base_path, authorship, rpc }: NodeConfigurationJson): NodeConfiguration;
|
|
54
|
+
static new({ $schema, version, flavor, chain_spec, database_base_path, state_backend, authorship, rpc, }: NodeConfigurationJson): NodeConfiguration;
|
|
45
55
|
private constructor();
|
|
46
56
|
}
|
|
47
57
|
export declare function loadConfig(config: string[], withRelPath: (p: string) => string): NodeConfiguration;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-config.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/config-node/node-config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,OAAO,EAAE,KAAK,QAAQ,EAAuB,MAAM,wBAAwB,CAAC;AAG5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAItC,+DAA+D;AAC/D,eAAO,MAAM,eAAe,QAAQ,CAAC;AACrC,eAAO,MAAM,eAAe,aAAa,CAAC;AAE1C,2BAA2B;AAC3B,eAAO,MAAM,cAAc,YAAY,CAAC;AAExC,eAAO,MAAM,aAAa;;;;CAIzB,CAAC;AAEF,0BAA0B;AAC1B,oBAAY,cAAc;IACxB,uBAAuB;IACvB,IAAI,SAAS;IACb,uBAAuB;IACvB,IAAI,SAAS;CACd;AAED,eAAO,MAAM,sBAAsB,EAS7B,QAAQ,CAAC,cAAc,CAAC,CAAC;AAE/B,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,cAAc,CAAC;IACvB,UAAU,EAAE,YAAY,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,GAAG,CAAC,EAAE,UAAU,CAAC;CAClB,CAAC;AAEF,qBAAa,iBAAiB;
|
|
1
|
+
{"version":3,"file":"node-config.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/config-node/node-config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,OAAO,EAAE,KAAK,QAAQ,EAAuB,MAAM,wBAAwB,CAAC;AAG5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAItC,+DAA+D;AAC/D,eAAO,MAAM,eAAe,QAAQ,CAAC;AACrC,eAAO,MAAM,eAAe,aAAa,CAAC;AAE1C,2BAA2B;AAC3B,eAAO,MAAM,cAAc,YAAY,CAAC;AAExC,eAAO,MAAM,aAAa;;;;CAIzB,CAAC;AAEF,0BAA0B;AAC1B,oBAAY,cAAc;IACxB,uBAAuB;IACvB,IAAI,SAAS;IACb,uBAAuB;IACvB,IAAI,SAAS;CACd;AAED,gDAAgD;AAChD,oBAAY,mBAAmB;IAC7B,oGAAoG;IACpG,IAAI,SAAS;IACb,KAAK,UAAU;CAChB;AAED,eAAO,MAAM,sBAAsB,EAS7B,QAAQ,CAAC,cAAc,CAAC,CAAC;AAE/B,eAAO,MAAM,2BAA2B,EASlC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;AAEpC,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,cAAc,CAAC;IACvB,UAAU,EAAE,YAAY,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,UAAU,EAAE,iBAAiB,CAAC;IAC9B,GAAG,CAAC,EAAE,UAAU,CAAC;CAClB,CAAC;AAEF,qBAAa,iBAAiB;aAyCV,OAAO,EAAE,MAAM;aACf,OAAO,EAAE,MAAM;aACf,MAAM,EAAE,cAAc;aACtB,SAAS,EAAE,YAAY;IACvC,iEAAiE;aACjD,gBAAgB,EAAE,MAAM,GAAG,SAAS;IACpD,uEAAuE;aACvD,YAAY,EAAE,mBAAmB;aACjC,UAAU,EAAE,iBAAiB;IAC7C,4FAA4F;aAC5E,GAAG,EAAE,UAAU,GAAG,SAAS;IAlD7C,MAAM,CAAC,QAAQ,kFAYb;IAEF,MAAM,CAAC,GAAG,CAAC,EACT,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,GAAG,GACJ,EAAE,qBAAqB;IAgBxB,OAAO;CAaR;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,iBAAiB,CAuDlG"}
|
|
@@ -27,6 +27,13 @@ export var KnownChainSpec;
|
|
|
27
27
|
/** Full chain spec. */
|
|
28
28
|
KnownChainSpec["Full"] = "full";
|
|
29
29
|
})(KnownChainSpec || (KnownChainSpec = {}));
|
|
30
|
+
/** Persistent regular-node database backend. */
|
|
31
|
+
export var RegularStateBackend;
|
|
32
|
+
(function (RegularStateBackend) {
|
|
33
|
+
/** @deprecated lmdb remains available as an explicit fallback, but fjall is the default backend. */
|
|
34
|
+
RegularStateBackend["Lmdb"] = "lmdb";
|
|
35
|
+
RegularStateBackend["Fjall"] = "fjall";
|
|
36
|
+
})(RegularStateBackend || (RegularStateBackend = {}));
|
|
30
37
|
export const knownChainSpecFromJson = json.fromString((input, ctx) => {
|
|
31
38
|
switch (input) {
|
|
32
39
|
case KnownChainSpec.Tiny:
|
|
@@ -37,12 +44,23 @@ export const knownChainSpecFromJson = json.fromString((input, ctx) => {
|
|
|
37
44
|
throw Error(`unknown network flavor: ${input} at ${ctx}`);
|
|
38
45
|
}
|
|
39
46
|
});
|
|
47
|
+
export const regularStateBackendFromJson = json.fromString((input, ctx) => {
|
|
48
|
+
switch (input) {
|
|
49
|
+
case RegularStateBackend.Lmdb:
|
|
50
|
+
return RegularStateBackend.Lmdb;
|
|
51
|
+
case RegularStateBackend.Fjall:
|
|
52
|
+
return RegularStateBackend.Fjall;
|
|
53
|
+
default:
|
|
54
|
+
throw Error(`unknown state backend: ${input} at ${ctx}`);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
40
57
|
export class NodeConfiguration {
|
|
41
58
|
$schema;
|
|
42
59
|
version;
|
|
43
60
|
flavor;
|
|
44
61
|
chainSpec;
|
|
45
62
|
databaseBasePath;
|
|
63
|
+
stateBackend;
|
|
46
64
|
authorship;
|
|
47
65
|
rpc;
|
|
48
66
|
static fromJson = json.object({
|
|
@@ -51,18 +69,21 @@ export class NodeConfiguration {
|
|
|
51
69
|
flavor: knownChainSpecFromJson,
|
|
52
70
|
chain_spec: JipChainSpec.fromJson,
|
|
53
71
|
database_base_path: json.optional("string"),
|
|
72
|
+
state_backend: json.optional(regularStateBackendFromJson),
|
|
54
73
|
authorship: AuthorshipOptions.fromJson,
|
|
55
74
|
rpc: json.optional(RpcOptions.fromJson),
|
|
56
75
|
}, NodeConfiguration.new);
|
|
57
|
-
static new({ $schema, version, flavor, chain_spec, database_base_path, authorship, rpc }) {
|
|
76
|
+
static new({ $schema, version, flavor, chain_spec, database_base_path, state_backend, authorship, rpc, }) {
|
|
58
77
|
if (version !== 1) {
|
|
59
78
|
throw new Error("Only version=1 config is supported.");
|
|
60
79
|
}
|
|
61
|
-
return new NodeConfiguration($schema, version, flavor, chain_spec, database_base_path ?? undefined, authorship, rpc ?? undefined);
|
|
80
|
+
return new NodeConfiguration($schema, version, flavor, chain_spec, database_base_path ?? undefined, state_backend ?? RegularStateBackend.Fjall, authorship, rpc ?? undefined);
|
|
62
81
|
}
|
|
63
82
|
constructor($schema, version, flavor, chainSpec,
|
|
64
83
|
/** If database path is not provided, we load an in-memory db. */
|
|
65
|
-
databaseBasePath,
|
|
84
|
+
databaseBasePath,
|
|
85
|
+
/** Persistent database backend used when `databaseBasePath` is set. */
|
|
86
|
+
stateBackend, authorship,
|
|
66
87
|
/** Optional RPC server configuration. When present, an in-process RPC server is started. */
|
|
67
88
|
rpc) {
|
|
68
89
|
this.$schema = $schema;
|
|
@@ -70,6 +91,7 @@ export class NodeConfiguration {
|
|
|
70
91
|
this.flavor = flavor;
|
|
71
92
|
this.chainSpec = chainSpec;
|
|
72
93
|
this.databaseBasePath = databaseBasePath;
|
|
94
|
+
this.stateBackend = stateBackend;
|
|
73
95
|
this.authorship = authorship;
|
|
74
96
|
this.rpc = rpc;
|
|
75
97
|
}
|
|
@@ -5,7 +5,7 @@ import { configs } from "#@typeberry/configs";
|
|
|
5
5
|
import polkajamConfig from "#@typeberry/configs/typeberry-polkajam-dev.json" with { type: "json" };
|
|
6
6
|
import { parseFromJson } from "#@typeberry/json-parser";
|
|
7
7
|
import { workspacePathFix } from "#@typeberry/utils";
|
|
8
|
-
import { KnownChainSpec, loadConfig, NodeConfiguration } from "./node-config.js";
|
|
8
|
+
import { KnownChainSpec, loadConfig, NodeConfiguration, RegularStateBackend } from "./node-config.js";
|
|
9
9
|
import { RpcOptions } from "./rpc.js";
|
|
10
10
|
const withRelPath = workspacePathFix(`${import.meta.dirname}/../..`);
|
|
11
11
|
const NODE_CONFIG_TEST = {
|
|
@@ -41,6 +41,9 @@ describe("Importing Node Configuration", () => {
|
|
|
41
41
|
it("should read the database base path", () => {
|
|
42
42
|
assert.deepStrictEqual(config.databaseBasePath, "/tmp/jam-node-db");
|
|
43
43
|
});
|
|
44
|
+
it("defaults to the fjall state backend", () => {
|
|
45
|
+
assert.deepStrictEqual(config.stateBackend, RegularStateBackend.Fjall);
|
|
46
|
+
});
|
|
44
47
|
it("defaults to no rpc config", () => {
|
|
45
48
|
assert.deepStrictEqual(config.rpc, undefined);
|
|
46
49
|
});
|
|
@@ -48,8 +51,9 @@ describe("Importing Node Configuration", () => {
|
|
|
48
51
|
const withRpc = parseFromJson({ ...NODE_CONFIG_TEST, rpc: { port: 9999 } }, NodeConfiguration.fromJson);
|
|
49
52
|
assert.deepStrictEqual(withRpc.rpc, RpcOptions.new({ port: 9999 }));
|
|
50
53
|
});
|
|
51
|
-
it("should
|
|
52
|
-
|
|
54
|
+
it("should read the state backend", () => {
|
|
55
|
+
const lmdbConfig = parseFromJson({ ...NODE_CONFIG_TEST, state_backend: "lmdb" }, NodeConfiguration.fromJson);
|
|
56
|
+
assert.deepStrictEqual(lmdbConfig.stateBackend, RegularStateBackend.Lmdb);
|
|
53
57
|
});
|
|
54
58
|
it("should read the chain spec", () => {
|
|
55
59
|
assert.deepStrictEqual(config.chainSpec.id, "testnet");
|
|
@@ -14,7 +14,14 @@ export interface InitStatesDb<T = State> {
|
|
|
14
14
|
/** Insert a pre-defined initial state directly into the database. */
|
|
15
15
|
insertInitialState(headerHash: HeaderHash, initialState: T): Promise<Result<OK, StateUpdateError>>;
|
|
16
16
|
}
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Interface for accessing states stored in the database.
|
|
19
|
+
*
|
|
20
|
+
* NOTE that the design of this interface is heavily influenced
|
|
21
|
+
* by the LMDB implementation, so that we can implement it efficiently.
|
|
22
|
+
*
|
|
23
|
+
* See the documentation there for more detailed reasoning.
|
|
24
|
+
*/
|
|
18
25
|
export interface StatesDb<T extends State = State> {
|
|
19
26
|
/** Compute a state root for given state. */
|
|
20
27
|
getStateRoot(state: T): Promise<StateRootHash>;
|
|
@@ -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
|
|
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"}
|
|
@@ -10,9 +10,10 @@ export interface ValueDelta {
|
|
|
10
10
|
/**
|
|
11
11
|
* Synchronous, read-only view of the persisted refcounting state.
|
|
12
12
|
*
|
|
13
|
-
* fjall
|
|
14
|
-
* store. All writes go through `ValueRefsUpdate` batches
|
|
15
|
-
* persistent backends can only write asynchronously
|
|
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).
|
|
16
17
|
*/
|
|
17
18
|
export interface ValueRefsReader {
|
|
18
19
|
/** How many leaves of the finalized-tip state reference the value. Missing keys read as `0`. */
|
|
@@ -27,7 +28,7 @@ export interface ValueRefsReader {
|
|
|
27
28
|
*
|
|
28
29
|
* The backend is responsible for applying the batch using its own write
|
|
29
30
|
* primitive - ideally atomically with the state write that triggered it
|
|
30
|
-
* (one fjall persist).
|
|
31
|
+
* (one LMDB transaction, one fjall persist).
|
|
31
32
|
*
|
|
32
33
|
* Counts are absolute values rather than increments, so applying the same
|
|
33
34
|
* update more than once (e.g. on crash-replay) is harmless.
|
|
@@ -1 +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
|
|
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,24 @@
|
|
|
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 { LmdbRoot } from "./root.js";
|
|
6
|
+
export declare class LmdbBlocks implements BlocksDb {
|
|
7
|
+
private readonly chainSpec;
|
|
8
|
+
private readonly root;
|
|
9
|
+
private readonly extrinsics;
|
|
10
|
+
private readonly headers;
|
|
11
|
+
private readonly postStateRoots;
|
|
12
|
+
static new(chainSpec: ChainSpec, root: LmdbRoot): LmdbBlocks;
|
|
13
|
+
private constructor();
|
|
14
|
+
setPostStateRoot(hash: HeaderHash, postStateRoot: StateRootHash): Promise<void>;
|
|
15
|
+
getPostStateRoot(hash: HeaderHash): StateRootHash | null;
|
|
16
|
+
insertBlock(block: WithHash<HeaderHash, BlockView>): Promise<void>;
|
|
17
|
+
setBestHeaderHash(hash: HeaderHash): Promise<void>;
|
|
18
|
+
getBestHeaderHash(): HeaderHash;
|
|
19
|
+
getHeader(hash: HeaderHash): HeaderView | null;
|
|
20
|
+
getExtrinsic(hash: HeaderHash): ExtrinsicView | null;
|
|
21
|
+
markUnused(hash: HeaderHash): void;
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=blocks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-lmdb/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;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAS,MAAM,WAAW,CAAC;AAQjD,qBAAa,UAAW,YAAW,QAAQ;IAUvC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAVvB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IAEvC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ;IAI/C,OAAO;IAeD,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;IASlE,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxD,iBAAiB,IAAI,UAAU;IAS/B,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI;IAS9C,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,aAAa,GAAG,IAAI;IAQpD,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAM5B,KAAK;CAGZ"}
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
const BEST_BLOCK = "best hash and posterior state root";
|
|
6
|
+
// TODO [ToDr] consider having a changeset for transactions,
|
|
7
|
+
// where we store all `insert ++ key ++ value` and `remove ++ key`
|
|
8
|
+
// in a single `Uint8Array` JAM-encoded. That could then
|
|
9
|
+
// be efficiently transferred between threads.
|
|
10
|
+
export class LmdbBlocks {
|
|
11
|
+
chainSpec;
|
|
12
|
+
root;
|
|
13
|
+
extrinsics;
|
|
14
|
+
headers;
|
|
15
|
+
postStateRoots;
|
|
16
|
+
static new(chainSpec, root) {
|
|
17
|
+
return new LmdbBlocks(chainSpec, root);
|
|
18
|
+
}
|
|
19
|
+
constructor(chainSpec, root) {
|
|
20
|
+
this.chainSpec = chainSpec;
|
|
21
|
+
this.root = root;
|
|
22
|
+
// NOTE [ToDr] Extrinsics are stored under header hash, not their hash. Revise if it's an issue.
|
|
23
|
+
this.extrinsics = this.root.subDb("extrinsics");
|
|
24
|
+
this.headers = this.root.subDb("headers");
|
|
25
|
+
// NOTE [ToDr] We currently store all posterior state roots, however it's
|
|
26
|
+
// most likely very redundant. We probably only need to store the posterior
|
|
27
|
+
// state roots of recent blocks to be able to quickly resolve forks
|
|
28
|
+
// OR we need a way to be able to traverse the blocks history forward
|
|
29
|
+
// (i.e. know what next block(s) is).
|
|
30
|
+
this.postStateRoots = this.root.subDb("postStateRoots");
|
|
31
|
+
}
|
|
32
|
+
async setPostStateRoot(hash, postStateRoot) {
|
|
33
|
+
await this.postStateRoots.put(hash.raw, postStateRoot.raw);
|
|
34
|
+
}
|
|
35
|
+
getPostStateRoot(hash) {
|
|
36
|
+
const postStateRoot = this.postStateRoots.get(hash.raw);
|
|
37
|
+
if (postStateRoot === undefined) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return Bytes.fromBlob(postStateRoot, HASH_SIZE).asOpaque();
|
|
41
|
+
}
|
|
42
|
+
async insertBlock(block) {
|
|
43
|
+
const header = block.data.header.view().encoded();
|
|
44
|
+
const extrinsic = block.data.extrinsic.view().encoded();
|
|
45
|
+
await this.root.db.transaction(() => {
|
|
46
|
+
this.headers.put(block.hash.raw, header.raw);
|
|
47
|
+
this.extrinsics.put(block.hash.raw, extrinsic.raw);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async setBestHeaderHash(hash) {
|
|
51
|
+
await this.root.db.put(BEST_BLOCK, hash.raw);
|
|
52
|
+
}
|
|
53
|
+
getBestHeaderHash() {
|
|
54
|
+
const bestHeaderHash = this.root.db.get(BEST_BLOCK);
|
|
55
|
+
if (bestHeaderHash === undefined) {
|
|
56
|
+
return Bytes.zero(HASH_SIZE).asOpaque();
|
|
57
|
+
}
|
|
58
|
+
return Bytes.fromBlob(bestHeaderHash, HASH_SIZE).asOpaque();
|
|
59
|
+
}
|
|
60
|
+
getHeader(hash) {
|
|
61
|
+
const data = this.headers.get(hash.raw);
|
|
62
|
+
if (data === undefined) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return Decoder.decodeObject(Header.Codec.View, data, this.chainSpec);
|
|
66
|
+
}
|
|
67
|
+
getExtrinsic(hash) {
|
|
68
|
+
const data = this.extrinsics.get(hash.raw);
|
|
69
|
+
if (data === undefined) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return Decoder.decodeObject(Extrinsic.Codec.View, data, this.chainSpec);
|
|
73
|
+
}
|
|
74
|
+
markUnused(hash) {
|
|
75
|
+
this.headers.removeSync(hash.raw);
|
|
76
|
+
this.extrinsics.removeSync(hash.raw);
|
|
77
|
+
this.postStateRoots.removeSync(hash.raw);
|
|
78
|
+
}
|
|
79
|
+
async close() {
|
|
80
|
+
await Promise.all([this.headers.close(), this.extrinsics.close(), this.postStateRoots.close()]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
/**
|
|
9
|
+
* Hybrid serialized-states db.
|
|
10
|
+
*
|
|
11
|
+
* States (leafs) are kept in-memory, but large values are persisted to lmdb.
|
|
12
|
+
* Reads go straight to lmdb, which keeps its own page cache.
|
|
13
|
+
* NOTE: this DB is designed for long fuzzing and to be used with pruning to
|
|
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.
|
|
21
|
+
*/
|
|
22
|
+
export declare class HybridSerializedStates implements StatesDb<SerializedState<LeafDb>>, InitStatesDb<StateEntries> {
|
|
23
|
+
private readonly spec;
|
|
24
|
+
private readonly blake2b;
|
|
25
|
+
private readonly root;
|
|
26
|
+
private readonly inMemStates;
|
|
27
|
+
private readonly lmdbValues;
|
|
28
|
+
private readonly valuesDb;
|
|
29
|
+
private readonly refsStore;
|
|
30
|
+
private readonly refs;
|
|
31
|
+
private pendingCleanup;
|
|
32
|
+
static new({ spec, blake2b, dbPath, readOnly, ephemeral, compression, }: {
|
|
33
|
+
spec: ChainSpec;
|
|
34
|
+
blake2b: Blake2b;
|
|
35
|
+
dbPath: string;
|
|
36
|
+
readOnly?: boolean;
|
|
37
|
+
ephemeral?: boolean;
|
|
38
|
+
compression?: boolean;
|
|
39
|
+
}): HybridSerializedStates;
|
|
40
|
+
private constructor();
|
|
41
|
+
insertInitialState(headerHash: HeaderHash, entries: StateEntries): Promise<Result<OK, StateUpdateError>>;
|
|
42
|
+
updateAndSetState(header: HeaderHash, state: SerializedState<LeafDb>, update: Partial<State & ServicesUpdate>): Promise<Result<OK, StateUpdateError>>;
|
|
43
|
+
getStateRoot(state: SerializedState<LeafDb>): Promise<StateRootHash>;
|
|
44
|
+
getState(header: HeaderHash): SerializedState<LeafDb> | null;
|
|
45
|
+
commitFinalized(headers: HeaderHash[]): void;
|
|
46
|
+
markUnused(header: HeaderHash): void;
|
|
47
|
+
/** Apply a refcounting update and remove values that lost their last reference. */
|
|
48
|
+
private applyRefs;
|
|
49
|
+
diskSizeInBytes(): number | null;
|
|
50
|
+
close(): Promise<void>;
|
|
51
|
+
/** Write new large values to LMDB in one transaction. */
|
|
52
|
+
private writeValues;
|
|
53
|
+
/** Read a value from LMDB. */
|
|
54
|
+
private readValue;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=hybrid-states.d.ts.map
|
|
@@ -0,0 +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,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"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { HashDictionary, SortedSet } from "#@typeberry/collections";
|
|
2
|
+
import { InMemoryValueRefsStore, LeafDb, StateUpdateError, updateLeafs, ValueRefs, } from "#@typeberry/database";
|
|
3
|
+
import { Logger } from "#@typeberry/logger";
|
|
4
|
+
import { SerializedState, StateEntryUpdateAction, serializeStateUpdate, } from "#@typeberry/state-merkleization";
|
|
5
|
+
import { leafComparator } from "#@typeberry/trie";
|
|
6
|
+
import { OK, Result } from "#@typeberry/utils";
|
|
7
|
+
import { LmdbRoot } from "./root.js";
|
|
8
|
+
const logger = Logger.new(import.meta.filename, "db");
|
|
9
|
+
/**
|
|
10
|
+
* Hybrid serialized-states db.
|
|
11
|
+
*
|
|
12
|
+
* States (leafs) are kept in-memory, but large values are persisted to lmdb.
|
|
13
|
+
* Reads go straight to lmdb, which keeps its own page cache.
|
|
14
|
+
* NOTE: this DB is designed for long fuzzing and to be used with pruning to
|
|
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.
|
|
22
|
+
*/
|
|
23
|
+
export class HybridSerializedStates {
|
|
24
|
+
spec;
|
|
25
|
+
blake2b;
|
|
26
|
+
root;
|
|
27
|
+
inMemStates = HashDictionary.new();
|
|
28
|
+
lmdbValues;
|
|
29
|
+
// A single shared values accessor reused by every `LeafDb` we hand out.
|
|
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();
|
|
35
|
+
static new({ spec, blake2b, dbPath, readOnly, ephemeral, compression, }) {
|
|
36
|
+
const root = LmdbRoot.new(dbPath, { readOnly, ephemeral, compression });
|
|
37
|
+
return new HybridSerializedStates(spec, blake2b, root);
|
|
38
|
+
}
|
|
39
|
+
constructor(spec, blake2b, root) {
|
|
40
|
+
this.spec = spec;
|
|
41
|
+
this.blake2b = blake2b;
|
|
42
|
+
this.root = root;
|
|
43
|
+
this.lmdbValues = this.root.subDb("values");
|
|
44
|
+
this.valuesDb = { get: (key) => this.readValue(key) };
|
|
45
|
+
}
|
|
46
|
+
async insertInitialState(headerHash, entries) {
|
|
47
|
+
const { values, leafs } = updateLeafs(SortedSet.fromArray(leafComparator, []), this.blake2b, Array.from(entries, (x) => [StateEntryUpdateAction.Insert, x[0], x[1]]));
|
|
48
|
+
const res = await this.writeValues(values);
|
|
49
|
+
if (res.isError) {
|
|
50
|
+
return res;
|
|
51
|
+
}
|
|
52
|
+
this.inMemStates.set(headerHash, leafs);
|
|
53
|
+
this.applyRefs(this.refs.onInitial(values.map((v) => v[0])));
|
|
54
|
+
return Result.ok(OK);
|
|
55
|
+
}
|
|
56
|
+
async updateAndSetState(header, state, update) {
|
|
57
|
+
const updatedValues = serializeStateUpdate(this.spec, this.blake2b, update);
|
|
58
|
+
// Clone the leaf set before mutating: the previous state keeps using its own.
|
|
59
|
+
const newLeafs = SortedSet.fromSortedArray(leafComparator, state.backend.leafs.array);
|
|
60
|
+
const { values, removed, leafs } = updateLeafs(newLeafs, this.blake2b, updatedValues);
|
|
61
|
+
const res = await this.writeValues(values);
|
|
62
|
+
if (res.isError) {
|
|
63
|
+
// Leave the caller's state untouched: its new leaves would reference
|
|
64
|
+
// values that never reached disk.
|
|
65
|
+
return res;
|
|
66
|
+
}
|
|
67
|
+
// Re-create the lookup with the shared values accessor only once the new
|
|
68
|
+
// values are durably written.
|
|
69
|
+
state.updateBackend(LeafDb.fromLeaves(leafs, this.valuesDb));
|
|
70
|
+
this.inMemStates.set(header, leafs);
|
|
71
|
+
this.applyRefs(this.refs.onImport(header, { inserted: values.map((v) => v[0]), removed }));
|
|
72
|
+
return Result.ok(OK);
|
|
73
|
+
}
|
|
74
|
+
async getStateRoot(state) {
|
|
75
|
+
return state.backend.getStateRoot(this.blake2b);
|
|
76
|
+
}
|
|
77
|
+
getState(header) {
|
|
78
|
+
const leafs = this.inMemStates.get(header);
|
|
79
|
+
if (leafs === undefined) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const leafDb = LeafDb.fromLeaves(leafs, this.valuesDb);
|
|
83
|
+
return SerializedState.new(this.spec, this.blake2b, leafDb);
|
|
84
|
+
}
|
|
85
|
+
commitFinalized(headers) {
|
|
86
|
+
this.applyRefs(this.refs.commitFinalized(headers));
|
|
87
|
+
}
|
|
88
|
+
markUnused(header) {
|
|
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));
|
|
92
|
+
this.inMemStates.delete(header);
|
|
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
|
+
}
|
|
111
|
+
diskSizeInBytes() {
|
|
112
|
+
return this.root.sizeInBytes();
|
|
113
|
+
}
|
|
114
|
+
async close() {
|
|
115
|
+
await this.pendingCleanup;
|
|
116
|
+
await this.lmdbValues.close();
|
|
117
|
+
await this.root.close();
|
|
118
|
+
}
|
|
119
|
+
/** Write new large values to LMDB in one transaction. */
|
|
120
|
+
async writeValues(values) {
|
|
121
|
+
if (values.length === 0) {
|
|
122
|
+
return Result.ok(OK);
|
|
123
|
+
}
|
|
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;
|
|
130
|
+
await this.lmdbValues.transaction(() => {
|
|
131
|
+
for (const [hash, val] of values) {
|
|
132
|
+
this.lmdbValues.put(hash.raw, val.raw);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
return Result.error(StateUpdateError.Commit, () => `Failed to commit values: ${e}`);
|
|
138
|
+
}
|
|
139
|
+
return Result.ok(OK);
|
|
140
|
+
}
|
|
141
|
+
/** Read a value from LMDB. */
|
|
142
|
+
readValue(key) {
|
|
143
|
+
const val = this.lmdbValues.get(key.raw);
|
|
144
|
+
if (val === undefined) {
|
|
145
|
+
throw new Error(`Missing value at key: ${key}`);
|
|
146
|
+
}
|
|
147
|
+
return val;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hybrid-states.test.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/database-lmdb/hybrid-states.test.ts"],"names":[],"mappings":""}
|