@typeberry/lib 0.8.4-faebc7a → 0.9.0-3f2c45b
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/configs/index.d.ts +30 -1
- package/packages/configs/index.d.ts.map +1 -1
- package/packages/configs/index.js +4 -2
- package/packages/configs/typeberry-dev-full.json +29 -0
- package/packages/core/bytes/bytes.d.ts +1 -0
- package/packages/core/bytes/bytes.d.ts.map +1 -1
- package/packages/core/bytes/bytes.js +8 -0
- package/packages/core/utils/debug.d.ts +4 -2
- package/packages/core/utils/debug.d.ts.map +1 -1
- package/packages/core/utils/debug.js +18 -13
- package/packages/core/utils/debug.test.js +12 -6
- package/packages/jam/config-node/node-config.d.ts +2 -1
- package/packages/jam/config-node/node-config.d.ts.map +1 -1
- package/packages/jam/config-node/node-config.js +8 -3
- package/packages/jam/config-node/node-config.test.js +3 -3
- package/packages/jam/database-fjall/hybrid-states.d.ts +47 -10
- package/packages/jam/database-fjall/hybrid-states.d.ts.map +1 -1
- package/packages/jam/database-fjall/hybrid-states.js +78 -22
- package/packages/jam/database-fjall/hybrid-states.test.js +31 -1
- package/packages/jam/database-fjall/root.d.ts +18 -12
- package/packages/jam/database-fjall/root.d.ts.map +1 -1
- package/packages/jam/database-fjall/root.js +14 -10
- package/packages/jam/jamnp-s/tasks/ticket-distribution.js +1 -1
- package/packages/jam/node/main-fuzz.d.ts.map +1 -1
- package/packages/jam/node/main-fuzz.js +53 -10
- package/packages/jam/node/main-importer.d.ts +8 -1
- package/packages/jam/node/main-importer.d.ts.map +1 -1
- package/packages/jam/node/main-importer.js +2 -1
- package/packages/jam/safrole/bandersnatch-vrf.d.ts +22 -2
- package/packages/jam/safrole/bandersnatch-vrf.d.ts.map +1 -1
- package/packages/jam/safrole/bandersnatch-vrf.js +54 -20
- package/packages/jam/safrole/bandersnatch-vrf.test.js +3 -2
- package/packages/jam/safrole/bandersnatch-wasm.d.ts +10 -0
- package/packages/jam/safrole/bandersnatch-wasm.d.ts.map +1 -1
- package/packages/jam/safrole/bandersnatch-wasm.js +12 -0
- package/packages/jam/ticket-pool/ticket-validator.d.ts +5 -4
- package/packages/jam/ticket-pool/ticket-validator.d.ts.map +1 -1
- package/packages/jam/ticket-pool/ticket-validator.js +8 -3
- package/packages/jam/ticket-pool/ticket-validator.test.js +5 -4
- package/packages/jam/ticket-pool/verified-ticket-pool.d.ts +2 -0
- package/packages/jam/ticket-pool/verified-ticket-pool.d.ts.map +1 -1
- package/packages/jam/ticket-pool/verified-ticket-pool.js +4 -0
- package/packages/jam/ticket-pool/verified-ticket-pool.test.js +5 -5
- package/packages/workers/api-node/config.d.ts +14 -6
- package/packages/workers/api-node/config.d.ts.map +1 -1
- package/packages/workers/api-node/config.js +15 -11
- package/packages/workers/block-authorship/{generator.d.ts → block-generator.d.ts} +5 -5
- package/packages/workers/block-authorship/block-generator.d.ts.map +1 -0
- package/packages/workers/block-authorship/{generator.js → block-generator.js} +3 -3
- package/packages/workers/block-authorship/block-generator.test.d.ts +2 -0
- package/packages/workers/block-authorship/block-generator.test.d.ts.map +1 -0
- package/packages/workers/block-authorship/{generator.test.js → block-generator.test.js} +8 -8
- package/packages/workers/block-authorship/epoch-authoring-slots.d.ts +35 -0
- package/packages/workers/block-authorship/epoch-authoring-slots.d.ts.map +1 -0
- package/packages/workers/block-authorship/epoch-authoring-slots.js +86 -0
- package/packages/workers/block-authorship/epoch-tracker.d.ts +29 -0
- package/packages/workers/block-authorship/epoch-tracker.d.ts.map +1 -0
- package/packages/workers/block-authorship/epoch-tracker.js +80 -0
- package/packages/workers/block-authorship/index.d.ts.map +1 -1
- package/packages/workers/block-authorship/index.js +1 -1
- package/packages/workers/block-authorship/main.d.ts +3 -0
- package/packages/workers/block-authorship/main.d.ts.map +1 -1
- package/packages/workers/block-authorship/main.js +193 -261
- package/packages/workers/block-authorship/ticket-generator/bootstrap-main.d.ts +2 -0
- package/packages/workers/block-authorship/ticket-generator/bootstrap-main.d.ts.map +1 -0
- package/packages/workers/block-authorship/ticket-generator/bootstrap-main.js +23 -0
- package/packages/workers/block-authorship/ticket-generator/index.d.ts +16 -0
- package/packages/workers/block-authorship/ticket-generator/index.d.ts.map +1 -0
- package/packages/workers/block-authorship/ticket-generator/index.js +62 -0
- package/packages/workers/block-authorship/ticket-generator/protocol.d.ts +50 -0
- package/packages/workers/block-authorship/ticket-generator/protocol.d.ts.map +1 -0
- package/packages/workers/block-authorship/ticket-generator/protocol.js +54 -0
- package/packages/workers/block-authorship/{ticket-generator.d.ts → ticket-generator/ticket-generator.d.ts} +4 -0
- package/packages/workers/block-authorship/ticket-generator/ticket-generator.d.ts.map +1 -0
- package/packages/workers/block-authorship/{ticket-generator.js → ticket-generator/ticket-generator.js} +19 -9
- package/packages/workers/block-authorship/ticket-generator/ticket-generator.test.d.ts.map +1 -0
- package/packages/workers/block-authorship/{ticket-generator.test.js → ticket-generator/ticket-generator.test.js} +13 -9
- package/packages/workers/block-authorship/ticket-generator/worker-pool.d.ts +36 -0
- package/packages/workers/block-authorship/ticket-generator/worker-pool.d.ts.map +1 -0
- package/packages/workers/block-authorship/ticket-generator/worker-pool.js +111 -0
- package/packages/workers/block-authorship/ticket-validator.d.ts +7 -8
- package/packages/workers/block-authorship/ticket-validator.d.ts.map +1 -1
- package/packages/workers/block-authorship/ticket-validator.js +26 -23
- package/packages/workers/comms-authorship-network/protocol.d.ts +4 -4
- package/packages/workers/comms-authorship-network/protocol.d.ts.map +1 -1
- package/packages/workers/comms-authorship-network/protocol.js +4 -5
- package/packages/workers/comms-authorship-network/tickets-message.d.ts +0 -14
- package/packages/workers/comms-authorship-network/tickets-message.d.ts.map +1 -1
- package/packages/workers/comms-authorship-network/tickets-message.js +0 -17
- package/packages/workers/importer/importer.d.ts +2 -2
- package/packages/workers/importer/importer.d.ts.map +1 -1
- package/packages/workers/importer/importer.js +5 -5
- package/packages/workers/importer/stats.d.ts +1 -3
- package/packages/workers/importer/stats.d.ts.map +1 -1
- package/packages/workers/importer/stats.js +12 -12
- package/packages/workers/jam-network/main.d.ts.map +1 -1
- package/packages/workers/jam-network/main.js +8 -3
- package/packages/workers/block-authorship/generator.d.ts.map +0 -1
- package/packages/workers/block-authorship/generator.test.d.ts +0 -2
- package/packages/workers/block-authorship/generator.test.d.ts.map +0 -1
- package/packages/workers/block-authorship/ticket-generator.d.ts.map +0 -1
- package/packages/workers/block-authorship/ticket-generator.test.d.ts.map +0 -1
- /package/packages/configs/{typeberry-dev.json → typeberry-dev-tiny.json} +0 -0
- /package/packages/workers/block-authorship/{ticket-generator.test.d.ts → ticket-generator/ticket-generator.test.d.ts} +0 -0
|
@@ -8,7 +8,7 @@ import { v1 as fuzzV1 } from "#@typeberry/fuzz-proto";
|
|
|
8
8
|
import { HASH_SIZE } from "#@typeberry/hash";
|
|
9
9
|
import { Logger } from "#@typeberry/logger";
|
|
10
10
|
import { CURRENT_VERSION, Result, version } from "#@typeberry/utils";
|
|
11
|
-
import { logHostEnvironment } from "#@typeberry/workers-api-node";
|
|
11
|
+
import { FjallValuesSession, logHostEnvironment } from "#@typeberry/workers-api-node";
|
|
12
12
|
import { getChainSpec } from "./common.js";
|
|
13
13
|
import { mainImporter } from "./main-importer.js";
|
|
14
14
|
const logger = Logger.new(import.meta.filename, "fuzztarget");
|
|
@@ -17,6 +17,14 @@ const FUZZ_DB_SUBDIR = "typeberry-fuzz-db";
|
|
|
17
17
|
const FUZZ_DB_FJALL = "fjall-hybrid";
|
|
18
18
|
const FUZZ_DB_LMDB = "lmdb-hybrid";
|
|
19
19
|
const FUZZ_DB_OPTIONS = [FUZZ_DB_FJALL, FUZZ_DB_LMDB];
|
|
20
|
+
/** Subdirectory (under the fuzzer's db dir) holding the reused fjall values keyspace. */
|
|
21
|
+
const FUZZ_FJALL_VALUES_SUBDIR = "values-session";
|
|
22
|
+
/**
|
|
23
|
+
* Size of the fjall block-cache for the fuzz session. Values pile up across
|
|
24
|
+
* resets (for fjall we do not wipe between them), so this cache is what keeps
|
|
25
|
+
* the resident memory bounded.
|
|
26
|
+
*/
|
|
27
|
+
const FUZZ_FJALL_CACHE_BYTES = 128 * 1024 * 1024;
|
|
20
28
|
/**
|
|
21
29
|
* Resolve the directory the fuzzer should use for its on-disk database, or
|
|
22
30
|
* `undefined` for an in-memory database. The dedicated `FUZZ_DB_SUBDIR` is
|
|
@@ -63,6 +71,10 @@ export async function mainFuzz(fuzzConfig, withRelPath) {
|
|
|
63
71
|
logger.info `🗄️ Fuzz persistent backend: ${hybridStateBackend}.`;
|
|
64
72
|
}
|
|
65
73
|
let runningNode = null;
|
|
74
|
+
// The fjall values keyspace is opened once per fuzz session and reused on
|
|
75
|
+
// every reset, because opening it is the slow part. Only the in-memory blocks
|
|
76
|
+
// and leaf sets are rebuilt for each vector. fjall-hybrid only.
|
|
77
|
+
let fjallSession = null;
|
|
66
78
|
const chainSpec = getChainSpec(config.node.flavor);
|
|
67
79
|
const closeFuzzTarget = startFuzzTarget(fuzzConfig.version, fuzzConfig.socket, {
|
|
68
80
|
...getFuzzDetails(),
|
|
@@ -113,20 +125,43 @@ export async function mainFuzz(fuzzConfig, withRelPath) {
|
|
|
113
125
|
// like the in-memory backend; only the large values live on disk.
|
|
114
126
|
dummyFinalityDepth: 20,
|
|
115
127
|
pruneBlocks: true,
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
// Tiny stays uncompressed
|
|
128
|
+
// The on-disk fuzz db is throwaway (we wipe it), so open it ephemeral and
|
|
129
|
+
// skip the fsync, we do not need durability here. On full spec ephemeral
|
|
130
|
+
// also turns on compression further down, so the big values do not grow the
|
|
131
|
+
// db too much. Tiny stays uncompressed, its db is small and speed matters more.
|
|
120
132
|
ephemeral: isPersistent,
|
|
121
133
|
stateBackend: isPersistent ? hybridStateBackend : "lmdb",
|
|
134
|
+
// Reuse the session keyspace (fjall-hybrid only, other backends
|
|
135
|
+
// ignore it). Nothing to pass for the in-memory fallback.
|
|
136
|
+
sharedFjallSession: isPersistent ? (fjallSession ?? undefined) : undefined,
|
|
122
137
|
});
|
|
123
138
|
};
|
|
124
139
|
if (fuzzDbBase !== undefined) {
|
|
125
|
-
// Each reset starts a fresh session from the genesis the fuzzer just sent,
|
|
126
|
-
// so the on-disk db must be empty: otherwise initializeDatabase sees an
|
|
127
|
-
// already-initialized db and silently resumes the previous run's state.
|
|
128
|
-
await wipeFuzzDb(fuzzDbBase);
|
|
129
140
|
try {
|
|
141
|
+
if (hybridStateBackend === FUZZ_DB_FJALL) {
|
|
142
|
+
// fjall-hybrid: open the values keyspace once and reuse it on every
|
|
143
|
+
// reset. The values partition is content-addressed and immutable, so
|
|
144
|
+
// it is fine that values pile up across resets, the unreferenced ones
|
|
145
|
+
// just sit there. `initializeDatabase` decides whether the db is
|
|
146
|
+
// already initialized from the in-memory blocks, which we rebuild on
|
|
147
|
+
// every reset, not from the values store, so reusing it does not
|
|
148
|
+
// resume the previous run.
|
|
149
|
+
if (fjallSession === null) {
|
|
150
|
+
// Start from a clean slate once, then keep the keyspace open.
|
|
151
|
+
await wipeFuzzDb(fuzzDbBase);
|
|
152
|
+
fjallSession = await FjallValuesSession.open(`${withRelPath(fuzzDbBase)}/${FUZZ_FJALL_VALUES_SUBDIR}`, {
|
|
153
|
+
ephemeral: true,
|
|
154
|
+
cacheSizeBytes: FUZZ_FJALL_CACHE_BYTES,
|
|
155
|
+
});
|
|
156
|
+
logger.info `🗄️ Opened reusable fjall values session at ${withRelPath(fuzzDbBase)}/${FUZZ_FJALL_VALUES_SUBDIR}`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// lmdb-hybrid: keep the old behaviour, wipe and reopen on every
|
|
161
|
+
// reset. A fresh db each reset makes `initializeDatabase` set up
|
|
162
|
+
// genesis again instead of resuming the previous run.
|
|
163
|
+
await wipeFuzzDb(fuzzDbBase);
|
|
164
|
+
}
|
|
130
165
|
runningNode = await buildNode(fuzzDbBase);
|
|
131
166
|
return await runningNode.getBestStateRootHash();
|
|
132
167
|
}
|
|
@@ -142,9 +177,17 @@ export async function mainFuzz(fuzzConfig, withRelPath) {
|
|
|
142
177
|
});
|
|
143
178
|
return () => {
|
|
144
179
|
closeFuzzTarget();
|
|
180
|
+
// Close the reused fjall values session (if any) before wiping its files, so
|
|
181
|
+
// the keyspace handle is released first.
|
|
182
|
+
const closed = fjallSession?.close() ?? Promise.resolve();
|
|
183
|
+
fjallSession = null;
|
|
145
184
|
if (fuzzDbBase !== undefined) {
|
|
146
185
|
// best-effort cleanup on shutdown; ignore failures (dir may already be gone).
|
|
147
|
-
|
|
186
|
+
closed
|
|
187
|
+
.catch(() => { })
|
|
188
|
+
.finally(() => {
|
|
189
|
+
wipeFuzzDb(fuzzDbBase).catch(() => { });
|
|
190
|
+
});
|
|
148
191
|
}
|
|
149
192
|
};
|
|
150
193
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type FjallValuesSession } from "#@typeberry/workers-api-node";
|
|
1
2
|
import type { JamConfig } from "./jam-config.js";
|
|
2
3
|
import type { NodeApi } from "./main.js";
|
|
3
4
|
export type StateBackend = "lmdb" | "lmdb-hybrid" | "fjall-hybrid";
|
|
@@ -8,9 +9,15 @@ export type ImporterOptions = {
|
|
|
8
9
|
/** Open the database without fsync/compression. Only safe for throwaway dbs (e.g. fuzzing). */
|
|
9
10
|
ephemeral?: boolean;
|
|
10
11
|
/**
|
|
11
|
-
* Persistent backend
|
|
12
|
+
* Persistent backend used when `databaseBasePath` is set. Defaults to full LMDB.
|
|
12
13
|
*/
|
|
13
14
|
stateBackend?: StateBackend;
|
|
15
|
+
/**
|
|
16
|
+
* Reuse an already-open fjall values session instead of opening a fresh
|
|
17
|
+
* keyspace. Only used when `stateBackend === "fjall-hybrid"`. The fuzz target
|
|
18
|
+
* opens one per run and reuses it across resets.
|
|
19
|
+
*/
|
|
20
|
+
sharedFjallSession?: FjallValuesSession;
|
|
14
21
|
};
|
|
15
22
|
export declare function mainImporter(config: JamConfig, withRelPath: (v: string) => string, options?: ImporterOptions): Promise<NodeApi>;
|
|
16
23
|
//# sourceMappingURL=main-importer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main-importer.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/main-importer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"main-importer.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/main-importer.ts"],"names":[],"mappings":"AASA,OAAO,EACL,KAAK,kBAAkB,EAIxB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIzC,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,aAAa,GAAG,cAAc,CAAC;AAEnE,MAAM,MAAM,eAAe,GAAG;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+FAA+F;IAC/F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC,CAAC;AAEF,wBAAsB,YAAY,CAChC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAClC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,OAAO,CAAC,CAsGlB"}
|
|
@@ -6,7 +6,7 @@ import { Blake2b, HASH_SIZE } from "#@typeberry/hash";
|
|
|
6
6
|
import { createImporter, ImporterConfig } from "#@typeberry/importer";
|
|
7
7
|
import { tryAsU16 } from "#@typeberry/numbers";
|
|
8
8
|
import { CURRENT_SUITE, CURRENT_VERSION, Result, resultToString, version } from "#@typeberry/utils";
|
|
9
|
-
import { HybridWorkerConfig, InMemWorkerConfig, LmdbWorkerConfig } from "#@typeberry/workers-api-node";
|
|
9
|
+
import { HybridWorkerConfig, InMemWorkerConfig, LmdbWorkerConfig, } from "#@typeberry/workers-api-node";
|
|
10
10
|
import { getChainSpec, getDatabasePath, initializeDatabase, logger } from "./common.js";
|
|
11
11
|
const zeroHash = Bytes.zero(HASH_SIZE).asOpaque();
|
|
12
12
|
export async function mainImporter(config, withRelPath, options = {}) {
|
|
@@ -49,6 +49,7 @@ export async function mainImporter(config, withRelPath, options = {}) {
|
|
|
49
49
|
ephemeral,
|
|
50
50
|
compression,
|
|
51
51
|
backend: dbBackend === "lmdb-hybrid" ? "lmdb" : "fjall",
|
|
52
|
+
sharedFjallSession: options.sharedFjallSession,
|
|
52
53
|
})
|
|
53
54
|
: LmdbWorkerConfig.new({
|
|
54
55
|
nodeName,
|
|
@@ -27,7 +27,27 @@ declare function generateSeal(bandersnatch: BandernsatchWasm, authorKey: Banders
|
|
|
27
27
|
export type VrfOutputHash = Opaque<OpaqueHash, "VRF Output Hash">;
|
|
28
28
|
declare function getVrfOutputHash(bandersnatch: BandernsatchWasm, authorKey: BandersnatchSecretSeed, input: BytesBlob): Promise<Result<VrfOutputHash, null>>;
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* Batch-generate signed tickets for multiple validators in a single native call,
|
|
31
|
+
* reusing the ring prover setup across all of them. Returns one ticket list per
|
|
32
|
+
* validator, in the same order as `proverKeyIndices`/`secrets`.
|
|
31
33
|
*/
|
|
32
|
-
declare function generateTickets(bandersnatch: BandernsatchWasm, ringKeys: BandersnatchKey[],
|
|
34
|
+
declare function generateTickets(bandersnatch: BandernsatchWasm, ringKeys: BandersnatchKey[], proverKeyIndices: readonly number[], secrets: readonly BandersnatchSecretSeed[], entropy: EntropyHash, ticketsPerValidator: number): Promise<Result<SignedTicket[][], null>>;
|
|
35
|
+
/**
|
|
36
|
+
* Build the concatenated ring-VRF inputs for ticket generation: one
|
|
37
|
+
* `JAM_TICKET_SEAL || entropy || attempt_byte` input per attempt.
|
|
38
|
+
*
|
|
39
|
+
* Exposed so the worker-pool path can build the same inputs to hand off to a
|
|
40
|
+
* worker thread without re-deriving the layout.
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildTicketVrfInputs(entropy: EntropyHash, ticketsPerValidator: number): {
|
|
43
|
+
inputsData: Uint8Array;
|
|
44
|
+
vrfInputDataLen: number;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Parse the raw output of `batchGenerateRingVrfForValidators` into per-validator
|
|
48
|
+
* ticket lists. Records are ordered validator-major, then attempt-major; each
|
|
49
|
+
* record is `status byte || signature`. A malformed batch yields a single error
|
|
50
|
+
* byte. Exposed so the worker-pool path can parse a worker's raw result.
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseTicketsBatchOutput(result: Uint8Array, numValidators: number, ticketsPerValidator: number): Result<SignedTicket[][], null>;
|
|
33
53
|
//# sourceMappingURL=bandersnatch-vrf.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bandersnatch-vrf.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/safrole/bandersnatch-vrf.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAsB,MAAM,6BAA6B,CAAC;AAC/E,OAAO,EAAS,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"bandersnatch-vrf.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/safrole/bandersnatch-vrf.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SignedTicket, tryAsTicketAttempt } from "#@typeberry/block/tickets.js";
|
|
2
2
|
import { Bytes, BytesBlob } from "#@typeberry/bytes";
|
|
3
|
+
import { SEED_SIZE } from "#@typeberry/crypto";
|
|
3
4
|
import { BANDERSNATCH_PROOF_BYTES, BANDERSNATCH_RING_ROOT_BYTES, BANDERSNATCH_VRF_SIGNATURE_BYTES, } from "#@typeberry/crypto/bandersnatch.js";
|
|
4
5
|
import { HASH_SIZE } from "#@typeberry/hash";
|
|
5
6
|
import { Result } from "#@typeberry/utils";
|
|
@@ -33,6 +34,10 @@ const ringCommitmentCache = [
|
|
|
33
34
|
keys: BytesBlob.empty(),
|
|
34
35
|
value: Promise.resolve(Result.error(null, () => "")),
|
|
35
36
|
},
|
|
37
|
+
{
|
|
38
|
+
keys: BytesBlob.empty(),
|
|
39
|
+
value: Promise.resolve(Result.error(null, () => "")),
|
|
40
|
+
},
|
|
36
41
|
];
|
|
37
42
|
const FUNCTIONS = {
|
|
38
43
|
verifySeal,
|
|
@@ -122,31 +127,60 @@ async function getVrfOutputHash(bandersnatch, authorKey, input) {
|
|
|
122
127
|
// One byte for result discriminator and the rest is the ring VRF signature.
|
|
123
128
|
const GENERATE_RESULT_ENTRY_LENGTH = 1 + BANDERSNATCH_PROOF_BYTES;
|
|
124
129
|
/**
|
|
125
|
-
*
|
|
130
|
+
* Batch-generate signed tickets for multiple validators in a single native call,
|
|
131
|
+
* reusing the ring prover setup across all of them. Returns one ticket list per
|
|
132
|
+
* validator, in the same order as `proverKeyIndices`/`secrets`.
|
|
126
133
|
*/
|
|
127
|
-
async function generateTickets(bandersnatch, ringKeys,
|
|
128
|
-
|
|
134
|
+
async function generateTickets(bandersnatch, ringKeys, proverKeyIndices, secrets, entropy, ticketsPerValidator) {
|
|
135
|
+
if (proverKeyIndices.length !== secrets.length) {
|
|
136
|
+
return Result.error(null, () => "proverKeyIndices and secrets must have the same length");
|
|
137
|
+
}
|
|
138
|
+
if (proverKeyIndices.length === 0) {
|
|
139
|
+
return Result.ok([]);
|
|
140
|
+
}
|
|
141
|
+
const { inputsData, vrfInputDataLen } = buildTicketVrfInputs(entropy, ticketsPerValidator);
|
|
142
|
+
const ringKeysData = BytesBlob.blobFromParts(ringKeys.map((k) => k.raw)).raw;
|
|
143
|
+
const secretSeedsData = BytesBlob.blobFromParts(secrets.map((s) => s.raw)).raw;
|
|
144
|
+
const result = await bandersnatch.batchGenerateRingVrfForValidators(ringKeysData, Uint32Array.from(proverKeyIndices), secretSeedsData, SEED_SIZE, inputsData, vrfInputDataLen);
|
|
145
|
+
return parseTicketsBatchOutput(result, proverKeyIndices.length, ticketsPerValidator);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Build the concatenated ring-VRF inputs for ticket generation: one
|
|
149
|
+
* `JAM_TICKET_SEAL || entropy || attempt_byte` input per attempt.
|
|
150
|
+
*
|
|
151
|
+
* Exposed so the worker-pool path can build the same inputs to hand off to a
|
|
152
|
+
* worker thread without re-deriving the layout.
|
|
153
|
+
*/
|
|
154
|
+
export function buildTicketVrfInputs(entropy, ticketsPerValidator) {
|
|
129
155
|
const vrfInputParts = [];
|
|
130
156
|
for (let attempt = 0; attempt < ticketsPerValidator; attempt++) {
|
|
131
157
|
vrfInputParts.push(BytesBlob.blobFromParts([JAM_TICKET_SEAL, entropy.raw, Uint8Array.of(attempt)]).raw);
|
|
132
158
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
159
|
+
return {
|
|
160
|
+
inputsData: BytesBlob.blobFromParts(vrfInputParts).raw,
|
|
161
|
+
vrfInputDataLen: JAM_TICKET_SEAL.length + entropy.length + 1,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Parse the raw output of `batchGenerateRingVrfForValidators` into per-validator
|
|
166
|
+
* ticket lists. Records are ordered validator-major, then attempt-major; each
|
|
167
|
+
* record is `status byte || signature`. A malformed batch yields a single error
|
|
168
|
+
* byte. Exposed so the worker-pool path can parse a worker's raw result.
|
|
169
|
+
*/
|
|
170
|
+
export function parseTicketsBatchOutput(result, numValidators, ticketsPerValidator) {
|
|
171
|
+
const perValidator = [];
|
|
172
|
+
let offset = 0;
|
|
173
|
+
for (let v = 0; v < numValidators; v++) {
|
|
174
|
+
const tickets = [];
|
|
175
|
+
for (let attempt = 0; attempt < ticketsPerValidator; attempt++) {
|
|
176
|
+
if (result[offset] === ResultValues.Error) {
|
|
177
|
+
return Result.error(null, () => `Ring VRF proof generation failed for validator ${v}, attempt ${attempt}`);
|
|
178
|
+
}
|
|
179
|
+
const signature = Bytes.fromBlob(result.subarray(offset + 1, offset + GENERATE_RESULT_ENTRY_LENGTH), BANDERSNATCH_PROOF_BYTES).asOpaque();
|
|
180
|
+
tickets.push(SignedTicket.create({ attempt: tryAsTicketAttempt(attempt), signature }));
|
|
181
|
+
offset += GENERATE_RESULT_ENTRY_LENGTH;
|
|
144
182
|
}
|
|
145
|
-
|
|
146
|
-
tickets.push(SignedTicket.create({
|
|
147
|
-
attempt: tryAsTicketAttempt(attempt),
|
|
148
|
-
signature,
|
|
149
|
-
}));
|
|
183
|
+
perValidator.push(tickets);
|
|
150
184
|
}
|
|
151
|
-
return Result.ok(
|
|
185
|
+
return Result.ok(perValidator);
|
|
152
186
|
}
|
|
@@ -165,11 +165,12 @@ describe("Bandersnatch verification", () => {
|
|
|
165
165
|
const ringKeys = secrets.map((secret) => deriveBandersnatchPublicKey(secret));
|
|
166
166
|
const proverIndex = 0;
|
|
167
167
|
const entropy = Bytes.fill(HASH_SIZE, 123).asOpaque();
|
|
168
|
-
const genResult = await bandersnatchVrf.generateTickets(await bandersnatchWasm, ringKeys, proverIndex, secrets[proverIndex], entropy, 2);
|
|
168
|
+
const genResult = await bandersnatchVrf.generateTickets(await bandersnatchWasm, ringKeys, [proverIndex], [secrets[proverIndex]], entropy, 2);
|
|
169
169
|
assert.ok(genResult.isOk);
|
|
170
170
|
const commitment = await bandersnatchVrf.getRingCommitment(await bandersnatchWasm, ringKeys);
|
|
171
171
|
assert.ok(commitment.isOk);
|
|
172
|
-
|
|
172
|
+
assert.strictEqual(genResult.ok.length, 1);
|
|
173
|
+
const verifyResult = await bandersnatchVrf.verifyTickets(await bandersnatchWasm, ringKeys.length, commitment.ok, genResult.ok[0], entropy);
|
|
173
174
|
assert.ok(verifyResult.isValid, "Generated tickets should pass verification");
|
|
174
175
|
});
|
|
175
176
|
});
|
|
@@ -8,5 +8,15 @@ export declare class BandernsatchWasm {
|
|
|
8
8
|
generateSeal(authorKey: Uint8Array, input: Uint8Array, auxData: Uint8Array): Promise<Uint8Array<ArrayBufferLike>>;
|
|
9
9
|
getVrfOutputHash(authorKey: Uint8Array, input: Uint8Array): Promise<Uint8Array<ArrayBufferLike>>;
|
|
10
10
|
batchGenerateRingVrf(ringKeys: Uint8Array, proverKeyIndex: number, secretSeed: Uint8Array, inputsData: Uint8Array, vrfInputDataLen: number): Promise<Uint8Array<ArrayBufferLike>>;
|
|
11
|
+
/**
|
|
12
|
+
* Batch-generate ring VRF tickets for multiple validators in a single call,
|
|
13
|
+
* reusing the ring prover setup across all of them.
|
|
14
|
+
*
|
|
15
|
+
* `secretSeedsData` is the fixed-width concatenation of the validators' secret
|
|
16
|
+
* seeds (each `secretSeedDataLen` bytes); `proverKeyIndices` are their indices
|
|
17
|
+
* within the ring and must have the same count. Output records are ordered
|
|
18
|
+
* validator-major then input-major, each `status byte || signature`.
|
|
19
|
+
*/
|
|
20
|
+
batchGenerateRingVrfForValidators(ringKeys: Uint8Array, proverKeyIndices: Uint32Array, secretSeedsData: Uint8Array, secretSeedDataLen: number, inputsData: Uint8Array, vrfInputDataLen: number): Promise<Uint8Array<ArrayBufferLike>>;
|
|
11
21
|
}
|
|
12
22
|
//# sourceMappingURL=bandersnatch-wasm.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bandersnatch-wasm.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/safrole/bandersnatch-wasm.ts"],"names":[],"mappings":"AAEA,qBAAa,gBAAgB;IAC3B,OAAO;WAEM,GAAG;IAKV,UAAU,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU;IAIjG,iBAAiB,CACrB,SAAS,EAAE,UAAU,EACrB,UAAU,EAAE,UAAU,EACtB,iBAAiB,EAAE,UAAU,EAC7B,cAAc,EAAE,UAAU,EAC1B,WAAW,EAAE,UAAU,EACvB,oBAAoB,EAAE,UAAU;IAY5B,iBAAiB,CAAC,IAAI,EAAE,UAAU;IAIlC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM;IAI1G,YAAY,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU;IAI1E,gBAAgB,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU;IAIzD,oBAAoB,CACxB,QAAQ,EAAE,UAAU,EACpB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,UAAU,EACtB,eAAe,EAAE,MAAM;
|
|
1
|
+
{"version":3,"file":"bandersnatch-wasm.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/safrole/bandersnatch-wasm.ts"],"names":[],"mappings":"AAEA,qBAAa,gBAAgB;IAC3B,OAAO;WAEM,GAAG;IAKV,UAAU,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU;IAIjG,iBAAiB,CACrB,SAAS,EAAE,UAAU,EACrB,UAAU,EAAE,UAAU,EACtB,iBAAiB,EAAE,UAAU,EAC7B,cAAc,EAAE,UAAU,EAC1B,WAAW,EAAE,UAAU,EACvB,oBAAoB,EAAE,UAAU;IAY5B,iBAAiB,CAAC,IAAI,EAAE,UAAU;IAIlC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM;IAI1G,YAAY,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU;IAI1E,gBAAgB,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU;IAIzD,oBAAoB,CACxB,QAAQ,EAAE,UAAU,EACpB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,UAAU,EACtB,eAAe,EAAE,MAAM;IAKzB;;;;;;;;OAQG;IACG,iCAAiC,CACrC,QAAQ,EAAE,UAAU,EACpB,gBAAgB,EAAE,WAAW,EAC7B,eAAe,EAAE,UAAU,EAC3B,iBAAiB,EAAE,MAAM,EACzB,UAAU,EAAE,UAAU,EACtB,eAAe,EAAE,MAAM;CAW1B"}
|
|
@@ -26,4 +26,16 @@ export class BandernsatchWasm {
|
|
|
26
26
|
async batchGenerateRingVrf(ringKeys, proverKeyIndex, secretSeed, inputsData, vrfInputDataLen) {
|
|
27
27
|
return bandersnatchWasm.batchGenerateRingVrf(ringKeys, proverKeyIndex, secretSeed, inputsData, vrfInputDataLen);
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Batch-generate ring VRF tickets for multiple validators in a single call,
|
|
31
|
+
* reusing the ring prover setup across all of them.
|
|
32
|
+
*
|
|
33
|
+
* `secretSeedsData` is the fixed-width concatenation of the validators' secret
|
|
34
|
+
* seeds (each `secretSeedDataLen` bytes); `proverKeyIndices` are their indices
|
|
35
|
+
* within the ring and must have the same count. Output records are ordered
|
|
36
|
+
* validator-major then input-major, each `status byte || signature`.
|
|
37
|
+
*/
|
|
38
|
+
async batchGenerateRingVrfForValidators(ringKeys, proverKeyIndices, secretSeedsData, secretSeedDataLen, inputsData, vrfInputDataLen) {
|
|
39
|
+
return bandersnatchWasm.batchGenerateRingVrfForValidators(ringKeys, proverKeyIndices, secretSeedsData, secretSeedDataLen, inputsData, vrfInputDataLen);
|
|
40
|
+
}
|
|
29
41
|
}
|
|
@@ -9,7 +9,8 @@ import { Result } from "#@typeberry/utils";
|
|
|
9
9
|
* it delegates to another process that doesn't bother to send the id back over the wire.
|
|
10
10
|
*/
|
|
11
11
|
export type ValidatedTicket = {
|
|
12
|
-
|
|
12
|
+
ticket: SignedTicket;
|
|
13
|
+
id: EntropyHash;
|
|
13
14
|
};
|
|
14
15
|
/** Reasons a ticket may fail validation. */
|
|
15
16
|
export declare enum ValidationError {
|
|
@@ -27,20 +28,20 @@ export declare enum ValidationError {
|
|
|
27
28
|
* worker via IPC, or short-circuit (Accept/Deny defaults for tests).
|
|
28
29
|
*/
|
|
29
30
|
export interface TicketValidator {
|
|
30
|
-
validate(epochIndex: Epoch,
|
|
31
|
+
validate(epochIndex: Epoch, tickets: SignedTicket[]): Promise<Result<ValidatedTicket[], ValidationError>>;
|
|
31
32
|
}
|
|
32
33
|
/**
|
|
33
34
|
* Accepts every ticket without inspection. Useful for unit tests where the validator
|
|
34
35
|
* isn't the subject under test. Must never be used in production.
|
|
35
36
|
*/
|
|
36
37
|
export declare class AcceptTicketsValidator implements TicketValidator {
|
|
37
|
-
validate(_epochIndex: Epoch,
|
|
38
|
+
validate(_epochIndex: Epoch, ticket: SignedTicket[]): Promise<Result<ValidatedTicket[], ValidationError>>;
|
|
38
39
|
}
|
|
39
40
|
/**
|
|
40
41
|
* Rejects every ticket. Used as the default for any task that needs an explicit, real
|
|
41
42
|
* validator wired in before it will accept anything from the network.
|
|
42
43
|
*/
|
|
43
44
|
export declare class DenyTicketsValidator implements TicketValidator {
|
|
44
|
-
validate(_epochIndex: Epoch,
|
|
45
|
+
validate(_epochIndex: Epoch, _tickets: SignedTicket[]): Promise<Result<ValidatedTicket[], ValidationError>>;
|
|
45
46
|
}
|
|
46
47
|
//# sourceMappingURL=ticket-validator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ticket-validator.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/ticket-pool/ticket-validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"ticket-validator.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/ticket-pool/ticket-validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGhE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,YAAY,CAAC;IACrB,EAAE,EAAE,WAAW,CAAC;CACjB,CAAC;AAEF,4CAA4C;AAC5C,oBAAY,eAAe;IACzB,+CAA+C;IAC/C,YAAY,kBAAkB;IAC9B,oFAAoF;IACpF,oBAAoB,0BAA0B;IAC9C,yEAAyE;IACzE,UAAU,gBAAgB;CAC3B;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC;CAC3G;AAED;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,eAAe;IACtD,QAAQ,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,EAAE,eAAe,CAAC,CAAC;CAQhH;AAED;;;GAGG;AACH,qBAAa,oBAAqB,YAAW,eAAe;IACpD,QAAQ,CAAC,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,EAAE,eAAe,CAAC,CAAC;CAGlH"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Bytes } from "#@typeberry/bytes";
|
|
2
|
+
import { HASH_SIZE } from "#@typeberry/hash";
|
|
1
3
|
import { Result } from "#@typeberry/utils";
|
|
2
4
|
/** Reasons a ticket may fail validation. */
|
|
3
5
|
export var ValidationError;
|
|
@@ -14,8 +16,11 @@ export var ValidationError;
|
|
|
14
16
|
* isn't the subject under test. Must never be used in production.
|
|
15
17
|
*/
|
|
16
18
|
export class AcceptTicketsValidator {
|
|
17
|
-
async validate(_epochIndex,
|
|
18
|
-
return Result.ok(
|
|
19
|
+
async validate(_epochIndex, ticket) {
|
|
20
|
+
return Result.ok(ticket.map((ticket) => ({
|
|
21
|
+
ticket,
|
|
22
|
+
id: Bytes.zero(HASH_SIZE).asOpaque(),
|
|
23
|
+
})));
|
|
19
24
|
}
|
|
20
25
|
}
|
|
21
26
|
/**
|
|
@@ -23,7 +28,7 @@ export class AcceptTicketsValidator {
|
|
|
23
28
|
* validator wired in before it will accept anything from the network.
|
|
24
29
|
*/
|
|
25
30
|
export class DenyTicketsValidator {
|
|
26
|
-
async validate(_epochIndex,
|
|
31
|
+
async validate(_epochIndex, _tickets) {
|
|
27
32
|
return Result.error(ValidationError.ValidatorUnavailable, () => "no ticket validator wired");
|
|
28
33
|
}
|
|
29
34
|
}
|
|
@@ -4,6 +4,7 @@ import { tryAsEpoch } from "#@typeberry/block";
|
|
|
4
4
|
import { SignedTicket, tryAsTicketAttempt } from "#@typeberry/block/tickets.js";
|
|
5
5
|
import { Bytes } from "#@typeberry/bytes";
|
|
6
6
|
import { BANDERSNATCH_PROOF_BYTES } from "#@typeberry/crypto";
|
|
7
|
+
import { HASH_SIZE } from "#@typeberry/hash";
|
|
7
8
|
import { AcceptTicketsValidator, DenyTicketsValidator, ValidationError } from "./ticket-validator.js";
|
|
8
9
|
const E1 = tryAsEpoch(1);
|
|
9
10
|
function makeTicket() {
|
|
@@ -13,19 +14,19 @@ function makeTicket() {
|
|
|
13
14
|
});
|
|
14
15
|
}
|
|
15
16
|
describe("AcceptTicketsValidator", () => {
|
|
16
|
-
it("returns ok with
|
|
17
|
+
it("returns ok with zero id", async () => {
|
|
17
18
|
const v = new AcceptTicketsValidator();
|
|
18
|
-
const res = await v.validate(E1, makeTicket());
|
|
19
|
+
const res = await v.validate(E1, [makeTicket()]);
|
|
19
20
|
assert.strictEqual(res.isOk, true);
|
|
20
21
|
if (res.isOk) {
|
|
21
|
-
assert.strictEqual(res.ok.id,
|
|
22
|
+
assert.strictEqual(res.ok[0].id.toString(), Bytes.zero(HASH_SIZE).toString());
|
|
22
23
|
}
|
|
23
24
|
});
|
|
24
25
|
});
|
|
25
26
|
describe("DenyTicketsValidator", () => {
|
|
26
27
|
it("returns ValidatorUnavailable", async () => {
|
|
27
28
|
const v = new DenyTicketsValidator();
|
|
28
|
-
const res = await v.validate(E1, makeTicket());
|
|
29
|
+
const res = await v.validate(E1, [makeTicket()]);
|
|
29
30
|
assert.strictEqual(res.isError, true);
|
|
30
31
|
if (res.isError) {
|
|
31
32
|
assert.strictEqual(res.error, ValidationError.ValidatorUnavailable);
|
|
@@ -16,6 +16,8 @@ export type VerifiedTicket = {
|
|
|
16
16
|
export declare class VerifiedTicketPool {
|
|
17
17
|
private readonly perEpoch;
|
|
18
18
|
private readonly idSets;
|
|
19
|
+
static new(): VerifiedTicketPool;
|
|
20
|
+
private constructor();
|
|
19
21
|
/** Add pre-verified tickets to the pool, deduping by id. */
|
|
20
22
|
add(epochIndex: Epoch, verifiedTickets: readonly VerifiedTicket[]): void;
|
|
21
23
|
/** Returns the verified tickets for the given epoch, or an empty array if none. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verified-ticket-pool.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/ticket-pool/verified-ticket-pool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGhE,yFAAyF;AACzF,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,EAAE,EAAE,WAAW,CAAC;CACjB,CAAC;AAEF;;;;;;;GAOG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsC;IAC/D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;IAEjE,4DAA4D;IAC5D,GAAG,CAAC,UAAU,EAAE,KAAK,EAAE,eAAe,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI;IAoBxE,mFAAmF;IACnF,WAAW,CAAC,UAAU,EAAE,KAAK,GAAG,SAAS,cAAc,EAAE;CAG1D"}
|
|
1
|
+
{"version":3,"file":"verified-ticket-pool.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/ticket-pool/verified-ticket-pool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGhE,yFAAyF;AACzF,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,EAAE,EAAE,WAAW,CAAC;CACjB,CAAC;AAEF;;;;;;;GAOG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsC;IAC/D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;IAEjE,MAAM,CAAC,GAAG;IAIV,OAAO;IAEP,4DAA4D;IAC5D,GAAG,CAAC,UAAU,EAAE,KAAK,EAAE,eAAe,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI;IAoBxE,mFAAmF;IACnF,WAAW,CAAC,UAAU,EAAE,KAAK,GAAG,SAAS,cAAc,EAAE;CAG1D"}
|
|
@@ -10,6 +10,10 @@ import { HashSet } from "#@typeberry/collections/hash-set.js";
|
|
|
10
10
|
export class VerifiedTicketPool {
|
|
11
11
|
perEpoch = new Map();
|
|
12
12
|
idSets = new Map();
|
|
13
|
+
static new() {
|
|
14
|
+
return new VerifiedTicketPool();
|
|
15
|
+
}
|
|
16
|
+
constructor() { }
|
|
13
17
|
/** Add pre-verified tickets to the pool, deduping by id. */
|
|
14
18
|
add(epochIndex, verifiedTickets) {
|
|
15
19
|
if (this.perEpoch.size > 0 && !this.perEpoch.has(epochIndex)) {
|
|
@@ -21,17 +21,17 @@ function makeId(byte) {
|
|
|
21
21
|
}
|
|
22
22
|
describe("VerifiedTicketPool", () => {
|
|
23
23
|
it("starts empty", () => {
|
|
24
|
-
const pool = new
|
|
24
|
+
const pool = VerifiedTicketPool.new();
|
|
25
25
|
assert.deepStrictEqual(pool.getForEpoch(E1), []);
|
|
26
26
|
});
|
|
27
27
|
it("adds and retrieves tickets per epoch", () => {
|
|
28
|
-
const pool = new
|
|
28
|
+
const pool = VerifiedTicketPool.new();
|
|
29
29
|
pool.add(E1, [{ ticket: makeTicket(1), id: makeId(0xaa) }]);
|
|
30
30
|
assert.strictEqual(pool.getForEpoch(E1).length, 1);
|
|
31
31
|
assert.deepStrictEqual(pool.getForEpoch(E2), []);
|
|
32
32
|
});
|
|
33
33
|
it("dedups by id", () => {
|
|
34
|
-
const pool = new
|
|
34
|
+
const pool = VerifiedTicketPool.new();
|
|
35
35
|
const id = makeId(0x01);
|
|
36
36
|
pool.add(E1, [{ ticket: makeTicket(1), id }]);
|
|
37
37
|
pool.add(E1, [{ ticket: makeTicket(2), id }]);
|
|
@@ -39,14 +39,14 @@ describe("VerifiedTicketPool", () => {
|
|
|
39
39
|
assert.strictEqual(pool.getForEpoch(E1)[0].ticket.signature.raw[0], 1);
|
|
40
40
|
});
|
|
41
41
|
it("clears previous epochs when a new epoch is added", () => {
|
|
42
|
-
const pool = new
|
|
42
|
+
const pool = VerifiedTicketPool.new();
|
|
43
43
|
pool.add(E1, [{ ticket: makeTicket(1), id: makeId(1) }]);
|
|
44
44
|
pool.add(E2, [{ ticket: makeTicket(2), id: makeId(2) }]);
|
|
45
45
|
assert.deepStrictEqual(pool.getForEpoch(E1), []);
|
|
46
46
|
assert.strictEqual(pool.getForEpoch(E2).length, 1);
|
|
47
47
|
});
|
|
48
48
|
it("appends across multiple add() calls for the same epoch", () => {
|
|
49
|
-
const pool = new
|
|
49
|
+
const pool = VerifiedTicketPool.new();
|
|
50
50
|
pool.add(E1, [{ ticket: makeTicket(1), id: makeId(1) }]);
|
|
51
51
|
pool.add(E1, [{ ticket: makeTicket(2), id: makeId(2) }]);
|
|
52
52
|
assert.strictEqual(pool.getForEpoch(E1).length, 2);
|
|
@@ -2,10 +2,12 @@ import type { MessagePort } from "node:worker_threads";
|
|
|
2
2
|
import { type Decode, type Encode } from "#@typeberry/codec";
|
|
3
3
|
import { ChainSpec } from "#@typeberry/config";
|
|
4
4
|
import { type BlocksDb, type RootDb, type SerializedStatesDb } from "#@typeberry/database";
|
|
5
|
+
import { FjallValuesSession } from "#@typeberry/database-fjall";
|
|
5
6
|
import { Blake2b } from "#@typeberry/hash";
|
|
6
7
|
import type { WorkerConfig } from "#@typeberry/workers-api";
|
|
7
8
|
import { ThreadPort, type TransferablePort } from "./port.js";
|
|
8
|
-
|
|
9
|
+
export { FjallValuesSession };
|
|
10
|
+
/** Worker config for node.js, backed by the LMDB database. */
|
|
9
11
|
export declare class LmdbWorkerConfig<T = void> implements WorkerConfig<T, BlocksDb, SerializedStatesDb> {
|
|
10
12
|
readonly nodeName: string;
|
|
11
13
|
readonly chainSpec: ChainSpec;
|
|
@@ -75,11 +77,10 @@ export declare class InMemWorkerConfig<T = undefined> implements WorkerConfig<T,
|
|
|
75
77
|
export type HybridBackend = "lmdb" | "fjall";
|
|
76
78
|
/**
|
|
77
79
|
* Hybrid worker config for the fuzz target: in-memory blocks and leaf sets,
|
|
78
|
-
* but large values persisted to disk
|
|
80
|
+
* but large values persisted to disk. The `backend` picks where the values go
|
|
81
|
+
* (lmdb or fjall).
|
|
79
82
|
*
|
|
80
|
-
*
|
|
81
|
-
* before committing to it. fjall opens its keyspace asynchronously, hence the
|
|
82
|
-
* async `new`.
|
|
83
|
+
* fjall opens its keyspace asynchronously, that is why `new` here is async.
|
|
83
84
|
*
|
|
84
85
|
* Like `InMemWorkerConfig`, the blocks and leaf sets are shared across the
|
|
85
86
|
* open/close/reopen dance that genesis init performs, so `openDatabase`
|
|
@@ -99,7 +100,7 @@ export declare class HybridWorkerConfig<T = undefined> implements WorkerConfig<T
|
|
|
99
100
|
readonly ephemeral: boolean;
|
|
100
101
|
readonly compression: boolean;
|
|
101
102
|
private readonly states;
|
|
102
|
-
static new<T>({ nodeName, chainSpec, workerParams, blake2b, dbPath, ephemeral, compression, backend, }: {
|
|
103
|
+
static new<T>({ nodeName, chainSpec, workerParams, blake2b, dbPath, ephemeral, compression, backend, sharedFjallSession, }: {
|
|
103
104
|
nodeName: string;
|
|
104
105
|
chainSpec: ChainSpec;
|
|
105
106
|
workerParams: T;
|
|
@@ -108,6 +109,13 @@ export declare class HybridWorkerConfig<T = undefined> implements WorkerConfig<T
|
|
|
108
109
|
ephemeral?: boolean;
|
|
109
110
|
compression?: boolean;
|
|
110
111
|
backend?: HybridBackend;
|
|
112
|
+
/**
|
|
113
|
+
* Reuse an already-open fjall values session instead of opening a fresh
|
|
114
|
+
* keyspace. The fuzz target opens one per run and passes it on every reset,
|
|
115
|
+
* so only the in-memory blocks/leaf sets are rebuilt per vector. Ignored
|
|
116
|
+
* unless `backend === "fjall"`.
|
|
117
|
+
*/
|
|
118
|
+
sharedFjallSession?: FjallValuesSession;
|
|
111
119
|
}): Promise<HybridWorkerConfig<T>>;
|
|
112
120
|
private readonly blocks;
|
|
113
121
|
private constructor();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/api-node/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,KAAK,MAAM,EAAW,KAAK,MAAM,EAAW,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EACL,KAAK,QAAQ,EAGb,KAAK,MAAM,EACX,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/api-node/config.ts"],"names":[],"mappings":"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"}
|